1 Commits

Author SHA1 Message Date
be910bcabe WIP refactoring geofavorites list activity into fragment 2021-09-15 09:16:40 +02:00
139 changed files with 1206 additions and 3755 deletions

7
.gitignore vendored
View File

@ -96,10 +96,3 @@ lint/generated/
lint/outputs/ lint/outputs/
lint/tmp/ lint/tmp/
# lint/reports/ # lint/reports/
app/release/output-metadata.json
.idea/deploymentTargetDropDown.xml
.idea/misc.xml
.idea/deploymentTargetSelector.xml
.idea/other.xml

2
.idea/compiler.xml generated
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" /> <bytecodeTargetLevel target="1.8" />
</component> </component>
</project> </project>

17
.idea/deploymentTargetDropDown.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_XL_API_30.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-09-14T16:48:49.750165Z" />
</component>
</project>

10
.idea/migrations.xml generated
View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectMigrations">
<option name="MigrateToGradleLocalJavaHome">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
</component>
</project>

19
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
<entry key="app/src/main/res/layout/activity_geofavorite_detail.xml" value="0.4" />
<entry key="app/src/main/res/layout/activity_list_view.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5218150087260035" />
<entry key="app/src/main/res/layout/fragment_geofavorite_list.xml" value="0.1734375" />
<entry key="app/src/main/res/layout/fragment_map.xml" value="0.19010416666666666" />
<entry key="app/src/main/res/layout/item_geofav.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/menu/list_context_menu.xml" value="0.41944444444444445" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

View File

@ -1,20 +1,15 @@
![Nextcloud Maps Geofavorites Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) ![Nextcloud Maps Geobookmarks Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
# Nextcloud Maps Geofavorites Android app # Nextcloud Maps Geobookmarks Android app
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/it/packages/it.danieleverducci.nextcloudmaps) Android app to show your Nextcloud Maps geobookmarks list. Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
[<img src="https://cdn.rawgit.com/steverichey/google-play-badge-svg/master/img/en_get.svg" height="80">](https://play.google.com/store/apps/details?id=it.danieleverducci.nextcloudmaps) A new geobookmark can be created on current location.
[<img src="https://raw.githubusercontent.com/andOTP/andOTP/master/assets/badges/get-it-on-github.png" height="80">](https://github.com/penguin86/nextcloud-maps-client/releases/latest)
(Always prefer [F-Droid](https://f-droid.org) build, when possible).
UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geofavorites in a list and a map.
Geofavorites can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
A new geofavorite can be created on current location, by sharing a "geo:" uri from another app or manually picking from the map.
**Requires Maps app to be installed on the Nextcloud instance.** **Requires Maps app to be installed on the Nextcloud instance.**
This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://github.com/matiasdelellis/app-tutorial-android) to implement [Nextcloud single sign on](https://github.com/nextcloud/Android-SingleSignOn). This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://github.com/matiasdelellis/app-tutorial-android) to implement [Nextcloud single sign on](https://github.com/nextcloud/Android-SingleSignOn).
![Screenshot 1](screenshots/1.png) ![Screenshot 2](screenshots/2.png) ![Screenshot 3](screenshots/3.png) ![Screenshot 1](screenshots/1.png) ![Screenshot 1](screenshots/2.png)
Download it from [the releases page](https://github.com/penguin86/nextcloud-maps-client/releases)

View File

@ -18,13 +18,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 30
defaultConfig { defaultConfig {
applicationId "it.danieleverducci.nextcloudmaps" applicationId "it.danieleverducci.nextcloudmaps"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 35 targetSdkVersion 30
compileSdk 35 versionCode 2
versionCode 9 versionName "0.2"
versionName "0.4.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -36,9 +37,6 @@ android {
} }
} }
compileOptions { compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@ -46,7 +44,6 @@ android {
buildFeatures { buildFeatures {
dataBinding true dataBinding true
} }
namespace 'it.danieleverducci.nextcloudmaps'
} }
repositories { repositories {
@ -57,40 +54,26 @@ repositories {
dependencies { dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
// Desugaring lib: see https://developer.android.com/studio/write/java8-support implementation 'com.android.support:design:30.0.1'
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.2") implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.android.support:design:34.0.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation "androidx.preference:preference:1.2.1" implementation "androidx.preference:preference:1.1.1"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Retrofif2 // Retrofif2
implementation 'com.squareup.retrofit2:retrofit:2.11.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1' implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
// Nextcloud SSO // Nextcloud SSO
implementation "com.github.nextcloud:Android-SingleSignOn:1.3.2" implementation "com.github.nextcloud:Android-SingleSignOn:0.5.6"
// OSMDroid // OSMDroid
implementation 'org.osmdroid:osmdroid-android:6.1.18' compile 'org.osmdroid:osmdroid-android:6.1.10'
//Threeten-Backport (ports Java 8 Date API on Java 6+)
implementation 'org.threeten:threetenbp:1.5.1'
// https://mvnrepository.com/artifact/commons-io/commons-io
implementation 'commons-io:commons-io:2.13.0'
// Picasso (image loader)
implementation 'com.squareup.picasso:picasso:2.8'
configurations.all {
resolutionStrategy {
force 'commons-io:commons-io:2.11.0'
}
}
} }

View File

@ -0,0 +1,18 @@
{
"version": 2,
"artifactType": {
"type": "APK",
"kind": "Directory"
},
"applicationId": "it.danieleverducci.nextcloudmaps",
"variantName": "release",
"elements": [
{
"type": "SINGLE",
"filters": [],
"versionCode": 2,
"versionName": "0.2",
"outputFile": "app-release.apk"
}
]
}

View File

@ -17,16 +17,14 @@
--> -->
<manifest <manifest
xmlns:android="http://schemas.android.com/apk/res/android"> xmlns:android="http://schemas.android.com/apk/res/android"
package="it.danieleverducci.nextcloudmaps">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<queries> <queries>
<package android:name="com.nextcloud.client" /> <package android:name="com.nextcloud.client" />
<!-- To see if google maps is installed, as it needs a specific intent Uri) -->
<package android:name="com.google.android.apps.maps" />
</queries> </queries>
<application <application
@ -37,16 +35,14 @@
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<activity android:name=".activity.login.LoginActivity" <activity android:name=".activity.login.LoginActivity">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".activity.main.MainActivity" <activity android:name=".activity.main.MainActivity">
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
</intent-filter> </intent-filter>
@ -54,62 +50,7 @@
<activity <activity
android:name=".activity.detail.GeofavoriteDetailActivity" android:name=".activity.detail.GeofavoriteDetailActivity"
android:exported="true"> android:theme="@style/AppTheme"/>
<!-- standard "geo" scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="geo"/>
</intent-filter>
<!-- Google Maps -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="google.navigation"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="maps.google.com"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="www.google.com"
android:pathPrefix="/maps"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="www.openstreetmap.org"
android:scheme="https"/>
</intent-filter>
</activity>
<activity android:name=".activity.mappicker.MapPickerActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity <activity
android:name=".activity.about.AboutActivity" android:name=".activity.about.AboutActivity"

View File

@ -1,26 +0,0 @@
package it.danieleverducci.nextcloudmaps.activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.osmdroid.views.overlay.TilesOverlay;
public class NextcloudMapsStyledActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// For whatever reason, android:windowLightStatusBar is ignored in styles.xml
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentNightMode == Configuration.UI_MODE_NIGHT_YES)
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);
}
}

View File

@ -30,9 +30,8 @@ import androidx.appcompat.widget.Toolbar;
import it.danieleverducci.nextcloudmaps.BuildConfig; import it.danieleverducci.nextcloudmaps.BuildConfig;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
public class AboutActivity extends NextcloudMapsStyledActivity { public class AboutActivity extends AppCompatActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -63,10 +62,6 @@ public class AboutActivity extends NextcloudMapsStyledActivity {
TextView tvIssues = findViewById(R.id.about_issues); TextView tvIssues = findViewById(R.id.about_issues);
tvIssues.setText(Html.fromHtml(getString(R.string.about_issues, getString(R.string.url_issues)))); tvIssues.setText(Html.fromHtml(getString(R.string.about_issues, getString(R.string.url_issues))));
tvIssues.setOnClickListener(view -> openUtl(getString(R.string.url_issues))); tvIssues.setOnClickListener(view -> openUtl(getString(R.string.url_issues)));
TextView tvMaps = findViewById(R.id.about_maps);
tvMaps.setText(Html.fromHtml(getString(R.string.about_maps)));
tvMaps.setOnClickListener(view -> openUtl(getString(R.string.url_maps)));
} }
@Override @Override

View File

@ -1,47 +0,0 @@
package it.danieleverducci.nextcloudmaps.activity.detail;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
import java.util.ArrayList;
import java.util.HashSet;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class CategoriesAdapter extends ArrayAdapter<String> {
public CategoriesAdapter(@NonNull Context context) {
super(context, R.layout.category_listitem, R.id.category_name, new ArrayList<>());
}
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
View v = super.getView(position, convertView, parent);
TextView categoryName = v.findViewById(R.id.category_name);
Drawable backgroundDrawable = categoryName.getBackground();
DrawableCompat.setTint(
backgroundDrawable,
Geofavorite.categoryColorFromName(categoryName.getText().toString()) == 0
? v.getContext().getColor(R.color.defaultBrand)
: Geofavorite.categoryColorFromName(categoryName.getText().toString())
);
return v;
}
public void setCategoriesList(HashSet<String> categories) {
clear();
addAll(categories);
notifyDataSetChanged();
}
}

View File

@ -18,14 +18,13 @@
package it.danieleverducci.nextcloudmaps.activity.detail; package it.danieleverducci.nextcloudmaps.activity.detail;
import android.Manifest; import android.Manifest;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Location; import android.location.Location;
import android.location.LocationListener; import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -34,10 +33,9 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import org.osmdroid.api.IMapController; import org.osmdroid.api.IMapController;
@ -45,60 +43,44 @@ import org.osmdroid.config.Configuration;
import org.osmdroid.util.GeoPoint; import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController; import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.overlay.Marker; import org.osmdroid.views.overlay.Marker;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.FormatStyle;
import java.util.HashSet; import java.util.Date;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity; import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.databinding.ActivityGeofavoriteDetailBinding; import it.danieleverducci.nextcloudmaps.databinding.ActivityGeofavoriteDetailBinding;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.EdgeToEdgeUtils; import retrofit2.Call;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser; import retrofit2.Callback;
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator; import retrofit2.Response;
import it.danieleverducci.nextcloudmaps.utils.MapUtils;
public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback { public class GeofavoriteDetailActivity extends AppCompatActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
public static final String TAG = "GeofavDetail"; public static final String TAG = "GeofavDetail";
public static final String DEFAULT_CATEGORY = "Personal";
public static final int MINIMUM_ACCEPTABLE_ACCURACY = 50; // In meters
public static final String ARG_GEOFAVORITE = "geofav"; public static final String ARG_GEOFAVORITE = "geofav";
private static final int PERMISSION_REQUEST_CODE = 9999; private static final int PERMISSION_REQUEST_CODE = 9999;
private ViewHolder mViewHolder; private ViewHolder mViewHolder;
private Geofavorite mGeofavorite; private Geofavorite mGeofavorite;
private GeofavoriteDetailActivityViewModel mViewModel;
@Override @Override
protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
MapUtils.configOsmdroid(this); // OSMDroid config
Configuration.getInstance().load(getApplicationContext(),
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
mViewHolder = new ViewHolder(getLayoutInflater()); mViewHolder = new ViewHolder(getLayoutInflater());
setContentView(mViewHolder.getRootView()); setContentView(mViewHolder.getRootView());
EdgeToEdgeUtils.clearTopBarWithPadding(mViewHolder.binding.appbar);
mViewHolder.setOnSubmitListener(new OnSubmitListener() { mViewHolder.setOnSubmitListener(new OnSubmitListener() {
@Override @Override
public void onBackPressed() { public void onBackPressed() {
finish(); finish();
} }
@Override
public void onActionIconShareClicked() {
startActivity(Intent.createChooser(IntentGenerator.newShareIntent(GeofavoriteDetailActivity.this, mGeofavorite), getString(R.string.share_via)));
}
@Override
public void onActionIconNavClicked() {
startActivity(IntentGenerator.newGeoUriIntent(GeofavoriteDetailActivity.this, mGeofavorite));
}
@Override
public void onActionIconDeleteClicked() {
// TODO
}
@Override @Override
public void onSubmit() { public void onSubmit() {
saveGeofavorite(); saveGeofavorite();
@ -106,67 +88,22 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
@Override @Override
public void onMapClicked() { public void onMapClicked() {
// TODO: Open map activity with pin Toast.makeText(GeofavoriteDetailActivity.this, "TODO: Open map activity with pin", Toast.LENGTH_SHORT).show();
startActivity(IntentGenerator.newGeoUriIntent(GeofavoriteDetailActivity.this, mGeofavorite));
} }
}); });
mViewModel = new ViewModelProvider(this).get(GeofavoriteDetailActivityViewModel.class); if (getIntent().hasExtra(ARG_GEOFAVORITE)) {
mViewModel.init(getApplicationContext());
mViewModel.getCategories().observe(this, new Observer<HashSet<String>>() {
@Override
public void onChanged(HashSet<String> categories) {
mViewHolder.setCategories(categories);
}
});
mViewModel.getIsUpdating().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean updating) {
mViewHolder.setUpdating(updating);
}
});
mViewModel.getOnFinished().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean success) {
if(success){
Toast.makeText(GeofavoriteDetailActivity.this, R.string.geofavorite_saved, Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show();
}
}
});
if (getIntent().hasExtra(ARG_GEOFAVORITE) && getIntent().getIntExtra(ARG_GEOFAVORITE, 0) != 0) {
// Opening geofavorite from list // Opening geofavorite from list
mGeofavorite = mViewModel.getGeofavorite( mGeofavorite = (Geofavorite) getIntent().getSerializableExtra(ARG_GEOFAVORITE);
getIntent().getIntExtra(ARG_GEOFAVORITE, 0)
);
mViewHolder.hideAccuracy(); mViewHolder.hideAccuracy();
} else { } else {
// New geofavorite // New geofavorite
mGeofavorite = new Geofavorite(); mGeofavorite = new Geofavorite();
mGeofavorite.setCategory(Geofavorite.DEFAULT_CATEGORY); mGeofavorite.setCategory(DEFAULT_CATEGORY);
mGeofavorite.setDateCreated(System.currentTimeMillis() / 1000); mGeofavorite.setDateCreated(System.currentTimeMillis());
mGeofavorite.setDateModified(System.currentTimeMillis() / 1000); mGeofavorite.setDateModified(System.currentTimeMillis());
mViewHolder.hideActions(); // Precompile location
getLocation();
if (getIntent().getData() != null) {
// Opened by external generic intent: parse URI
try {
double[] coords = GeoUriParser.parseUri(getIntent().getData(), false);
mGeofavorite.setLat(coords[0]);
mGeofavorite.setLng(coords[1]);
mViewHolder.hideAccuracy();
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.error_unsupported_uri, Toast.LENGTH_SHORT).show();
finish();
}
} else {
// Precompile location with current one
getLocation();
}
} }
mViewHolder.updateView(mGeofavorite); mViewHolder.updateView(mGeofavorite);
@ -210,7 +147,35 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
return; return;
} }
mViewModel.saveGeofavorite(mGeofavorite); Call<Geofavorite> call;
if (mGeofavorite.getId() == 0) {
// New geofavorite
call = ApiProvider.getAPI().createGeofavorite(mGeofavorite);
} else {
// Update existing geofavorite
call = ApiProvider.getAPI().updateGeofavorite(mGeofavorite.getId(), mGeofavorite);
}
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
if (response.isSuccessful())
finish();
else
onGeofavoriteSaveFailed();
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
onGeofavoriteSaveFailed();
Log.e(TAG, "Unable to update geofavorite: " + t.getMessage());
}
});
}
private void onGeofavoriteSaveFailed() {
runOnUiThread(() ->
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show()
);
} }
/** /**
@ -290,37 +255,18 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
private class ViewHolder implements View.OnClickListener { private class ViewHolder implements View.OnClickListener {
private final ActivityGeofavoriteDetailBinding binding; private final ActivityGeofavoriteDetailBinding binding;
private DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
private OnSubmitListener listener; private OnSubmitListener listener;
private Marker mapMarker; private Marker mapMarker;
public ViewHolder(LayoutInflater inflater) { public ViewHolder(LayoutInflater inflater) {
this.binding = ActivityGeofavoriteDetailBinding.inflate(inflater); this.binding = ActivityGeofavoriteDetailBinding.inflate(inflater);
this.binding.submitBt.setOnClickListener(this); this.binding.submitBt.setOnClickListener(this);
this.binding.mapBt.setOnClickListener(this); this.binding.mapBt.setOnClickListener(this);
this.binding.backBt.setOnClickListener(this); this.binding.backBt.setOnClickListener(this);
this.binding.actionIconShare.setOnClickListener(this);
this.binding.actionIconDelete.setOnClickListener(this);
this.binding.actionIconNav.setOnClickListener(this);
// Set categories adapter
CategoriesAdapter categoriesAdapter = new CategoriesAdapter(binding.root.getContext());
this.binding.categoryAt.setAdapter(categoriesAdapter);
this.binding.categoryAt.setText(Geofavorite.DEFAULT_CATEGORY);
this.binding.categoryAtClear.setOnClickListener((v) -> {
if (this.binding.categoryAt.getText().toString().isEmpty())
this.binding.categoryAt.setText(Geofavorite.DEFAULT_CATEGORY);
else
this.binding.categoryAt.setText("");
});
// Set map properties // Set map properties
this.binding.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER); this.binding.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
this.binding.map.setMultiTouchControls(true); this.binding.map.setMultiTouchControls(true);
MapUtils.setTheme(this.binding.map);
// this.binding.map.setTilesScaledToDpi(true);
// Create marker // Create marker
mapMarker = new Marker(binding.map); mapMarker = new Marker(binding.map);
@ -334,23 +280,16 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
} }
public void updateView(Geofavorite item) { public void updateView(Geofavorite item) {
binding.collapsingToolbar.setTitle(item.getName() != null ? item.getName() : getString(R.string.new_geobookmark)); binding.nameEt.setText(item.getName());
binding.nameEt.setText(item.getName() != null ? item.getName() : ""); binding.descriptionEt.setText(item.getComment());
binding.descriptionEt.setText(item.getComment() != null ? item.getComment() : ""); binding.createdTv.setText(new Date(item.getDateCreated() * 1000).toString());
binding.createdTv.setText(item.getLocalDateCreated().format(dateFormatter)); binding.modifiedTv.setText(new Date(item.getDateModified() * 1000).toString());
binding.modifiedTv.setText(item.getLocalDateCreated().format(dateFormatter)); binding.categoryTv.setText(item.getCategory()); // TODO: Category spinner from existing categories
binding.categoryAt.setText(item.getCategory() != null ? item.getCategory() : Geofavorite.DEFAULT_CATEGORY);
updateViewCoords(item); updateViewCoords(item);
} }
public void updateViewCoords(Geofavorite item) { public void updateViewCoords(Geofavorite item) {
binding.coordsTv.setText(item.getCoordinatesString()); binding.coordsTv.setText(item.getCoordinatesString());
binding.coordsTv.setOnClickListener((v) -> {
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText(item.getCoordinatesString(), item.getCoordinatesString());
clipboard.setPrimaryClip(clip);
Toast.makeText(GeofavoriteDetailActivity.this, R.string.coords_copied, Toast.LENGTH_SHORT).show();
});
// Center map // Center map
GeoPoint position = new GeoPoint(item.getLat(), item.getLng()); GeoPoint position = new GeoPoint(item.getLat(), item.getLng());
@ -365,34 +304,21 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
public void updateModel(Geofavorite item) { public void updateModel(Geofavorite item) {
item.setName(binding.nameEt.getText().toString()); item.setName(binding.nameEt.getText().toString());
item.setComment(binding.descriptionEt.getText().toString()); item.setComment(binding.descriptionEt.getText().toString());
item.setCategory(binding.categoryAt.getText().toString());
item.setDateModified(System.currentTimeMillis() / 1000); item.setDateModified(System.currentTimeMillis() / 1000);
} }
public void setUpdating(boolean updating) {
binding.progress.setVisibility(updating ? View.VISIBLE : View.GONE);
}
public void setAccuracy(float accuracy) { public void setAccuracy(float accuracy) {
// Display accuracy in meters
binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + "")); binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + ""));
// Display accuracy in progress bar // Color the accuracy background with a scale from red (MINIMUM_ACCEPTABLE_ACCURACY) to green (0 meters)
int accuracyPercent = (int)accuracy > 100 ? 0 : Math.abs((int)accuracy - 100); float red = accuracy / MINIMUM_ACCEPTABLE_ACCURACY;
binding.accuracyProgress.setIndeterminate(false); if (red > 1.0f) red = 1.0f;
binding.accuracyProgress.setProgress(accuracyPercent); float green = 1.0f - red;
} if (Build.VERSION.SDK_INT >= 26)
binding.accuracyTv.setBackgroundColor(Color.rgb(red, green, 0.0f));
public void setCategories(HashSet<String> categories) {
((CategoriesAdapter)binding.categoryAt.getAdapter()).setCategoriesList(categories);
} }
public void hideAccuracy() { public void hideAccuracy() {
binding.accuracyTv.setVisibility(View.GONE); binding.accuracyTv.setVisibility(View.GONE);
binding.accuracyProgressContainer.setVisibility(View.GONE);
}
public void hideActions() {
binding.actionIcons.setVisibility(View.GONE);
} }
public void setOnSubmitListener(OnSubmitListener listener) { public void setOnSubmitListener(OnSubmitListener listener) {
@ -418,17 +344,6 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
if (v.getId() == R.id.back_bt && this.listener != null) { if (v.getId() == R.id.back_bt && this.listener != null) {
this.listener.onBackPressed(); this.listener.onBackPressed();
} }
// Actions
if (v.getId() == R.id.action_icon_share && this.listener != null) {
this.listener.onActionIconShareClicked();
}
if (v.getId() == R.id.action_icon_nav && this.listener != null) {
this.listener.onActionIconNavClicked();
}
if (v.getId() == R.id.action_icon_delete && this.listener != null) {
this.listener.onActionIconDeleteClicked();
}
} }
} }
@ -436,8 +351,5 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
void onSubmit(); void onSubmit();
void onMapClicked(); void onMapClicked();
void onBackPressed(); void onBackPressed();
void onActionIconShareClicked();
void onActionIconNavClicked();
void onActionIconDeleteClicked();
} }
} }

View File

@ -1,43 +0,0 @@
package it.danieleverducci.nextcloudmaps.activity.detail;
import android.content.Context;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
public class GeofavoriteDetailActivityViewModel extends ViewModel {
private GeofavoriteRepository mRepo;
public void init(Context applicationContext) {
mRepo = GeofavoriteRepository.getInstance(applicationContext);
}
public Geofavorite getGeofavorite(int id) {
return mRepo.getGeofavorite(id);
}
public void saveGeofavorite(Geofavorite geofav) {
mRepo.saveGeofavorite(geofav);
}
public LiveData<HashSet<String>> getCategories(){
return mRepo.getCategories();
}
public LiveData<Boolean> getIsUpdating(){
return mRepo.isUpdating();
}
public LiveData<Boolean> getOnFinished(){
return mRepo.onFinished();
}
}

View File

@ -20,8 +20,6 @@ package it.danieleverducci.nextcloudmaps.activity.login;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.Button; import android.widget.Button;
import android.widget.ProgressBar; import android.widget.ProgressBar;
@ -41,14 +39,12 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.nextcloud.android.sso.ui.UiExceptionManager; import com.nextcloud.android.sso.ui.UiExceptionManager;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity; import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.api.API;
import it.danieleverducci.nextcloudmaps.api.ApiProvider; import it.danieleverducci.nextcloudmaps.api.ApiProvider;
public class LoginActivity extends NextcloudMapsStyledActivity { public class LoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
protected ApiProvider mApi;
protected ProgressBar progress; protected ProgressBar progress;
protected Button button; protected Button button;
@ -66,25 +62,18 @@ public class LoginActivity extends NextcloudMapsStyledActivity {
openAccountChooser(); openAccountChooser();
}); });
Handler h = new Handler(); try {
h.post(() -> { ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext());
try { SingleAccountHelper.setCurrentAccount(getApplicationContext(), ssoAccount.name);
ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()); accountAccessDone();
SingleAccountHelper.applyCurrentAccount(getApplicationContext(), ssoAccount.name); } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
accountAccessDone(); }
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
Log.e(TAG, "Autologin: " + e.toString());
}
});
} }
private void openAccountChooser() { private void openAccountChooser() {
try { try {
AccountImporter.pickNewAccount(this); AccountImporter.pickNewAccount(this);
} catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) { } catch (NextcloudFilesAppNotInstalledException | AndroidGetAccountsPermissionNotGranted e) {
UiExceptionManager.showDialogForException(this, e); UiExceptionManager.showDialogForException(this, e);
Log.e(TAG, "openAccountChooser: " + e.toString());
progress.setVisibility(View.GONE);
} }
} }
@ -108,7 +97,7 @@ public class LoginActivity extends NextcloudMapsStyledActivity {
@Override @Override
public void accountAccessGranted(SingleSignOnAccount account) { public void accountAccessGranted(SingleSignOnAccount account) {
Context l_context = getApplicationContext(); Context l_context = getApplicationContext();
SingleAccountHelper.applyCurrentAccount(l_context, account.name); SingleAccountHelper.setCurrentAccount(l_context, account.name);
accountAccessDone(); accountAccessDone();
} }
@ -125,6 +114,9 @@ public class LoginActivity extends NextcloudMapsStyledActivity {
} }
private void accountAccessDone() { private void accountAccessDone() {
Context l_context = getApplicationContext();
mApi = new ApiProvider(l_context);
Intent intent = new Intent(LoginActivity.this, MainActivity.class); Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent); startActivity(intent);

View File

@ -0,0 +1,11 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public interface GeofavoriteActivity {
void showGeofavoriteDetailActivity(Geofavorite item);
void showGeofavoriteDeteleDialog(Geofavorite item);
void updateGeofavorites();
void getGeofavorites();
}

View File

@ -21,49 +21,46 @@
package it.danieleverducci.nextcloudmaps.activity.main; package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context; import android.content.Context;
import android.graphics.drawable.Drawable; import android.content.Intent;
import android.text.Html; import android.text.Html;
import android.util.Log; import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.PopupMenu; import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.FormatStyle;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.GeofavoriteViewHolder> { public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.GeofavoriteViewHolder> implements Filterable {
public static final String TAG = "GeofavoriteAdapter"; public static final String TAG = "GeofavoriteAdapter";
public static final int SORT_BY_TITLE = 0; public static final int SORT_BY_TITLE = 0;
public static final int SORT_BY_CREATED = 1; public static final int SORT_BY_CREATED = 1;
public static final int SORT_BY_CATEGORY = 2;
public static final int SORT_BY_DISTANCE = 3;
private final Context context; private Context context;
private final ItemClickListener itemClickListener; private ItemClickListener itemClickListener;
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
private List<Geofavorite> items = new ArrayList<>(); private List<Geofavorite> geofavoriteList = new ArrayList<>();
private List<Geofavorite> geofavoriteListFiltered = new ArrayList<>();
private int sortRule = SORT_BY_CREATED; private int sortRule = SORT_BY_CREATED;
private int bottomInset = 0;
private int leftInset = 0;
private int rightInset = 0;
// Contains the position of the element containing the overflow menu clicked // Contains the position of the element containing the overflow menu clicked
private int overflowMenuSelectedPosition = -1; private int overflowMenuSelectedPosition = -1;
@ -74,15 +71,26 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
} }
public void setGeofavoriteList(@NonNull List<Geofavorite> geofavoriteList) { public void setGeofavoriteList(@NonNull List<Geofavorite> geofavoriteList) {
this.items.clear(); this.geofavoriteList = geofavoriteList;
this.items.addAll(geofavoriteList); this.geofavoriteListFiltered = new ArrayList<>(geofavoriteList);
performSort(); performSort();
notifyDataSetChanged(); notifyDataSetChanged();
} }
public Geofavorite get(int position) { public Geofavorite get(int position) {
return items.get(position); return geofavoriteListFiltered.get(position);
}
public void removeById(int id) {
for (Geofavorite g : geofavoriteList) {
if (g.getId() == id) {
geofavoriteList.remove(g);
geofavoriteListFiltered.remove(g);
break;
}
}
notifyDataSetChanged();
} }
public int getSortRule() { public int getSortRule() {
@ -105,46 +113,60 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
@Override @Override
public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) { public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
Geofavorite geofavorite = items.get(position); Geofavorite geofavorite = geofavoriteListFiltered.get(position);
holder.tv_category.setText(geofavorite.categoryLetter()); holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
holder.setCategoryColor( holder.tv_content.setText(geofavorite.getComment());
geofavorite.categoryColor() == 0 ? context.getColor(R.color.defaultBrand) : geofavorite.categoryColor());
holder.tv_title.setText(Html.fromHtml(geofavorite.getName() == null ? "" : geofavorite.getName()));
holder.tv_content.setText(geofavorite.getComment() == null ? "" : geofavorite.getComment());
holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter));
// Last item: Clear bottom system bar
holder.root_view.setPadding(
leftInset,
holder.root_view.getPaddingTop(),
rightInset,
position == getItemCount() - 1 ? bottomInset : 0
);
} }
@Override @Override
public int getItemCount() { public int getItemCount() {
return items.size(); return geofavoriteListFiltered.size();
} }
public void setBottomInset(int inset) { @Override
this.bottomInset = inset; public Filter getFilter() {
return filter;
} }
public void setLeftInset(int inset) { Filter filter = new Filter() {
this.leftInset = inset; @Override
} // Run on Background thread.
protected FilterResults performFiltering(CharSequence charSequence) {
FilterResults filterResults = new FilterResults();
List <Geofavorite> filteredGeofavorites = new ArrayList<>();
public void setRightInset(int inset) { if (charSequence.toString().isEmpty()) {
this.rightInset = inset; filteredGeofavorites.addAll(geofavoriteList);
} } else {
for (Geofavorite geofavorite : geofavoriteList) {
String query = charSequence.toString().toLowerCase();
if (geofavorite.getName() != null && geofavorite.getName().toLowerCase().contains(query)) {
filteredGeofavorites.add(geofavorite);
} else if (geofavorite.getComment() != null && geofavorite.getComment().toLowerCase().contains(query)) {
filteredGeofavorites.add(geofavorite);
}
}
}
filterResults.values = filteredGeofavorites;
return filterResults;
}
//Run on ui thread
@Override
protected void publishResults(CharSequence charSequence, FilterResults filterResults) {
geofavoriteListFiltered.clear();
geofavoriteListFiltered.addAll((Collection<? extends Geofavorite>) filterResults.values);
performSort();
notifyDataSetChanged();
}
};
class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
View root_view; TextView tv_title, tv_content;
TextView tv_category, tv_title, tv_content, tv_date;
ImageView bt_context_menu; ImageView bt_context_menu;
ImageView bt_nav; ImageView bt_share;
ItemClickListener itemClickListener; ItemClickListener itemClickListener;
@ -152,54 +174,40 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) { GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
super(itemView); super(itemView);
root_view = itemView;
tv_category = itemView.findViewById(R.id.tv_category);
tv_title = itemView.findViewById(R.id.title); tv_title = itemView.findViewById(R.id.title);
tv_content = itemView.findViewById(R.id.content); tv_content = itemView.findViewById(R.id.content);
tv_date = itemView.findViewById(R.id.date);
bt_context_menu = itemView.findViewById(R.id.geofav_context_menu_bt); bt_context_menu = itemView.findViewById(R.id.geofav_context_menu_bt);
bt_nav = itemView.findViewById(R.id.geofav_nav_bt); bt_share = itemView.findViewById(R.id.geofav_share_bt);
this.itemClickListener = itemClickListener; this.itemClickListener = itemClickListener;
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
bt_context_menu.setOnClickListener(this); bt_context_menu.setOnClickListener(this);
bt_nav.setOnClickListener(this); bt_share.setOnClickListener(this);
} }
@Override @Override
public void onClick(View view) { public void onClick(View view) {
switch (view.getId()) { switch (view.getId()) {
case R.id.geofav_context_menu_bt: case R.id.geofav_context_menu_bt:
onOverflowIconClicked(view, getBindingAdapterPosition()); onOverflowIconClicked(view, getAdapterPosition());
break; break;
case R.id.geofav_nav_bt: case R.id.geofav_share_bt:
if (itemClickListener != null) if (itemClickListener != null)
itemClickListener.onItemNavClick(get(getBindingAdapterPosition())); itemClickListener.onItemShareClick(get(getAdapterPosition()));
break; break;
default: default:
if (itemClickListener != null) if (itemClickListener != null)
itemClickListener.onItemClick(get(getBindingAdapterPosition())); itemClickListener.onItemClick(get(getAdapterPosition()));
} }
} }
public void setCategoryColor(int ccTint) {
Drawable bg = DrawableCompat.wrap(this.tv_category.getContext().getDrawable(R.drawable.ic_list_pin));
this.tv_category.setBackground(bg);
DrawableCompat.setTint(bg, ccTint);
}
} }
private void performSort() { private void performSort() {
if (sortRule == SORT_BY_TITLE) { if (sortRule == SORT_BY_TITLE) {
Collections.sort(items, Geofavorite.ByTitleAZ); Collections.sort(geofavoriteListFiltered, Geofavorite.ByTitleAZ);
} else if (sortRule == SORT_BY_CREATED) { } else if (sortRule == SORT_BY_CREATED) {
Collections.sort(items, Geofavorite.ByLastCreated); Collections.sort(geofavoriteListFiltered, Geofavorite.ByLastCreated);
} else if (sortRule == SORT_BY_CATEGORY) {
Collections.sort(items, Geofavorite.ByCategory);
} else if (sortRule == SORT_BY_DISTANCE) {
Collections.sort(items, Geofavorite.ByDistance);
} }
} }
@ -221,9 +229,9 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
} }
Geofavorite gf = get(overflowMenuSelectedPosition); Geofavorite gf = get(overflowMenuSelectedPosition);
overflowMenuSelectedPosition = -1; overflowMenuSelectedPosition = -1;
if (item.getItemId() == R.id.list_context_menu_share && itemClickListener != null) if (item.getItemId() == R.id.list_context_menu_detail && itemClickListener != null)
itemClickListener.onItemShareClick(gf); itemClickListener.onItemDetailsClick(gf);
if (item.getItemId() == R.id.list_context_menu_delete && itemClickListener != null) if (item.getItemId() == R.id.list_context_menu_delete)
itemClickListener.onItemDeleteClick(gf); itemClickListener.onItemDeleteClick(gf);
return true; return true;
} }
@ -231,7 +239,7 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
public interface ItemClickListener { public interface ItemClickListener {
void onItemClick(Geofavorite item); void onItemClick(Geofavorite item);
void onItemShareClick(Geofavorite item); void onItemShareClick(Geofavorite item);
void onItemNavClick(Geofavorite item); void onItemDetailsClick(Geofavorite item);
void onItemDeleteClick(Geofavorite item); void onItemDeleteClick(Geofavorite item);
} }

View File

@ -1,46 +0,0 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import java.util.HashSet;
import java.util.List;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
public class GeofavoritesFragmentViewModel extends ViewModel {
private GeofavoriteRepository mRepo;
public void init(Context applicationContext) {
mRepo = GeofavoriteRepository.getInstance(applicationContext);
}
public LiveData<List<Geofavorite>> getGeofavorites(){
mRepo.updateGeofavorites();
return mRepo.getGeofavorites();
}
public void updateGeofavorites() {
mRepo.updateGeofavorites();
}
public LiveData<HashSet<String>> getCategories(){
return mRepo.getCategories();
}
public void deleteGeofavorite(Geofavorite geofav) {
mRepo.deleteGeofavorite(geofav);
}
public LiveData<Boolean> getIsUpdating(){
return mRepo.isUpdating();
}
public LiveData<Boolean> getOnFinished(){
return mRepo.onFinished();
}
}

View File

@ -17,170 +17,137 @@
package it.danieleverducci.nextcloudmaps.activity.main; package it.danieleverducci.nextcloudmaps.activity.main;
import android.Manifest; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets; import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.nextcloud.android.sso.helper.SingleAccountHelper; import com.nextcloud.android.sso.helper.SingleAccountHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity; import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity; import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity; import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity;
import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem; import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem;
import it.danieleverducci.nextcloudmaps.activity.mappicker.MapPickerActivity; import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener;
import it.danieleverducci.nextcloudmaps.api.ApiProvider; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.fragments.GeofavoriteListFragment;
import it.danieleverducci.nextcloudmaps.fragments.GeofavoriteMapFragment;
import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
import it.danieleverducci.nextcloudmaps.utils.EdgeToEdgeUtils;
import it.danieleverducci.nextcloudmaps.utils.SettingsManager;
public class MainActivity extends NextcloudMapsStyledActivity { import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.*;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
public class MainActivity extends AppCompatActivity implements MainView, GeofavoriteActivity {
private static final int INTENT_ADD = 100;
private static final int INTENT_EDIT = 200;
private static final String TAG = "MainActivity"; private static final String TAG = "MainActivity";
private static final int PERMISSION_REQUEST_CODE = 3890;
private static final String NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS = "add_from_gps"; private static final String NAVIGATION_KEY_ADD_GEOFAVORITE = "add";
private static final String NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP = "add_from_map";
private static final String NAVIGATION_KEY_SHOW_ABOUT = "about"; private static final String NAVIGATION_KEY_SHOW_ABOUT = "about";
private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account"; private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account";
private ArrayList<OnGpsPermissionGrantedListener> onGpsPermissionGrantedListener = new ArrayList<>();
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private Toolbar toolbar;
private MaterialCardView homeToolbar;
private SearchView searchView;
private FloatingActionButton fab;
private boolean isFabOpen = false; private MainPresenter presenter;
NavigationAdapter navigationCommonAdapter; NavigationAdapter navigationCommonAdapter;
public void openDrawer() {
drawerLayout.openDrawer(GravityCompat.START);
}
public void showMap() {
replaceFragment(new GeofavoriteMapFragment());
SettingsManager.setGeofavoriteListShownAsMap(this, true);
}
public void showList() {
replaceFragment(new GeofavoriteListFragment());
SettingsManager.setGeofavoriteListShownAsMap(this, false);
}
public void addOnGpsPermissionGrantedListener(OnGpsPermissionGrantedListener l) {
onGpsPermissionGrantedListener.add(l);
}
public void removeOnGpsPermissionGrantedListener(OnGpsPermissionGrantedListener l) {
onGpsPermissionGrantedListener.remove(l);
}
public void requestGpsPermissions() {
ActivityCompat.requestPermissions(
this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
PERMISSION_REQUEST_CODE
);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE && permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
for (OnGpsPermissionGrantedListener l : onGpsPermissionGrantedListener) {
l.onGpsPermissionGranted();
}
}
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
boolean showMap = SettingsManager.isGeofavoriteListShownAsMap(this); presenter = new MainPresenter(this);
if (showMap)
showMap();
else
showList();
setupFAB(); fab = findViewById(R.id.add);
fab.setOnClickListener(view -> addGeofavorite());
toolbar = findViewById(R.id.toolbar);
homeToolbar = findViewById(R.id.home_toolbar);
searchView = findViewById(R.id.search_view);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String query) {
geofavoriteAdapter.getFilter().filter(query);
return false;
}
});
searchView.setOnCloseListener(() -> {
if (toolbar.getVisibility() == VISIBLE && TextUtils.isEmpty(searchView.getQuery())) {
updateToolbars(true);
return true;
}
return false;
});
setSupportActionBar(toolbar);
setupNavigationMenu(); setupNavigationMenu();
homeToolbar.setOnClickListener(view -> updateToolbars(false));
AppCompatImageView sortButton = findViewById(R.id.sort_mode);
sortButton.setOnClickListener(view -> openSortingOrderDialogFragment(getSupportFragmentManager(), geofavoriteAdapter.getSortRule()));
drawerLayout = findViewById(R.id.drawerLayout); drawerLayout = findViewById(R.id.drawerLayout);
AppCompatImageButton menuButton = findViewById(R.id.menu_button);
menuButton.setOnClickListener(view -> drawerLayout.openDrawer(GravityCompat.START));
updateSortingIcon(sortRule);
} }
@Override @Override
protected void onPause() { protected void onStart() {
openFab(false); super.onStart();
super.onPause();
}
private void replaceFragment(Fragment fragment) { // Update list
FragmentManager manager = getSupportFragmentManager(); presenter.getGeofavorites();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_container, fragment);
transaction.commit();
}
private void setupFAB() {
// Register listeners
FloatingActionButton fab = findViewById(R.id.open_fab);
fab.setOnClickListener(view -> openFab(!this.isFabOpen));
fab = findViewById(R.id.add_from_gps);
fab.setOnClickListener(view -> addGeofavoriteFromGps());
fab = findViewById(R.id.add_from_map);
fab.setOnClickListener(view -> addGeofavoriteFromMap());
// Clear system bars (in edge to edge mode)
EdgeToEdgeUtils.clearBottomBarWithMargin(findViewById(R.id.fab_container));
} }
private void setupNavigationMenu() { private void setupNavigationMenu() {
//EdgeToEdgeUtils.clearTopBarWithPadding(findViewById(R.id.header_view));
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.drawerLayout), (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
View hv = findViewById(R.id.header_view);
hv.setPadding(insets.left, insets.top, insets.right, hv.getPaddingBottom());
//return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
ArrayList<NavigationItem> navItems = new ArrayList<>(); ArrayList<NavigationItem> navItems = new ArrayList<>();
navigationCommonAdapter = new NavigationAdapter(this, item -> { navigationCommonAdapter = new NavigationAdapter(this, item -> {
switch (item.id) { switch (item.id) {
case NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS: case NAVIGATION_KEY_ADD_GEOFAVORITE:
addGeofavoriteFromGps(); addGeofavorite();
break;
case NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP:
addGeofavoriteFromMap();
break; break;
case NAVIGATION_KEY_SHOW_ABOUT: case NAVIGATION_KEY_SHOW_ABOUT:
show_about(); show_about();
@ -191,8 +158,7 @@ public class MainActivity extends NextcloudMapsStyledActivity {
} }
}); });
navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS, getString(R.string.new_geobookmark_gps), R.drawable.ic_add_gps)); navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE, getString(R.string.new_geobookmark), R.drawable.ic_add));
navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP, getString(R.string.new_geobookmark_map), R.drawable.ic_add_map));
navItems.add(new NavigationItem(NAVIGATION_KEY_SHOW_ABOUT, getString(R.string.about), R.drawable.ic_info_grey)); navItems.add(new NavigationItem(NAVIGATION_KEY_SHOW_ABOUT, getString(R.string.about), R.drawable.ic_info_grey));
navItems.add(new NavigationItem(NAVIGATION_KEY_SWITCH_ACCOUNT, getString(R.string.switch_account), R.drawable.ic_logout_grey)); navItems.add(new NavigationItem(NAVIGATION_KEY_SWITCH_ACCOUNT, getString(R.string.switch_account), R.drawable.ic_logout_grey));
navigationCommonAdapter.setItems(navItems); navigationCommonAdapter.setItems(navItems);
@ -201,52 +167,114 @@ public class MainActivity extends NextcloudMapsStyledActivity {
navigationMenuCommon.setAdapter(navigationCommonAdapter); navigationMenuCommon.setAdapter(navigationCommonAdapter);
} }
private void addGeofavoriteFromGps() { private void updateToolbars(boolean disableSearch) {
startActivity( homeToolbar.setVisibility(disableSearch ? VISIBLE : GONE);
new Intent(this, GeofavoriteDetailActivity.class) toolbar.setVisibility(disableSearch ? GONE : VISIBLE);
); if (disableSearch) {
searchView.setQuery(null, true);
}
searchView.setIconified(disableSearch);
} }
private void addGeofavoriteFromMap() { private void openSortingOrderDialogFragment(FragmentManager supportFragmentManager, int sortOrder) {
startActivity( FragmentTransaction fragmentTransaction = supportFragmentManager.beginTransaction();
new Intent(this, MapPickerActivity.class) fragmentTransaction.addToBackStack(null);
);
SortingOrderDialogFragment.newInstance(sortOrder).show(fragmentTransaction, SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == INTENT_ADD && resultCode == RESULT_OK) {
presenter.getGeofavorites();
} else if (requestCode == INTENT_EDIT && resultCode == RESULT_OK) {
presenter.getGeofavorites();
}
}
private void addGeofavorite() {
startActivityForResult(
new Intent(this, GeofavoriteDetailActivity.class), INTENT_ADD);
} }
private void show_about() { private void show_about() {
startActivity(new Intent(this, AboutActivity.class)); startActivity(new Intent(this, AboutActivity.class));
} }
public void switch_account() { private void switch_account() {
ApiProvider.logout(); SingleAccountHelper.setCurrentAccount(this, null);
GeofavoriteRepository.resetInstance();
SingleAccountHelper.applyCurrentAccount(this, null);
Intent intent = new Intent(MainActivity.this, LoginActivity.class); Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent); startActivity(intent);
finish();
} }
private void openFab(boolean open) { @Override
View fab = findViewById(R.id.open_fab); public void showLoading() {
View addFromGpsFab = findViewById(R.id.add_from_gps); //todo notify GeofavoriteListConsumer.geoFavoriteLoading(true)
View addFromMapFab = findViewById(R.id.add_from_map); }
if (open) { @Override
this.isFabOpen = true; public void hideLoading() {
fab.animate().rotation(45.0f); //todo notify GeofavoriteListConsumer.geoFavoriteLoading(false)
addFromGpsFab.animate().translationY(-getResources().getDimension(R.dimen.fab_vertical_offset)); }
addFromMapFab.animate().translationY(-getResources().getDimension(R.dimen.fab_vertical_offset) * 2);
} else { @Override
this.isFabOpen = false; public void onGetResult(List<Geofavorite> geofavorite_list) {
fab.animate().rotation(0f); //todo notify GeofavoriteListConsumer
addFromGpsFab.animate().translationY(0); }
addFromMapFab.animate().translationY(0);
} @Override
public void onGeofavoriteDeleted(int id) {
// Notify fragment
runOnUiThread(() -> {
//todo notify GeofavoriteListConsumer
Toast.makeText(MainActivity.this, R.string.list_geofavorite_deleted, Toast.LENGTH_LONG).show();
});
}
@Override
public void onErrorLoading(String message) {
Toast.makeText(MainActivity.this, R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show();
Log.e(TAG, "Unable to obtain geofavorites list: " + message);
}
@Override
public void showGeofavoriteDeteleDialog(Geofavorite item) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName()))
.setTitle(R.string.dialog_delete_title)
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
presenter.deleteGeofavorite(item.getId());
dialog.dismiss();
// Callback is onGeofavoriteDeleted
}
})
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog ad = builder.create();
ad.show();
}
@Override
public void updateGeofavorites() {
presenter.getGeofavorites();
}
@Override
public void getGeofavorites() {
} }
public interface OnGpsPermissionGrantedListener { @Override
public void onGpsPermissionGranted(); public void showGeofavoriteDetailActivity(Geofavorite item) {
Intent i = new Intent(this, GeofavoriteDetailActivity.class);
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item);
startActivity(i);
} }
} }

View File

@ -0,0 +1,89 @@
/*
* Nextcloud Maps Geofavorites for Android
*
* @copyright Copyright (c) 2020 John Doe <john@doe.com>
* @author John Doe <john@doe.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.danieleverducci.nextcloudmaps.activity.main;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainPresenter {
private MainView view;
public MainPresenter(MainView view) {
this.view = view;
}
public void getGeofavorites() {
view.showLoading();
Call<List<Geofavorite>> call = ApiProvider.getAPI().getGeofavorites();
call.enqueue(new Callback<List<Geofavorite>>() {
@Override
public void onResponse(@NonNull Call<List<Geofavorite>> call, @NonNull Response<List<Geofavorite>> response) {
((AppCompatActivity) view).runOnUiThread(() -> {
view.hideLoading();
if (response.isSuccessful() && response.body() != null) {
view.onGetResult(response.body());
} else {
((AppCompatActivity) view).runOnUiThread(() -> {
view.hideLoading();
view.onErrorLoading(response.raw().message());
});
}
});
}
@Override
public void onFailure(@NonNull Call<List<Geofavorite>> call, @NonNull Throwable t) {
((AppCompatActivity) view).runOnUiThread(() -> {
view.hideLoading();
view.onErrorLoading(t.getLocalizedMessage());
});
}
});
}
public void deleteGeofavorite(int id) {
view.showLoading();
Call<Geofavorite> call = ApiProvider.getAPI().deleteGeofavorite(id);
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
view.hideLoading();
view.onGeofavoriteDeleted(id);
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
view.hideLoading();
view.onErrorLoading(t.getLocalizedMessage());
}
});
}
}

View File

@ -0,0 +1,33 @@
/*
* Nextcloud Maps Geofavorites for Android
*
* @copyright Copyright (c) 2020 John Doe <john@doe.com>
* @author John Doe <john@doe.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.danieleverducci.nextcloudmaps.activity.main;
import java.util.List;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public interface MainView {
void showLoading();
void hideLoading();
void onGetResult(List<Geofavorite> geofavorites);
void onGeofavoriteDeleted(int id);
void onErrorLoading(String message);
}

View File

@ -80,12 +80,15 @@ public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.Vi
} }
private void bind(@NonNull NavigationItem item) { private void bind(@NonNull NavigationItem item) {
int color = view.getResources().getColor(R.color.accent);
currentItem = item; currentItem = item;
name.setText(item.label); name.setText(item.label);
name.setTextColor(color);
icon.setImageDrawable(DrawableCompat.wrap(icon.getResources().getDrawable(item.icon))); icon.setImageDrawable(DrawableCompat.wrap(icon.getResources().getDrawable(item.icon)));
icon.setColorFilter(color);
icon.setVisibility(View.VISIBLE); icon.setVisibility(View.VISIBLE);
} }

View File

@ -32,7 +32,6 @@ import android.widget.ImageButton;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
@ -48,7 +47,6 @@ public class SortingOrderDialogFragment extends DialogFragment {
public static final String SORTING_ORDER_FRAGMENT = "SORTING_ORDER_FRAGMENT"; public static final String SORTING_ORDER_FRAGMENT = "SORTING_ORDER_FRAGMENT";
private static final String KEY_SORT_ORDER = "SORT_ORDER"; private static final String KEY_SORT_ORDER = "SORT_ORDER";
private OnSortingOrderListener onSortingOrderListener;
private View mView; private View mView;
private View[] mTaggedViews; private View[] mTaggedViews;
private Button mCancel; private Button mCancel;
@ -91,20 +89,15 @@ public class SortingOrderDialogFragment extends DialogFragment {
return mView; return mView;
} }
public void setOnSortingOrderListener(OnSortingOrderListener listener) {
this.onSortingOrderListener = listener;
}
/** /**
* find all relevant UI elements and set their values. * find all relevant UI elements and set their values.
* TODO: this is REALLY ugly.
* *
* @param view the parent view * @param view the parent view
*/ */
private void setupDialogElements(View view) { private void setupDialogElements(View view) {
mCancel = view.findViewById(R.id.cancel); mCancel = view.findViewById(R.id.cancel);
mTaggedViews = new View[8]; mTaggedViews = new View[4];
mTaggedViews[0] = view.findViewById(R.id.sortByTitleAscending); mTaggedViews[0] = view.findViewById(R.id.sortByTitleAscending);
mTaggedViews[0].setTag(GeofavoriteAdapter.SORT_BY_TITLE); mTaggedViews[0].setTag(GeofavoriteAdapter.SORT_BY_TITLE);
mTaggedViews[1] = view.findViewById(R.id.sortByTitleAscendingText); mTaggedViews[1] = view.findViewById(R.id.sortByTitleAscendingText);
@ -113,14 +106,6 @@ public class SortingOrderDialogFragment extends DialogFragment {
mTaggedViews[2].setTag(GeofavoriteAdapter.SORT_BY_CREATED); mTaggedViews[2].setTag(GeofavoriteAdapter.SORT_BY_CREATED);
mTaggedViews[3] = view.findViewById(R.id.sortByCreationDateDescendingText); mTaggedViews[3] = view.findViewById(R.id.sortByCreationDateDescendingText);
mTaggedViews[3].setTag(GeofavoriteAdapter.SORT_BY_CREATED); mTaggedViews[3].setTag(GeofavoriteAdapter.SORT_BY_CREATED);
mTaggedViews[4] = view.findViewById(R.id.sortByCategoryAscending);
mTaggedViews[4].setTag(GeofavoriteAdapter.SORT_BY_CATEGORY);
mTaggedViews[5] = view.findViewById(R.id.sortByCategoryAscendingText);
mTaggedViews[5].setTag(GeofavoriteAdapter.SORT_BY_CATEGORY);
mTaggedViews[6] = view.findViewById(R.id.sortByDistanceAscending);
mTaggedViews[6].setTag(GeofavoriteAdapter.SORT_BY_DISTANCE);
mTaggedViews[7] = view.findViewById(R.id.sortByDistanceAscendingText);
mTaggedViews[7].setTag(GeofavoriteAdapter.SORT_BY_DISTANCE);
setupActiveOrderSelection(); setupActiveOrderSelection();
} }
@ -136,10 +121,10 @@ public class SortingOrderDialogFragment extends DialogFragment {
if (view instanceof ImageButton) { if (view instanceof ImageButton) {
Drawable normalDrawable = ((ImageButton) view).getDrawable(); Drawable normalDrawable = ((ImageButton) view).getDrawable();
Drawable wrapDrawable = DrawableCompat.wrap(normalDrawable); Drawable wrapDrawable = DrawableCompat.wrap(normalDrawable);
DrawableCompat.setTint(wrapDrawable, ContextCompat.getColor(getContext(), R.color.selector_item_selected)); DrawableCompat.setTint(wrapDrawable, this.getResources().getColor(R.color.defaultTint));
} }
if (view instanceof TextView) { if (view instanceof TextView) {
((TextView)view).setTextColor(ContextCompat.getColor(getContext(), R.color.selector_item_selected)); ((TextView)view).setTextColor(this.getResources().getColor(R.color.defaultTint));
((TextView)view).setTypeface(Typeface.DEFAULT_BOLD); ((TextView)view).setTypeface(Typeface.DEFAULT_BOLD);
} }
} }
@ -176,8 +161,8 @@ public class SortingOrderDialogFragment extends DialogFragment {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
dismissAllowingStateLoss(); dismissAllowingStateLoss();
if (onSortingOrderListener != null) ((SortingOrderDialogFragment.OnSortingOrderListener) getActivity())
onSortingOrderListener.onSortingOrderChosen((int) v.getTag()); .onSortingOrderChosen((int) v.getTag());
} }
} }

View File

@ -1,223 +0,0 @@
package it.danieleverducci.nextcloudmaps.activity.mappicker;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapController;
import org.osmdroid.events.MapListener;
import org.osmdroid.events.ScrollEvent;
import org.osmdroid.events.ZoomEvent;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.MapView;
import java.util.Locale;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.databinding.ActivityMapPickerBinding;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
import it.danieleverducci.nextcloudmaps.utils.MapUtils;
public class MapPickerActivity extends NextcloudMapsStyledActivity {
public static final String TAG = "MapPickerActivity";
private static final int PERMISSION_REQUEST_CODE = 8888;
private ViewHolder mViewHolder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MapUtils.configOsmdroid(this);
mViewHolder = new MapPickerActivity.ViewHolder(getLayoutInflater());
mViewHolder.setViewEventListener(new ViewEventListener() {
@Override
public void onExitButtonPressed() {
finish();
}
@Override
public void onConfirmButtonPressed() {
double[] coords = mViewHolder.getCurrentCoordinates();
Uri geoUri = GeoUriParser.createGeoUri(coords[0], coords[1], null);
Intent i = new Intent(MapPickerActivity.this, GeofavoriteDetailActivity.class);
i.setData(geoUri);
startActivity(i);
finish();
}
});
Location l = getLastKnownPosition();
if (l != null)
mViewHolder.centerMapOn(l.getLatitude(), l.getLongitude());
setContentView(mViewHolder.getRootView());
}
/**
* May return last known GPS position or null. Used to center the map.
* @return last known GPS position or null
*/
private Location getLastKnownPosition() {
// Check if user granted location permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// User didn't grant permission. Ask it.
requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_CODE);
return null;
}
LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
// Try to use last available location
return locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
}
private class ViewHolder implements View.OnClickListener {
private final ActivityMapPickerBinding binding;
private ViewEventListener listener;
private final MapView map;
private boolean coordsEditMode = false;
public ViewHolder(LayoutInflater inflater) {
this.binding = ActivityMapPickerBinding.inflate(inflater);
// Show confirm button on lat/lng edit
View.OnFocusChangeListener latlonFocusListener = new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean focused) {
if (!focused)
return;
(ViewHolder.this).coordsEditMode(true);
}
};
this.binding.latEt.setOnFocusChangeListener(latlonFocusListener);
this.binding.lonEt.setOnFocusChangeListener(latlonFocusListener);
// Setup map
this.map = this.binding.map;
this.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
this.map.setMultiTouchControls(true);
MapUtils.setTheme(this.map);
this.map.addMapListener(new MapListener() {
@Override
public boolean onScroll(ScrollEvent event) {
// Disable edit mode
if ((ViewHolder.this).coordsEditMode)
(ViewHolder.this).coordsEditMode(false);
// Write coords on edittext
IGeoPoint igp = (ViewHolder.this).map.getMapCenter();
(ViewHolder.this).binding.latEt.setText(String.format(Locale.ENGLISH, "%.06f", igp.getLatitude()));
(ViewHolder.this).binding.lonEt.setText(String.format(Locale.ENGLISH, "%.06f", igp.getLongitude()));
return false;
}
@Override
public boolean onZoom(ZoomEvent event) {
return false;
}
});
IMapController mapController = binding.map.getController();
mapController.setZoom(7.0f);
// Setup onClick
this.binding.latlonConfirmBtn.setOnClickListener(this);
this.binding.backBt.setOnClickListener(this);
this.binding.okBt.setOnClickListener(this);
}
public void setViewEventListener(ViewEventListener listener) {
this.listener = listener;
}
public void centerMapOn(Double lat, Double lon ) {
IMapController mapController = binding.map.getController();
mapController.setCenter(new GeoPoint(lat, lon));
}
public View getRootView() {
return this.binding.root;
}
@Override
public void onClick(View view) {
if (view == this.binding.latlonConfirmBtn) {
this.coordsEditMode(false);
Double lat;
Double lon;
try {
lat = Double.parseDouble(this.binding.latEt.getText().toString());
lon = Double.parseDouble(this.binding.lonEt.getText().toString());
} catch (NumberFormatException e) {
Log.e(TAG, "Unable to parse coordinates: " + e.getLocalizedMessage());
Toast.makeText(MapPickerActivity.this, R.string.coordinates_parse_error, Toast.LENGTH_SHORT).show();
return;
}
// Validate coordinates
if (lon <= -180 || lon >= 180 || lat <= -90 || lat >= 90) {
Toast.makeText(MapPickerActivity.this, R.string.coordinates_invalid_error, Toast.LENGTH_SHORT).show();
return;
}
// Move map to coordinates
this.centerMapOn(lat, lon);
}
if (view == this.binding.backBt)
listener.onExitButtonPressed();
if (view == this.binding.okBt)
listener.onConfirmButtonPressed();
}
public double[] getCurrentCoordinates() {
IGeoPoint igp = (ViewHolder.this).map.getMapCenter();
return new double[]{igp.getLatitude(), igp.getLongitude()};
}
/**
* Enters/exits coordinates edit mode.
* On exit, removes focus from the coordinates ET and hides the button
*/
private void coordsEditMode(boolean active) {
this.coordsEditMode = active;
View btn = this.binding.latlonConfirmBtn;
if (active) {
btn.setVisibility(View.VISIBLE);
} else {
this.binding.latEt.clearFocus();
this.binding.lonEt.clearFocus();
btn.setVisibility(View.GONE);
// Hide soft keyboard
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(btn.getWindowToken(), 0);
}
}
}
protected interface ViewEventListener {
public void onExitButtonPressed();
public void onConfirmButtonPressed();
}
}

View File

@ -24,7 +24,6 @@ import android.content.Context;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.nextcloud.android.sso.api.NextcloudAPI; import com.nextcloud.android.sso.api.NextcloudAPI;
@ -36,27 +35,47 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import retrofit2.NextcloudRetrofitApiBuilder; import retrofit2.NextcloudRetrofitApiBuilder;
public class ApiProvider { public class ApiProvider {
private static final String TAG = ApiProvider.class.getCanonicalName(); private final String TAG = ApiProvider.class.getCanonicalName();
@NonNull
protected Context context;
protected static API mApi; protected static API mApi;
@Nullable protected static String ssoAccountName;
public static API getAPI(Context context) {
if (mApi == null) {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create());
mApi = new NextcloudRetrofitApiBuilder(nextcloudAPI, API.mApiEndpoint).create(API.class); public ApiProvider(Context context) {
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { this.context = context;
Log.d(TAG, "setAccout() called with: ex = [" + e + "]"); initSsoApi(new NextcloudAPI.ApiConnectedListener() {
@Override
public void onConnected() {
Log.d(TAG, "Connected to Nextcloud instance");
} }
}
@Override
public void onError(Exception ex) {
Log.d(TAG, "Unable to connect to Nextcloud instance: " + ex.toString());
}
});
}
public void initSsoApi(final NextcloudAPI.ApiConnectedListener callback) {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), callback);
ssoAccountName = ssoAccount.name;
mApi = new NextcloudRetrofitApiBuilder(nextcloudAPI, API.mApiEndpoint).create(API.class);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
Log.d(TAG, "setAccout() called with: ex = [" + e + "]");
}
}
public static API getAPI() {
return mApi; return mApi;
} }
public static void logout() { public static String getAccountName() {
mApi = null; return ssoAccountName;
} }
} }

View File

@ -0,0 +1,12 @@
package it.danieleverducci.nextcloudmaps.fragments;
import java.util.List;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public interface GeofavoriteListConsumer {
void onGeofavoritesUpdated(List<Geofavorite> geofavorites);
void onGeofavoriteRemoved(Geofavorite geofavorite);
void onGeofavoriteAdded(Geofavorite geofavorite);
void onGeofavoriteLoading(boolean isLoading);
}

View File

@ -1,154 +1,145 @@
package it.danieleverducci.nextcloudmaps.fragments; package it.danieleverducci.nextcloudmaps.fragments;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CATEGORY;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED; import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_DISTANCE;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE; import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Observer;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteActivity;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter; import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity; import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment; import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment;
import it.danieleverducci.nextcloudmaps.databinding.FragmentGeofavoriteListBinding;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.EdgeToEdgeUtils;
import it.danieleverducci.nextcloudmaps.utils.GeofavoritesFilter;
import it.danieleverducci.nextcloudmaps.utils.SettingsManager;
public class GeofavoriteListFragment extends GeofavoritesFragment implements SortingOrderDialogFragment.OnSortingOrderListener { /**
* Geofavorites list
private SwipeRefreshLayout swipeRefresh; */
public class GeofavoriteListFragment extends Fragment implements GeofavoriteListConsumer, SortingOrderDialogFragment.OnSortingOrderListener {
private FragmentGeofavoriteListBinding binding;
private GeofavoriteAdapter geofavoriteAdapter; private GeofavoriteAdapter geofavoriteAdapter;
private SharedPreferences preferences;
public GeofavoriteListFragment() {
// Required empty public constructor
}
@Nullable
@Override @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container,
View v = inflater.inflate(R.layout.fragment_geofavorite_list, container, false); Bundle savedInstanceState) {
if (binding == null) {
binding = FragmentGeofavoriteListBinding.inflate(
LayoutInflater.from(container.getContext()),
container,
false
);
}
// Setup list preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
int sortRule = SettingsManager.getGeofavoriteListSortBy(requireContext());
RecyclerView recyclerView = v.findViewById(R.id.recycler_view); StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
LinearLayoutManager layoutManager = new LinearLayoutManager(requireContext()); binding.recyclerView.setLayoutManager(layoutManager);
recyclerView.setLayoutManager(layoutManager);
int sortRule = preferences.getInt(getString(R.string.setting_sort_by), SORT_BY_CREATED);
GeofavoriteAdapter.ItemClickListener rvItemClickListener = new GeofavoriteAdapter.ItemClickListener() { GeofavoriteAdapter.ItemClickListener rvItemClickListener = new GeofavoriteAdapter.ItemClickListener() {
@Override @Override
public void onItemClick(Geofavorite item) { public void onItemClick(Geofavorite geofavorite) {
openGeofavorite(item); Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(geofavorite.getGeoUri());
startActivity(i);
} }
@Override @Override
public void onItemShareClick(Geofavorite item) { public void onItemShareClick(Geofavorite item) {
shareGeofavorite(item); Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
String shareMessage = getString(R.string.share_message)
.replace("{lat}", ""+item.getLat())
.replace("{lng}", ""+item.getLng());
i.putExtra(Intent.EXTRA_TEXT, shareMessage );
startActivity(Intent.createChooser(i, getString(R.string.share_via)));
} }
@Override @Override
public void onItemNavClick(Geofavorite item) { public void onItemDetailsClick(Geofavorite item) {
navigateToGeofavorite(item); if (getActivity() instanceof GeofavoriteActivity)
((GeofavoriteActivity)getActivity()).showGeofavoriteDetailActivity(item);
else
throw new IllegalStateException("Expected activity implementing GeofavoriteAcivity");
} }
@Override @Override
public void onItemDeleteClick(Geofavorite item) { public void onItemDeleteClick(Geofavorite item) {
deleteGeofavorite(item); if (getActivity() instanceof GeofavoriteActivity)
((GeofavoriteActivity)getActivity()).showGeofavoriteDeteleDialog(item);
else
throw new IllegalStateException("Expected activity implementing GeofavoriteAcivity");
} }
}; };
geofavoriteAdapter = new GeofavoriteAdapter(getContext(), rvItemClickListener);
geofavoriteAdapter = new GeofavoriteAdapter(requireContext(), rvItemClickListener); binding.recyclerView.setAdapter(geofavoriteAdapter);
recyclerView.setAdapter(geofavoriteAdapter);
geofavoriteAdapter.setSortRule(sortRule); geofavoriteAdapter.setSortRule(sortRule);
// Register for data source events binding.swipeRefresh.setOnRefreshListener(() ->
mGeofavoritesFragmentViewModel.getIsUpdating().observe(getViewLifecycleOwner(), new Observer<Boolean>() { ((GeofavoriteActivity)getActivity()).updateGeofavorites());
@Override
public void onChanged(@Nullable Boolean changed) {
if(Boolean.TRUE.equals(changed)){
swipeRefresh.setRefreshing(true);
}
else{
swipeRefresh.setRefreshing(false);
}
}
});
// Setup view listeners return binding.root;
swipeRefresh = v.findViewById(R.id.swipe_refresh); }
swipeRefresh.setOnRefreshListener(() ->
mGeofavoritesFragmentViewModel.updateGeofavorites());
AppCompatImageView sortButton = v.findViewById(R.id.sort_mode);
sortButton.setOnClickListener(view -> openSortingOrderDialogFragment(geofavoriteAdapter.getSortRule()));
View showMapButton = v.findViewById(R.id.view_mode_map); @Override
showMapButton.setOnClickListener(View -> ((MainActivity)requireActivity()).showMap()); public void onGeofavoritesUpdated(List<Geofavorite> geofavorites) {
if (geofavoriteAdapter != null)
// Clear top bar in edge to edge and notify the adapter about the system bar size to avoid the system bar covering the last row geofavoriteAdapter.setGeofavoriteList(geofavorites);
ViewCompat.setOnApplyWindowInsetsListener(v.findViewById(R.id.activity_list_view), (rv, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
rv.setPadding(rv.getPaddingLeft(), insets.top, rv.getPaddingRight(), rv.getPaddingBottom());
geofavoriteAdapter.setBottomInset(insets.bottom);
geofavoriteAdapter.setLeftInset(insets.left);
geofavoriteAdapter.setRightInset(insets.right);
//return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
return v;
} }
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onGeofavoriteRemoved(Geofavorite geofavorite) {
super.onViewCreated(view, savedInstanceState); // Update list
geofavoriteAdapter.removeById(geofavorite.getId());
// Set icons
int sortRule = SettingsManager.getGeofavoriteListSortBy(requireContext());
updateSortingIcon(sortRule);
} }
@Override @Override
public void onDatasetChange(List<Geofavorite> items) { public void onGeofavoriteAdded(Geofavorite geofavorite) {
// Called when the items are loaded or a filtering happens
geofavoriteAdapter.setGeofavoriteList(items);
} }
@Override
public void onGeofavoriteLoading(boolean isLoading) {
binding.swipeRefresh.setRefreshing(isLoading);
}
@Override @Override
public void onSortingOrderChosen(int sortSelection) { public void onSortingOrderChosen(int sortSelection) {
geofavoriteAdapter.setSortRule(sortSelection); geofavoriteAdapter.setSortRule(sortSelection);
updateSortingIcon(sortSelection); updateSortingIcon(sortSelection);
SettingsManager.setGeofavoriteListSortBy(requireContext(), sortSelection); preferences.edit().putInt(getString(R.string.setting_sort_by), sortSelection).apply();
} }
private void openSortingOrderDialogFragment(int sortOrder) { public void updateSortingIcon(int sortSelection) {
FragmentTransaction fragmentTransaction = requireActivity().getSupportFragmentManager().beginTransaction(); // TODO: Gestire il bottone come bottone generico da aggiungere all'activity
fragmentTransaction.addToBackStack(null); AppCompatImageView sortButton = findViewById(R.id.sort_mode);
SortingOrderDialogFragment sodf = SortingOrderDialogFragment.newInstance(sortOrder);
sodf.setOnSortingOrderListener(this);
sodf.show(fragmentTransaction, SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT);
}
private void updateSortingIcon(int sortSelection) {
AppCompatImageView sortButton = requireView().findViewById(R.id.sort_mode);
switch (sortSelection) { switch (sortSelection) {
case SORT_BY_TITLE: case SORT_BY_TITLE:
sortButton.setImageResource(R.drawable.ic_alphabetical_asc); sortButton.setImageResource(R.drawable.ic_alphabetical_asc);
@ -156,13 +147,6 @@ public class GeofavoriteListFragment extends GeofavoritesFragment implements Sor
case SORT_BY_CREATED: case SORT_BY_CREATED:
sortButton.setImageResource(R.drawable.ic_modification_asc); sortButton.setImageResource(R.drawable.ic_modification_asc);
break; break;
case SORT_BY_CATEGORY:
sortButton.setImageResource(R.drawable.ic_category_asc);
break;
case SORT_BY_DISTANCE:
sortButton.setImageResource(R.drawable.ic_distance_asc);
break;
} }
} }
}
}

View File

@ -1,241 +0,0 @@
package it.danieleverducci.nextcloudmaps.fragments;
import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.ActivityCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.lifecycle.Observer;
import org.osmdroid.api.IMapController;
import org.osmdroid.events.MapEventsReceiver;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.MapEventsOverlay;
import org.osmdroid.views.overlay.Marker;
import org.osmdroid.views.overlay.Overlay;
import org.osmdroid.views.overlay.infowindow.InfoWindow;
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider;
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay;
import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.EdgeToEdgeUtils;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
import it.danieleverducci.nextcloudmaps.utils.MapUtils;
import it.danieleverducci.nextcloudmaps.utils.SettingsManager;
import it.danieleverducci.nextcloudmaps.views.GeofavMarkerInfoWindow;
public class GeofavoriteMapFragment extends GeofavoritesFragment implements MainActivity.OnGpsPermissionGrantedListener {
private MapView map;
private MyLocationNewOverlay mLocationOverlay;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MapUtils.configOsmdroid(requireContext());
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_geofavorite_map, container, false);
// Register listeners
View centerPositionFab = v.findViewById(R.id.center_position);
centerPositionFab.setOnClickListener((cpv) -> moveToUserPosition());
// Clear bottom bar in edge to edge
EdgeToEdgeUtils.clearBottomBarWithMargin(v.findViewById(R.id.center_position_container));
// Setup map
map = v.findViewById(R.id.map);
map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
map.setMultiTouchControls(true);
MapUtils.setTheme(map);
MapEventsOverlay meo = new MapEventsOverlay(new MapEventsReceiver() {
@Override
public boolean singleTapConfirmedHelper(GeoPoint p) {
InfoWindow.closeAllInfoWindowsOn(map);
return false;
}
@Override
public boolean longPressHelper(GeoPoint p) {
// Create new geofavorite (go to geofav creation activity)
Uri geoUri = GeoUriParser.createGeoUri(p.getLatitude(), p.getLongitude(), null);
Intent i = new Intent(requireActivity(), GeofavoriteDetailActivity.class);
i.setData(geoUri);
startActivity(i);
return true;
}
});
map.getOverlays().add(0, meo);
showUserPosition();
// Setup view listeners
View showListButton = v.findViewById(R.id.view_mode_list);
showListButton.setOnClickListener(View -> ((MainActivity)requireActivity()).showList());
View loadingWall = v.findViewById(R.id.loading_wall);
// Register for data source events
mGeofavoritesFragmentViewModel.getIsUpdating().observe(getViewLifecycleOwner(), new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean changed) {
if(Boolean.TRUE.equals(changed)){
loadingWall.setVisibility(View.VISIBLE);
}
else{
loadingWall.setVisibility(View.GONE);
}
}
});
return v;
}
@Override
public void onStart() {
super.onStart();
((MainActivity)requireActivity()).addOnGpsPermissionGrantedListener(this);
}
@Override
public void onDatasetChange(List<Geofavorite> items) {
clearAllMarkers();
for(Geofavorite gf : items)
addMarker(gf);
map.invalidate();
}
@Override
public void onStop() {
super.onStop();
((MainActivity)requireActivity()).removeOnGpsPermissionGrantedListener(this);
SettingsManager.setLastMapPosition(
requireContext(),
map.getMapCenter().getLatitude(),
map.getMapCenter().getLongitude(),
map.getZoomLevelDouble()
);
}
@Override
public void onGpsPermissionGranted() {
showUserPosition();
}
private void showUserPosition() {
// Center map on last position
double[] pos = SettingsManager.getLastMapPosition(requireContext());
IMapController mapController = map.getController();
mapController.setCenter(new GeoPoint(pos[0], pos[1]));
mapController.setZoom(pos[2]);
// Check if user granted location permission
if (ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(requireContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED
) {
// User didn't grant permission. Ask it.
((MainActivity)requireActivity()).requestGpsPermissions();
return;
}
// Display user position on screen
mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(requireContext()), map);
Bitmap personIcon = ((BitmapDrawable)AppCompatResources.getDrawable(requireContext(), R.mipmap.ic_person)).getBitmap();
mLocationOverlay.setPersonIcon(personIcon);
mLocationOverlay.setDirectionIcon(personIcon);
mLocationOverlay.setPersonAnchor(.5f, .5f);
mLocationOverlay.setDirectionAnchor(.5f, .5f);
// On first gps fix, show "center to my position" icon
mLocationOverlay.runOnFirstFix(() -> {
if(getActivity() != null) {
getActivity().runOnUiThread(() -> {
getView().findViewById(R.id.center_position).setVisibility(View.VISIBLE);
});
}
});
mLocationOverlay.enableMyLocation();
map.getOverlays().add(mLocationOverlay);
}
void moveToUserPosition() {
if (mLocationOverlay != null)
map.getController().animateTo(mLocationOverlay.getMyLocation());
}
private void addMarker(Geofavorite geofavorite){
GeoPoint pos = new GeoPoint(geofavorite.getLat(), geofavorite.getLng());
// Set icon and color
Drawable icon = DrawableCompat.wrap(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_map_pin));
DrawableCompat.setTint(icon, geofavorite.categoryColor() == 0 ? requireContext().getColor(R.color.defaultBrand) : geofavorite.categoryColor());
// Set infowindow (popup opened on marker click) and its listeners
GeofavMarkerInfoWindow iw = new GeofavMarkerInfoWindow(map, geofavorite);
iw.setOnGeofavMarkerInfoWindowClickListener(new GeofavMarkerInfoWindow.OnGeofavMarkerInfoWindowClickListener() {
@Override
public void onGeofavMarkerInfoWindowEditClick() {
openGeofavorite(geofavorite);
}
@Override
public void onGeofavMarkerInfoWindowShareClick() {
shareGeofavorite(geofavorite);
}
@Override
public void onGeofavMarkerInfoWindowNavClick() {
navigateToGeofavorite(geofavorite);
}
@Override
public void onGeofavMarkerInfoWindowDeleteClick() {
deleteGeofavorite(geofavorite);
}
});
// Set marker
Marker m = new Marker(map);
m.setPosition(pos);
m.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
m.setIcon(icon);
m.setTitle(geofavorite.getName());
m.setSnippet(geofavorite.getComment());
m.setSubDescription(geofavorite.getCategory());
m.setInfoWindow(iw);
map.getOverlays().add(m);
}
private void clearAllMarkers() {
// Close any open infowindow before removing related marker
InfoWindow.closeAllInfoWindowsOn(map);
// Remove all markers, leaving the other overlays (user position and tap listener ones)
for(Overlay o : map.getOverlays()) {
if (o instanceof Marker) {
map.getOverlays().remove(o);
}
}
}
}

View File

@ -1,273 +0,0 @@
package it.danieleverducci.nextcloudmaps.fragments;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.appcompat.widget.SearchView;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.squareup.picasso.Picasso;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.detail.CategoriesAdapter;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoritesFragmentViewModel;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.EdgeToEdgeUtils;
import it.danieleverducci.nextcloudmaps.utils.GeofavoritesFilter;
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
/**
* Separates the specific list/map implementation details providing a standard interface
* to communicate with the activity
*/
public abstract class GeofavoritesFragment extends Fragment {
private final String TAG = "GeofavoritesFragment";
protected GeofavoritesFragmentViewModel mGeofavoritesFragmentViewModel;
private View toolbar;
private View homeToolbar;
private SearchView searchView;
private ImageButton filterButton;
private List<Geofavorite> geofavorites = new ArrayList<>();
private HashSet<String> categories = new HashSet<>();
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load data
mGeofavoritesFragmentViewModel = new ViewModelProvider(this).get(GeofavoritesFragmentViewModel.class);
mGeofavoritesFragmentViewModel.init(requireContext());
mGeofavoritesFragmentViewModel.getOnFinished().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean success) {
if(success == null || !success){
Toast.makeText(requireContext(), R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show();
}
}
});
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Set views
AppCompatImageButton menuButton = view.findViewById(R.id.menu_button);
menuButton.setOnClickListener(v -> ((MainActivity)requireActivity()).openDrawer());
View userBadgeContainer = view.findViewById(R.id.user_badge_container);
userBadgeContainer.setOnClickListener(v -> showSwitchAccountDialog());
// Setup toolbar/searchbar
View toolbarContainer = view.findViewById(R.id.toolbar_container);
EdgeToEdgeUtils.clearTopBarWithMargin(toolbarContainer);
toolbar = view.findViewById(R.id.toolbar);
homeToolbar = view.findViewById(R.id.home_toolbar);
filterButton = view.findViewById(R.id.search_filter);
filterButton.setOnClickListener(v -> showCategoryFilterDialog());
searchView = view.findViewById(R.id.search_view);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
return false;
}
@Override
public boolean onQueryTextChange(String query) {
onDatasetChange(
(new GeofavoritesFilter(geofavorites)).byText(query)
);
return false;
}
});
mGeofavoritesFragmentViewModel.getGeofavorites().observe(getViewLifecycleOwner(), new Observer<List<Geofavorite>>() {
@Override
public void onChanged(List<Geofavorite> geofavorites) {
GeofavoritesFragment.this.geofavorites = geofavorites;
onDatasetChange(geofavorites);
}
});
mGeofavoritesFragmentViewModel.getCategories().observe(getViewLifecycleOwner(), new Observer<HashSet<String>>() {
@Override
public void onChanged(HashSet<String> categories) {
GeofavoritesFragment.this.categories = categories;
}
});
searchView.setOnCloseListener(() -> {
if (toolbar.getVisibility() == VISIBLE && TextUtils.isEmpty(searchView.getQuery())) {
updateToolbars(true);
return true;
}
return false;
});
homeToolbar.setOnClickListener(v -> updateToolbars(false));
// Set user badge (async)
Handler h = new Handler();
h.post(() -> {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext());
String userBadgePath = ssoAccount.url + "/index.php/avatar/" + ssoAccount.userId + "/64";
if (getActivity() != null)
getActivity().runOnUiThread(
() -> Picasso.get().load(userBadgePath).into((AppCompatImageView)userBadgeContainer.findViewById(R.id.user_badge))
);
} catch (Exception e) {
Log.e(TAG, "Unable to load user image: " + e.toString());
}
});
}
@Override
public void onStart() {
super.onStart();
// Reset filter and update data
filterButton.setImageResource(R.drawable.ic_filter_off);
mGeofavoritesFragmentViewModel.updateGeofavorites();
}
abstract public void onDatasetChange(List<Geofavorite> items);
protected void openGeofavorite(Geofavorite item) {
showGeofavoriteDetailActivity(item);
}
protected void shareGeofavorite(Geofavorite item) {
startActivity(Intent.createChooser(IntentGenerator.newShareIntent(requireActivity(), item), getString(R.string.share_via)));
}
protected void navigateToGeofavorite(Geofavorite item) {
startActivity(IntentGenerator.newGeoUriIntent(requireActivity(), item));
}
protected void deleteGeofavorite(Geofavorite item) {
showGeofavoriteDeteleDialog(item);
}
private void showGeofavoriteDetailActivity(Geofavorite item) {
Intent i = new Intent(requireContext(), GeofavoriteDetailActivity.class);
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item.getId());
startActivity(i);
}
private void showGeofavoriteDeteleDialog(Geofavorite item) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName() != null ? item.getName() : ""))
.setTitle(R.string.dialog_delete_title)
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mGeofavoritesFragmentViewModel.deleteGeofavorite(item);
dialog.dismiss();
}
})
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog ad = builder.create();
ad.show();
}
private void showSwitchAccountDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setMessage(R.string.dialog_logout_message)
.setTitle(R.string.dialog_logout_title)
.setPositiveButton(android.R.string.yes, (dialog, id) -> {
if (getActivity() != null)
((MainActivity) getActivity()).switch_account();
dialog.dismiss();
})
.setNegativeButton(android.R.string.no, (dialog, id) -> dialog.dismiss());
AlertDialog ad = builder.create();
ad.show();
}
private void showCategoryFilterDialog() {
if (categories.isEmpty()) {
Toast.makeText(requireContext(), R.string.filtering_unavailable, Toast.LENGTH_SHORT).show();
return;
}
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
builder.setTitle(R.string.filtering_dialog_title);
CategoriesAdapter ca = new CategoriesAdapter(requireContext());
ca.setCategoriesList(categories);
builder.setAdapter(ca, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Set filter button enabled icon and color
String categoryName = ca.getItem(which);
filterButton.setImageResource(R.drawable.ic_filter);
Drawable d = filterButton.getDrawable();
DrawableCompat.setTint(
d,
Geofavorite.categoryColorFromName(categoryName) == 0
? requireContext().getColor(R.color.defaultBrand)
: Geofavorite.categoryColorFromName(categoryName)
);
filterByCategory(categoryName);
}
});
builder.setPositiveButton(R.string.filtering_dialog_cancel, (dialog, id) -> {
dialog.dismiss();
filterButton.setImageResource(R.drawable.ic_filter_off);
filterByCategory(null);
});
AlertDialog dialog = builder.create();
dialog.show();
}
private void updateToolbars(boolean disableSearch) {
homeToolbar.setVisibility(disableSearch ? VISIBLE : GONE);
toolbar.setVisibility(disableSearch ? GONE : VISIBLE);
if (disableSearch) {
searchView.setQuery(null, true);
}
searchView.setIconified(disableSearch);
}
private void filterByCategory(String category) {
onDatasetChange(
(new GeofavoritesFilter(geofavorites)).byCategory(category)
);
}
}

View File

@ -0,0 +1,66 @@
package it.danieleverducci.nextcloudmaps.fragments;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import it.danieleverducci.nextcloudmaps.R;
/**
* A simple {@link Fragment} subclass.
* Use the {@link MapFragment#newInstance} factory method to
* create an instance of this fragment.
*/
public class MapFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";
// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;
public MapFragment() {
// Required empty public constructor
}
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment MapFragment.
*/
// TODO: Rename and change types and number of parameters
public static MapFragment newInstance(String param1, String param2) {
MapFragment fragment = new MapFragment();
Bundle args = new Bundle();
args.putString(ARG_PARAM1, param1);
args.putString(ARG_PARAM2, param2);
fragment.setArguments(args);
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_map, container, false);
}
}

View File

@ -0,0 +1,5 @@
package it.danieleverducci.nextcloudmaps.fragments;
public interface SortableListFragment {
}

View File

@ -20,31 +20,23 @@
package it.danieleverducci.nextcloudmaps.model; package it.danieleverducci.nextcloudmaps.model;
import android.graphics.Color;
import android.net.Uri; import android.net.Uri;
import android.widget.Filter; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName; import com.google.gson.annotations.SerializedName;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.Date;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
public class Geofavorite implements Serializable { public class Geofavorite implements Serializable {
public static final String DEFAULT_CATEGORY = "Personal";
private static final double EARTH_RADIUS = 6371; // https://en.wikipedia.org/wiki/Earth_radius
/** /**
* JSON Definition: * JSON Definition:
* { * {
@ -110,17 +102,10 @@ public class Geofavorite implements Serializable {
public void setDateModified(long dateModified) { public void setDateModified(long dateModified) {
this.dateModified = dateModified; this.dateModified = dateModified;
} }
public LocalDate getLocalDateModified() {
return Instant.ofEpochSecond(getDateCreated()).atZone(ZoneId.systemDefault()).toLocalDate();
}
public long getDateCreated() { public long getDateCreated() {
return dateCreated; return dateCreated;
} }
public LocalDate getLocalDateCreated() {
return Instant.ofEpochSecond(getDateCreated()).atZone(ZoneId.systemDefault()).toLocalDate();
}
public void setDateCreated(long dateCreated) { public void setDateCreated(long dateCreated) {
this.dateCreated = dateCreated; this.dateCreated = dateCreated;
} }
@ -154,63 +139,18 @@ public class Geofavorite implements Serializable {
this.comment = comment; this.comment = comment;
} }
/** public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name);
* Comparators for list order public static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id;
*/
public static Comparator<Geofavorite> ByTitleAZ = (gf0, gf1) -> gf0.name.compareTo(gf1.name);
public static Comparator<Geofavorite> ByLastCreated = (gf0, gf1) -> (int) (gf1.dateCreated - gf0.dateCreated);
public static Comparator<Geofavorite> ByCategory = (gf0, gf1) -> (gf0.category + gf0.name).compareTo(gf1.category + gf1.name);
public static Comparator<Geofavorite> ByDistance = (gf0, gf1) -> 0; // (int) ((gf1.getDistanceFrom(userPosition) - gf0.getDistanceFrom(userPosition)) * 1000);
public String getCoordinatesString() { public String getCoordinatesString() {
return this.lat + " N, " + this.lng + " E"; return this.lat + " N, " + this.lng + " E";
} }
public Uri getGeoUri() { public Uri getGeoUri() {
return GeoUriParser.createGeoUri(this.lat, this.lng, this.name); return Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")");
}
public Uri getGmapsUri() {
return GeoUriParser.createGmapsUri(this.lat, this.lng);
} }
public boolean valid() { public boolean valid() {
return return getLat() != 0 && getLng() != 0 && getName() != null && getName().length() > 0;
getLat() != 0 && getLng() != 0 &&
getName() != null && getName().length() > 0 &&
getCategory() != null && getCategory().length() > 0;
}
/**
* Returns the distance between the current Geofavorite and the provided one, in kilometers.
* @param other Geovavorite
* @return the distance in kilometers
*/
public double getDistanceFrom(Geofavorite other) {
double latDistance = (other.lat-lat) * Math.PI / 180;
double lonDistance = (other.lng-lng) * Math.PI / 180;
double a =
Math.sin(latDistance / 2) * Math.sin(latDistance / 2) +
Math.cos(lat * Math.PI / 180) * Math.cos(other.lat * Math.PI / 180) *
Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return EARTH_RADIUS * c;
}
/**
* Based on Nextcloud Maps's getLetterColor util.
* Assigns a color to a category based on its two first letters.
*
* @see "https://github.com/nextcloud/maps/blob/master/src/utils.js"
* @return the generated color or null for the default category
*/
public int categoryColor() {
return categoryColorFromName(this.category);
}
public String categoryLetter() {
if (category == null || category.length() == 0 || category.equals(DEFAULT_CATEGORY))
return "\u2022";
return category.substring(0,1);
} }
@NonNull @NonNull
@ -218,28 +158,4 @@ public class Geofavorite implements Serializable {
public String toString() { public String toString() {
return "[" + getName() + " (" + getLat() + "," + getLng() + ")]"; return "[" + getName() + " (" + getLat() + "," + getLng() + ")]";
} }
/**
* Based on Nextcloud Maps's getLetterColor util.
* Assigns a color to a category based on its two first letters.
*
* @see "https://github.com/nextcloud/maps/blob/master/src/utils.js"
* @return the generated color or null for the default category
*/
public static int categoryColorFromName(String category) {
// If category is default, return null: will be used Nextcloud's accent
if (category == null || category.equals(DEFAULT_CATEGORY) || category.length() == 0)
return 0;
float letter1Index = category.toLowerCase().charAt(0);
float letter2Index = category.toLowerCase().charAt(1);
float letterCoef = ((letter1Index * letter2Index) % 100) / 100;
float h = letterCoef * 360;
float s = 75 + letterCoef * 10;
float l = 50 + letterCoef * 10;
return Color.HSVToColor( new float[]{ Math.round(h), Math.round(s), Math.round(l) });
}
} }

View File

@ -1,176 +0,0 @@
package it.danieleverducci.nextcloudmaps.repository;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.SingleLiveEvent;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
/**
* Singleton pattern
*/
public class GeofavoriteRepository {
private static final String TAG = "GeofavoriteRepository";
private static GeofavoriteRepository instance;
private MutableLiveData<List<Geofavorite>> mGeofavorites;
private MutableLiveData<HashSet<String>> mCategories = new MutableLiveData<HashSet<String>>();
private MutableLiveData<Boolean> mIsUpdating = new MutableLiveData<>(false);
private SingleLiveEvent<Boolean> mOnFinished = new SingleLiveEvent<>();
private Context applicationContext;
public GeofavoriteRepository(Context applicationContext) {
this.applicationContext = applicationContext;
}
public static GeofavoriteRepository getInstance(Context applicationContext) {
if(instance == null){
instance = new GeofavoriteRepository(applicationContext);
}
return instance;
}
public static void resetInstance() {
instance = null;
}
public MutableLiveData<List<Geofavorite>> getGeofavorites(){
if (mGeofavorites == null) {
mGeofavorites = new MutableLiveData<>();
mGeofavorites.setValue(new ArrayList<>());
}
return mGeofavorites;
}
public MutableLiveData<HashSet<String>> getCategories() {
return mCategories;
}
public MutableLiveData<Boolean> isUpdating() {
return mIsUpdating;
}
public SingleLiveEvent<Boolean> onFinished() {
return mOnFinished;
}
public void updateGeofavorites() {
mIsUpdating.postValue(true);
// Obtain geofavorites
Call<List<Geofavorite>> call = ApiProvider.getAPI(this.applicationContext).getGeofavorites();
call.enqueue(new Callback<List<Geofavorite>>() {
@Override
public void onResponse(@NonNull Call<List<Geofavorite>> call, @NonNull Response<List<Geofavorite>> response) {
if (response.isSuccessful() && response.body() != null) {
mGeofavorites.postValue(response.body());
updateCategories(response.body());
mIsUpdating.postValue(false);
mOnFinished.postValue(true);
} else {
onFailure(call, new Throwable("Dataset is empty"));
}
}
@Override
public void onFailure(@NonNull Call<List<Geofavorite>> call, @NonNull Throwable t) {
mIsUpdating.postValue(false);
mOnFinished.postValue(false);
}
});
}
public Geofavorite getGeofavorite(int id) {
for (Geofavorite g : mGeofavorites.getValue()) {
if (g.getId() == id)
return g;
}
return null;
}
public void saveGeofavorite(Geofavorite geofav) {
mIsUpdating.postValue(true);
Call<Geofavorite> call;
if (geofav.getId() == 0) {
// New geofavorite
call = ApiProvider.getAPI(this.applicationContext).createGeofavorite(geofav);
} else {
// Update existing geofavorite
call = ApiProvider.getAPI(this.applicationContext).updateGeofavorite(geofav.getId(), geofav);
}
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
if (response.isSuccessful()) {
List<Geofavorite> geofavs = mGeofavorites.getValue();
if (geofav.getId() != 0) {
geofavs.remove(geofav);
}
geofavs.add(response.body());
mGeofavorites.postValue(geofavs);
mIsUpdating.postValue(false);
mOnFinished.postValue(true);
} else {
mIsUpdating.postValue(false);
mOnFinished.postValue(false);
}
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
Log.e(TAG, t.getMessage());
mIsUpdating.postValue(false);
mOnFinished.postValue(false);
}
});
}
public void deleteGeofavorite(Geofavorite geofav) {
mIsUpdating.postValue(true);
// Delete Geofavorite
Call<Geofavorite> call = ApiProvider.getAPI(this.applicationContext).deleteGeofavorite(geofav.getId());
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
List<Geofavorite> geofavs = mGeofavorites.getValue();
if (geofavs.remove(geofav)) {
mGeofavorites.postValue(geofavs);
mIsUpdating.postValue(false);
mOnFinished.postValue(true);
} else {
// Should never happen
mIsUpdating.postValue(false);
mOnFinished.postValue(false);
}
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
mIsUpdating.postValue(false);
mOnFinished.postValue(false);
}
});
}
private void updateCategories(List<Geofavorite> geofavs) {
HashSet<String> categories = new HashSet<>();
for (Geofavorite g : geofavs) {
String cat = g.getCategory();
if (cat != null)
categories.add(cat);
}
mCategories.postValue(categories);
}
}

View File

@ -1,62 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import android.view.View;
import android.view.ViewGroup;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
public class EdgeToEdgeUtils {
/**
* Clears the system bars in edge to edge mode (https://developer.android.com/develop/ui/views/layout/edge-to-edge)
* @param viewToClear the view to clear from system bars
*/
public static void clearBottomBarWithMargin(View viewToClear) {
ViewCompat.setOnApplyWindowInsetsListener(viewToClear, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
mlp.bottomMargin = insets.bottom;
mlp.leftMargin = insets.left;
mlp.rightMargin = insets.right;
v.setLayoutParams(mlp);
//return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
}
/**
* Clears the system bars in edge to edge mode (https://developer.android.com/develop/ui/views/layout/edge-to-edge)
* @param viewToClear the view to clear from system bars
*/
public static void clearTopBarWithMargin(View viewToClear) {
ViewCompat.setOnApplyWindowInsetsListener(viewToClear, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) v.getLayoutParams();
mlp.topMargin = insets.top;
mlp.leftMargin = insets.left;
mlp.rightMargin = insets.right;
v.setLayoutParams(mlp);
//return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
}
/**
* Clears the system bars in edge to edge mode (https://developer.android.com/develop/ui/views/layout/edge-to-edge)
* @param viewToClear the view to clear from system bars
*/
public static void clearTopBarWithPadding(View viewToClear) {
ViewCompat.setOnApplyWindowInsetsListener(viewToClear, (v, windowInsets) -> {
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(insets.left, insets.top, insets.right, v.getPaddingBottom());
//return WindowInsetsCompat.CONSUMED;
return windowInsets;
});
}
}

View File

@ -1,88 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import android.net.Uri;
import android.util.Log;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeoUriParser {
private static final Pattern PATTERN_GEO = Pattern.compile("geo:(-?[\\d.]+),(-?[\\d.]+)");
// Try to match not only geoUri but also Google Maps Uri
private static final Pattern PATTERN_BROAD = Pattern.compile(
"(?:@|&query=|&ce\nter=|geo:|#map=\\d{1,2}\\/)(-?\\d{1,2}\\.\\d+)(?:,|%2C|\\/)(-?\\d{1,3}\\.\\d{1,10})"
);
/**
* Parses an URI into latitude and longitude
* @param uri to parse
* @param strict if true, the uri must be a valid geo: uri, otherwise a broader check is applied to include other uris, like Google Maps ones
* @return the parsed coordinates in an array [lat,lon]
* @throws IllegalArgumentException if the url could not be parsed
*/
public static double[] parseUri(Uri uri, boolean strict) throws IllegalArgumentException {
if (uri == null)
throw new IllegalArgumentException("no uri");
// Try to extract coordinates in uri string with regexp
Pattern pattern = strict ? PATTERN_GEO : PATTERN_BROAD;
Matcher m = pattern.matcher(uri.toString());
if (!m.find() || m.groupCount() != 2)
throw new IllegalArgumentException("unable to parse uri: unable to find coordinates in uri");
// Obtain coordinates from regexp result
String sLat = m.group(1);
String sLon = m.group(2);
double[] coords = null;
try {
// Check coordinates are numeric
coords = new double[]{Double.parseDouble(sLat), Double.parseDouble(sLon)};
} catch (NumberFormatException e) {
throw new IllegalArgumentException("unable to parse uri: coordinates are not double");
}
// Check coordinates validity
String error = checkCoordsValidity(coords[0], coords[1]);
if (error != null)
throw new IllegalArgumentException(error);
return coords;
}
public static Uri createGeoUri(double lat, double lon, String name) {
String error = checkCoordsValidity(lat, lon);
if (error != null)
throw new IllegalArgumentException(error);
String uriStr = "geo:" + lat + "," + lon;
if (name != null)
uriStr += "(" + name + ")";
return Uri.parse(uriStr);
}
public static Uri createGmapsUri(double lat, double lon) {
String error = checkCoordsValidity(lat, lon);
if (error != null)
throw new IllegalArgumentException(error);
String uriStr = "https://www.google.com/maps/search/?api=1&query=" + lat + "," + lon;
return Uri.parse(uriStr);
}
/**
* Checks a latitude/longitude is valid
* @param lat latitude
* @param lon longitude
* @return null if valid, a string containing an error otherwise
*/
private static String checkCoordsValidity(double lat, double lon) {
// Check coords validity
if (lon <= -180 || lon >= 180 )
return "Invalid longitude: " + lon;
if (lat <= -90 || lat >= 90)
return "Invalid latitude: " + lat;
return null;
}
}

View File

@ -1,47 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import java.util.ArrayList;
import java.util.List;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavoritesFilter {
List<Geofavorite> items;
public GeofavoritesFilter(List<Geofavorite> items) {
this.items = items;
}
public List<Geofavorite> byText(String text) {
List<Geofavorite> filteredGeofavorites = new ArrayList<>();
if (text.isEmpty()) {
return items;
} else {
for (Geofavorite geofavorite : items) {
String query = text.toLowerCase();
if (geofavorite.getName() != null && geofavorite.getName().toLowerCase().contains(query)) {
filteredGeofavorites.add(geofavorite);
} else if (geofavorite.getComment() != null && geofavorite.getComment().toLowerCase().contains(query)) {
filteredGeofavorites.add(geofavorite);
}
}
}
return filteredGeofavorites;
}
public List<Geofavorite> byCategory(String category) {
List<Geofavorite> filteredGeofavorites = new ArrayList<>();
if (category == null) {
return items;
} else {
for (Geofavorite geofavorite : items) {
if (geofavorite.getCategory().equals(category)) {
filteredGeofavorites.add(geofavorite);
}
}
}
return filteredGeofavorites;
}
}

View File

@ -1,39 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class IntentGenerator {
public static Intent newShareIntent(Context context, Geofavorite item) {
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
String shareMessage = context.getString(R.string.share_message)
.replace("{title}", item.getName() == null ? context.getString(R.string.share_message_default_title) : item.getName())
.replace("{lat}", ""+item.getLat())
.replace("{lng}", ""+item.getLng());
i.putExtra(Intent.EXTRA_TEXT, shareMessage );
return i;
}
public static Intent newGeoUriIntent(Context context, Geofavorite item) {
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(isGoogleMapsInstalled(context) ? item.getGmapsUri() : item.getGeoUri());
return i;
}
public static boolean isGoogleMapsInstalled(Context context) {
try {
context.getPackageManager().getApplicationInfo("com.google.android.apps.maps", 0);
return true;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
}

View File

@ -1,36 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import android.content.Context;
import android.content.res.Configuration;
import androidx.preference.PreferenceManager;
import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.TilesOverlay;
import it.danieleverducci.nextcloudmaps.BuildConfig;
public class MapUtils {
public static void configOsmdroid(Context context) {
IConfigurationProvider osmdroidConfig = org.osmdroid.config.Configuration.getInstance();
osmdroidConfig.load(context,
PreferenceManager.getDefaultSharedPreferences(context));
osmdroidConfig.setUserAgentValue(BuildConfig.APPLICATION_ID);
}
public static void setTheme(MapView mapView) {
int currentNightMode = mapView.getContext().getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
// Night mode is not active, we're using the light theme
mapView.getOverlayManager().getTilesOverlay().setColorFilter(null);
break;
case Configuration.UI_MODE_NIGHT_YES:
// Night mode is active, we're using dark theme
mapView.getOverlayManager().getTilesOverlay().setColorFilter(TilesOverlay.INVERT_COLORS);
break;
}
}
}

View File

@ -1,57 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter;
public class SettingsManager {
static private final String SETTING_SORT_BY = "SETTING_SORT_BY";
static private final String SETTING_LAST_SELECTED_LIST_VIEW = "SETTING_LAST_SELECTED_LIST_VIEW";
static private final String SETTING_LAST_MAP_POSITION_LAT = "SETTING_LAST_MAP_POSITION_LAT";
static private final String SETTING_LAST_MAP_POSITION_LNG = "SETTING_LAST_MAP_POSITION_LNG";
static private final String SETTING_LAST_MAP_POSITION_ZOOM = "SETTING_LAST_MAP_POSITION_ZOOM";
public static int getGeofavoriteListSortBy(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getInt(SETTING_SORT_BY, GeofavoriteAdapter.SORT_BY_CREATED);
}
public static void setGeofavoriteListSortBy(Context context, int value) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putInt(SETTING_SORT_BY, value).apply();
}
public static boolean isGeofavoriteListShownAsMap(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(SETTING_LAST_SELECTED_LIST_VIEW, false);
}
public static void setGeofavoriteListShownAsMap(Context context, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean(SETTING_LAST_SELECTED_LIST_VIEW, value).apply();
}
/**
* Returns the last saved position
* @param context
* @return double[lat,lng,zoom]
*/
public static double[] getLastMapPosition(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return new double[] {
(double) sp.getFloat(SETTING_LAST_MAP_POSITION_LAT, 0.0f),
(double) sp.getFloat(SETTING_LAST_MAP_POSITION_LNG, 0.0f),
(double) sp.getFloat(SETTING_LAST_MAP_POSITION_ZOOM, 10.0f),
};
}
public static void setLastMapPosition(Context context, double lat, double lng, double zoom) {
PreferenceManager.getDefaultSharedPreferences(context).edit()
.putFloat(SETTING_LAST_MAP_POSITION_LAT, (float)lat)
.putFloat(SETTING_LAST_MAP_POSITION_LNG, (float)lng)
.putFloat(SETTING_LAST_MAP_POSITION_ZOOM, (float)zoom)
.apply();
}
}

View File

@ -1,44 +0,0 @@
package it.danieleverducci.nextcloudmaps.utils;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Events implementation in LiveData
* From: https://gist.github.com/teegarcs/319a3e7e4736a0cce8eba2216c52b0ca#file-singleliveevent
* @param <T>
*/
public class SingleLiveEvent<T> extends MutableLiveData<T> {
AtomicBoolean mPending = new AtomicBoolean(false);
@MainThread
@Override
public void setValue(T value) {
mPending.set(true);
super.setValue(value);
}
@MainThread
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
super.observe(owner, new Observer<T>() {
@Override
public void onChanged(T t) {
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t);
}
}
});
}
/**
* Util function for Void implementations.
*/
public void call() {
setValue(null);
}
}

View File

@ -1,72 +0,0 @@
package it.danieleverducci.nextcloudmaps.views;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.View;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.drawable.DrawableCompat;
import org.osmdroid.views.MapView;
import org.osmdroid.views.overlay.infowindow.InfoWindow;
import org.osmdroid.views.overlay.infowindow.MarkerInfoWindow;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavMarkerInfoWindow extends MarkerInfoWindow implements View.OnClickListener {
private OnGeofavMarkerInfoWindowClickListener onGeofavMarkerInfoWindowClickListener;
public GeofavMarkerInfoWindow(MapView mapView, Geofavorite geofavorite) {
super(R.layout.infowindow_geofav, mapView);
Context context = getView().getContext();
// Set category color
View category = getView().findViewById(R.id.bubble_subdescription);
Drawable backgroundDrawable = category.getBackground();
DrawableCompat.setTint(backgroundDrawable, geofavorite.categoryColor() == 0 ? context.getColor(R.color.defaultBrand) : geofavorite.categoryColor());
// Set listeners
getView().findViewById(R.id.action_icon_share).setOnClickListener(this);
getView().findViewById(R.id.action_icon_nav).setOnClickListener(this);
getView().findViewById(R.id.action_icon_delete).setOnClickListener(this);
getView().findViewById(R.id.action_icon_edit).setOnClickListener(this);
}
public void setOnGeofavMarkerInfoWindowClickListener(OnGeofavMarkerInfoWindowClickListener l) {
this.onGeofavMarkerInfoWindowClickListener = l;
}
@Override
public void onOpen(Object item) {
InfoWindow.closeAllInfoWindowsOn(getMapView());
super.onOpen(item);
}
@Override
public void onClick(View v) {
if (onGeofavMarkerInfoWindowClickListener == null)
return;
if (v.getId() == R.id.action_icon_share)
onGeofavMarkerInfoWindowClickListener.onGeofavMarkerInfoWindowShareClick();
if (v.getId() == R.id.action_icon_nav)
onGeofavMarkerInfoWindowClickListener.onGeofavMarkerInfoWindowNavClick();
if (v.getId() == R.id.action_icon_delete)
onGeofavMarkerInfoWindowClickListener.onGeofavMarkerInfoWindowDeleteClick();
if (v.getId() == R.id.action_icon_edit)
onGeofavMarkerInfoWindowClickListener.onGeofavMarkerInfoWindowEditClick();
}
public interface OnGeofavMarkerInfoWindowClickListener {
public void onGeofavMarkerInfoWindowEditClick();
public void onGeofavMarkerInfoWindowShareClick();
public void onGeofavMarkerInfoWindowNavClick();
public void onGeofavMarkerInfoWindowDeleteClick();
}
}

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="oval">
<solid android:color="@color/defaultBrandAlpha"/>
</shape>
</item>
</layer-list>

View File

@ -1,13 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="209.21dp"
android:height="45.14dp"
android:viewportWidth="209.21"
android:viewportHeight="45.14">
<path
android:pathData="m204.31,0.14c-0.97,0.02 -2.1,0.07 -3.3,0.15l8.18,-0.01c0,0 -1.81,-0.2 -4.89,-0.14zM201.01,0.29 L0.02,0.46c0,0 32.67,5.29 57.83,17.7 25.15,12.42 45.33,26.81 45.33,26.81 0,0 18.75,-13.75 38.46,-26.28 20.46,-13 49.14,-17.73 59.37,-18.41z"
android:strokeLineJoin="miter"
android:strokeWidth="0.264583"
android:fillColor="#000000"
android:strokeColor="#00000000"
android:strokeLineCap="butt"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46,-4.27l9.81,9.81z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
</vector>

View File

@ -1,16 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2l-5.5,9h11z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M17.5,17.5m-4.5,0a4.5,4.5 0,1 1,9 0a4.5,4.5 0,1 1,-9 0"/>
<path
android:fillColor="@android:color/white"
android:pathData="M3,13.5h8v8H3z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM19,5L8,5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h11c1.1,0 2,-0.9 2,-2L21,7c0,-1.1 -0.9,-2 -2,-2zM19,21L8,21L8,7h11v14z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M21,3L3,10.53v0.98l6.84,2.65L12.48,21h0.98L21,3z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#0082C9"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M4.25,5.61C6.27,8.2 10,13 10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-6c0,0 3.72,-4.8 5.74,-7.39C20.25,4.95 19.78,4 18.95,4H5.04C4.21,4 3.74,4.95 4.25,5.61z"/>
</vector>

View File

@ -1,6 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M19.79,5.61C20.3,4.95 19.83,4 19,4H6.83l7.97,7.97L19.79,5.61z"/>
<path android:fillColor="@android:color/white" android:pathData="M2.81,2.81L1.39,4.22L10,13v6c0,0.55 0.45,1 1,1h2c0.55,0 1,-0.45 1,-1v-2.17l5.78,5.78l1.41,-1.41L2.81,2.81z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="48dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM12.8,13.22v1.75l3.2,-2.99L12.8,9v1.7c-3.11,0.43 -4.35,2.56 -4.8,4.7 1.11,-1.5 2.58,-2.18 4.8,-2.18z"/>
</vector>

View File

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?android:attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@ -1,5 +0,0 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48V20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48V3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM15,19l-6,-2.11V5l6,2.11V19z"/>
</vector>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/defaultBackground"/>
<stroke android:width="7dp" android:color="@color/defaultBrand" />
<corners android:radius="20dp"/>
</shape>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="oval">
<solid android:color="@color/defaultBrand"/>
</shape>
</item>
</layer-list>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#000" />
<padding android:left="10dp" android:top="3dp" android:right="10dp" android:bottom="3dp" />
<corners android:radius="20dp" />
</shape>

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:shape="oval">
<solid android:color="@color/translucent"/>
</shape>
</item>
</layer-list>

View File

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape
android:innerRadius="0dp"
android:shape="oval"
android:useLevel="false">
<solid android:color="@android:color/transparent" />
<stroke
android:width="10dp"
android:color="@color/toolbar_background" />
</shape>
</item>
</layer-list>

View File

@ -32,7 +32,7 @@
<androidx.appcompat.widget.Toolbar <androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize" android:layout_height="?attr/actionBarSize"
app:contentInsetStartWithNavigation="0dp" app:contentInsetStartWithNavigation="0dp"
app:navigationIcon="@drawable/ic_back_grey" app:navigationIcon="@drawable/ic_back_grey"
app:titleMarginStart="0dp"/> app:titleMarginStart="0dp"/>
@ -115,20 +115,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="10dp" android:padding="10dp"
android:text="@string/about_issues" /> android:text="@string/about_issues" />
<TextView
style="?android:attr/listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/about_maps_title" />
<TextView
android:id="@+id/about_maps"
style="?android:attr/editTextPreferenceStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:text="@string/about_maps" />
</LinearLayout> </LinearLayout>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>

View File

@ -2,30 +2,23 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/root"> android:id="@+id/root">
<com.google.android.material.appbar.AppBarLayout <ScrollView
android:id="@+id/appbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="250dp"> android:layout_height="match_parent">
<com.google.android.material.appbar.CollapsingToolbarLayout <LinearLayout
android:id="@+id/collapsing_toolbar" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content">
app:contentScrim="@color/defaultBrand"
app:layout_scrollFlags="scroll|snap|exitUntilCollapsed"
app:title="@string/new_geobookmark"
app:expandedTitleTextAppearance="@style/TextAppearance.GeofavoriteDetail.Header.Expanded"
app:collapsedTitleTextAppearance="@style/TextAppearance.GeofavoriteDetail.Header.Collapsed">
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="200dp">
app:layout_collapseMode="parallax">
<org.osmdroid.views.MapView <org.osmdroid.views.MapView
android:id="@+id/map" android:id="@+id/map"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -38,250 +31,121 @@
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</FrameLayout> </FrameLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?android:attr/actionBarSize"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
android:background="@android:color/transparent">
<!-- Back button -->
<ImageView
android:id="@+id/back_bt"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="start"
android:layout_margin="8dp"
android:padding="8dp"
android:src="@drawable/ic_back_grey"
app:tint="@color/white"
android:background="@drawable/floating_semitransparent_button_background"/>
<!-- Save button -->
<ImageView
android:id="@+id/submit_bt"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="end"
android:layout_margin="8dp"
android:padding="8dp"
android:src="@drawable/ic_ok"
app:tint="@color/white"
android:background="@drawable/round_button_background"/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="20dp">
<LinearLayout <LinearLayout
android:id="@+id/accuracy_progress_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:src="@drawable/ic_accuracy_fail"
app:tint="@color/disabled"/>
<ProgressBar
android:id="@+id/accuracy_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:indeterminate="true"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:src="@drawable/ic_accuracy_ok"/>
</LinearLayout>
<TextView
android:id="@+id/accuracy_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:textAlignment="center"
android:text="@string/accuracy_nosignal"
android:textColor="@color/defaultBrand"/>
<LinearLayout
android:id="@+id/action_icons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/action_icon_share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_share"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_nav"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_delete_grey"
app:tint="@color/defaultBrand"
android:visibility="gone"/> <!-- TODO Implement delete -->
</LinearLayout>
<EditText
android:id="@+id/name_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:ems="10"
android:hint="@string/name"
android:lines="1"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/defaultBrand"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:background="@android:color/transparent"
android:drawableLeft="@drawable/ic_edit"
android:drawablePadding="5dp"
android:drawableTint="@color/defaultBrand"/>
<EditText
android:id="@+id/description_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
android:maxLines="10"
android:background="@android:color/transparent"
android:drawableLeft="@drawable/ic_edit"
android:drawablePadding="5dp"
android:drawableTint="@color/defaultBrand"
android:hint="@string/description"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp">
<AutoCompleteTextView
android:id="@+id/category_at"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxLines="1"
android:background="@android:color/transparent"
android:drawableLeft="@drawable/ic_category_asc"
android:drawablePadding="5dp"
android:drawableTint="@color/defaultBrand"
android:hint="@string/category"
android:completionThreshold="0"/>
<ImageButton
android:id="@+id/category_at_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="3dp"
android:background="@color/transparent"
android:src="@drawable/ic_clear"/>
</LinearLayout>
<TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:text="@string/category_hint" android:orientation="vertical"
android:textSize="12sp"/> android:padding="20dp">
<TextView <EditText
android:layout_width="match_parent" android:id="@+id/name_et"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_marginTop="40dp" android:layout_height="wrap_content"
android:textStyle="bold" android:ems="10"
android:text="@string/created" /> android:hint="@string/name"
android:lines="1"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"/>
<TextView <EditText
android:id="@+id/created_tv" android:id="@+id/description_et"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="textEnd" /> android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
android:lines="5"
android:maxLines="10"
android:hint="@string/description"
android:ellipsize="end" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:textStyle="bold" android:textStyle="bold"
android:text="@string/modified" /> android:text="@string/created" />
<TextView <TextView
android:id="@+id/modified_tv" android:id="@+id/created_tv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="textEnd" /> android:textAlignment="textEnd" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:text="@string/coords" android:textStyle="bold"
android:textStyle="bold" /> android:text="@string/modified" />
<TextView <TextView
android:id="@+id/coords_tv" android:id="@+id/modified_tv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="textEnd" android:textAlignment="textEnd" />
app:drawableEndCompat="@drawable/ic_copy"
android:drawablePadding="10sp"/>
<ProgressBar <TextView
android:id="@+id/progress" android:layout_width="match_parent"
android:layout_width="match_parent" android:layout_height="wrap_content"
android:layout_height="wrap_content" android:layout_marginTop="20dp"
android:indeterminate="true" android:textStyle="bold"
android:visibility="gone"/> android:text="@string/category" />
<TextView
android:id="@+id/category_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/coords"
android:textStyle="bold" />
<TextView
android:id="@+id/coords_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:id="@+id/accuracy_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textAlignment="center"
android:text="@string/accuracy"
android:textColor="@android:color/white"
android:background="@android:color/darker_gray"/>
<Button
android:id="@+id/submit_bt"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="50dp"
android:text="@string/confirm"
app:backgroundTint="@color/defaultBrand"
android:onClick="onSubmit"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView> <!-- Back button -->
<ImageView
android:id="@+id/back_bt"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_back_grey"
app:tint="@color/white"
android:background="@color/defaultBrandAlpha"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </FrameLayout>
</layout> </layout>

View File

@ -1,80 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Maps Geofavorites for Android
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="it.danieleverducci.nextcloudmaps.fragments.GeofavoriteListFragment"
tools:layout="@layout/fragment_geofavorite_list" />
<FrameLayout
android:id="@+id/fab_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="bottom|end">
<!-- Add from map FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_from_map"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_margin="24dp"
android:src="@drawable/ic_add_map"
app:backgroundTint="@color/defaultBrand"
app:fabSize="mini"
app:tint="@color/white"
tools:ignore="DuplicateClickableBoundsCheck"
android:contentDescription="@string/add_from_map" />
<!-- Add from current position FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_from_gps"
android:layout_margin="24dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_add_gps"
app:backgroundTint="@color/defaultBrand"
app:tint="@color/white"
android:contentDescription="@string/add_from_gps"/>
<!-- Main FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/open_fab"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
app:backgroundTint="@color/defaultBrand"
app:tint="@color/white"
android:contentDescription="@string/open_fab"/>
</FrameLayout>
</FrameLayout>

View File

@ -46,7 +46,8 @@
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"
android:textSize="24sp" android:textSize="24sp"
android:gravity="center_horizontal"/> android:gravity="center_horizontal"
android:textColor="@color/accent"/>
<Button <Button
android:id="@+id/chose_button" android:id="@+id/chose_button"

View File

@ -26,65 +26,172 @@
tools:openDrawer="start" tools:openDrawer="start"
tools:context=".activity.main.MainActivity"> tools:context=".activity.main.MainActivity">
<include layout="@layout/activity_list_view" <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/activity_list_view" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:background="@color/primary"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"
android:id="@+id/root">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:toolbarId="@+id/toolbar" >
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:contentInsetStartWithNavigation="0dp"
app:titleMarginStart="0dp"
tools:title="@string/app_name">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.appcompat.widget.SearchView>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacer_2x"
android:layout_marginTop="@dimen/spacer_1hx"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_marginBottom="@dimen/spacer_1hx"
app:cardBackgroundColor="@color/appbar"
app:cardCornerRadius="@dimen/spacer_1x"
app:cardElevation="2dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menu_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_menu_grey"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/spacer_1x"
android:layout_marginEnd="@dimen/spacer_1x"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="start"
android:lines="1"
android:textSize="16sp"
android:text="@string/search_in_all"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/sort_mode"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="center_vertical|end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:padding="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:translationX="@dimen/spacer_1x"
android:src="@drawable/ic_alphabetical_asc" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:name="it.danieleverducci.nextcloudmaps.fragments.GeofavoriteListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.fragment.app.FragmentContainerView>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
app:backgroundTint="@color/defaultBrand"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<!-- TODO: Is use another layout for that is fill screen. -->
<com.google.android.material.navigation.NavigationView <com.google.android.material.navigation.NavigationView
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="start"> android:layout_gravity="start"
android:fitsSystemWindows="true">
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:id="@+id/scrollView" android:id="@+id/scrollView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/colorPrimary"> android:background="?attr/colorPrimary">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<FrameLayout <RelativeLayout
android:id="@+id/header_view" android:id="@+id/header_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="@dimen/drawer_header_height"
android:background="@color/defaultBrand"> android:background="@color/defaultBrand">
<LinearLayout <androidx.appcompat.widget.AppCompatImageView
android:layout_width="match_parent" android:id="@+id/logo"
android:layout_width="@dimen/drawer_header_logo_size"
android:layout_height="@dimen/drawer_header_logo_size"
android:layout_centerVertical="true"
android:layout_margin="@dimen/spacer_2x"
android:gravity="center"
android:src="@drawable/ic_app" />
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical"> android:layout_centerVertical="true"
android:layout_toEndOf="@id/logo"
<androidx.appcompat.widget.AppCompatImageView android:ellipsize="end"
android:id="@+id/logo" android:fontFamily="sans-serif-light"
android:layout_width="@dimen/drawer_header_logo_size" android:gravity="center_vertical"
android:layout_height="@dimen/drawer_header_logo_size" android:text="@string/app_name"
android:layout_margin="@dimen/spacer_2x" android:textColor="@android:color/white"
android:gravity="center" android:textSize="24sp"
android:src="@drawable/ic_app" /> android:layout_toRightOf="@id/logo" />
</RelativeLayout>
<TextView
android:id="@+id/app_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_marginRight="20dp"
android:ellipsize="end"
android:lines="2"
android:fontFamily="sans-serif-light"
android:gravity="center_vertical"
android:text="@string/app_name"
android:textColor="@android:color/white"
android:textSize="24sp"/>
</LinearLayout>
</FrameLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/navigationCommon" android:id="@+id/navigationCommon"

View File

@ -1,107 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<FrameLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.osmdroid.views.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="24dp"
android:layout_gravity="center"
android:src="@drawable/ic_map_pin"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/back_bt"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:layout_margin="16dp"
android:layout_gravity="start"
android:padding="16dp"
android:src="@drawable/ic_back_grey"
app:tint="@color/text_color"
android:background="@drawable/unselected_floating_semitransparent_button_background"/>
<ImageView
android:id="@+id/ok_bt"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:layout_margin="16dp"
android:layout_gravity="end"
android:padding="16dp"
android:src="@drawable/ic_ok"
app:tint="@color/white"
android:background="@drawable/round_button_background"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:padding="10dp"
android:orientation="horizontal"
android:background="@color/defaultBrandAlpha">
<EditText
android:id="@+id/lat_et"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:textAlignment="center"
android:lines="1"
android:maxLines="1"
android:inputType="numberDecimal"
android:textColor="@color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textStyle="bold"
android:text="°N"
android:textColor="@color/white"/>
<EditText
android:id="@+id/lon_et"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight="1"
android:layout_marginStart="10dp"
android:textAlignment="center"
android:lines="1"
android:maxLines="1"
android:inputType="numberDecimal"
android:textColor="@color/white"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:textStyle="bold"
android:text="°E"
android:textColor="@color/white"/>
<ImageView
android:id="@+id/latlon_confirm_btn"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginLeft="10dp"
android:padding="8dp"
android:layout_gravity="center_vertical"
android:src="@drawable/ic_ok"
app:tint="@color/white"
android:background="@drawable/round_button_background"
android:visibility="gone"/>
</LinearLayout>
</FrameLayout>
</layout>

View File

@ -1,103 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:contentInsetStartWithNavigation="0dp"
app:titleMarginStart="0dp"
tools:title="@string/app_name">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:background="@color/translucent">
</androidx.appcompat.widget.SearchView>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacer_2x"
android:layout_marginTop="@dimen/spacer_1hx"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_marginBottom="@dimen/spacer_1hx"
app:cardBackgroundColor="@color/toolbar_background"
app:cardCornerRadius="30dp"
app:cardElevation="2dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menu_button"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical|start"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x"
android:tint="@color/text_color"
android:src="@drawable/ic_menu_grey"
android:contentDescription="@string/menu"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/spacer_1x"
android:layout_marginEnd="@dimen/spacer_1x"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="start"
android:lines="1"
android:textSize="16sp"
android:text="@string/search_in_all"/>
<!-- Filter button -->
<ImageButton
android:id="@+id/search_filter"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize"
android:src="@drawable/ic_filter_off"
app:tint="@color/inactive"
android:background="@color/transparent"/>
<!-- User badge -->
<FrameLayout
android:id="@+id/user_badge_container"
android:layout_width="?android:attr/actionBarSize"
android:layout_height="?android:attr/actionBarSize">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/user_badge"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"/>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/user_badge_mask"/>
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</FrameLayout>

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<TextView
android:id="@+id/category_name"
android:layout_width="match_parent"
android:layout_height="30dp"
android:gravity="center_vertical"
android:lines="1"
android:ellipsize="end"
android:text="Lorem ipsum"
app:drawableLeftCompat="@drawable/ic_category_asc"
android:drawablePadding="8sp"
android:drawableTint="@color/white"
android:textColor="@color/white"
android:textStyle="bold"
android:background="@drawable/rounded_label_background"/>
</FrameLayout>

View File

@ -1,89 +1,55 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Maps Geofavorites for Android
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
android:id="@+id/activity_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout <FrameLayout
android:id="@+id/appBar" android:id="@+id/root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
app:contentScrim="?android:attr/colorPrimary" android:layout_margin="@dimen/spacer_1hx"
app:layout_scrollFlags="scroll|enterAlways" app:layout_behavior="@string/appbar_scrolling_view_behavior">
app:toolbarId="@+id/toolbar" >
<include layout="@layout/app_toolbar"/> <androidx.core.widget.NestedScrollView
<RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent">
android:orientation="horizontal"
android:layout_marginTop="?android:attr/actionBarSize"
android:paddingTop="20dp"
android:paddingStart="5dp"
android:paddingEnd="5dp">
<androidx.appcompat.widget.AppCompatImageView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/sort_mode" android:id="@+id/recycler_view"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="@dimen/floating_bar_height" android:layout_height="match_parent"
android:layout_alignParentLeft="true" app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
android:background="?android:attr/selectableItemBackgroundBorderless" app:spanCount="1"
android:contentDescription="@string/list_mode" tools:itemCount="3"
android:paddingStart="@dimen/spacer_2x" tools:listitem="@layout/item_geofav">
android:paddingEnd="@dimen/spacer_2x" </androidx.recyclerview.widget.RecyclerView>
android:tint="@color/text_color"
android:src="@drawable/ic_alphabetical_asc" />
<androidx.appcompat.widget.AppCompatImageView </androidx.core.widget.NestedScrollView>
android:id="@+id/view_mode_map"
android:layout_width="wrap_content"
android:layout_height="@dimen/floating_bar_height"
android:layout_alignParentRight="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:paddingStart="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_2x"
android:tint="@color/text_color"
android:src="@drawable/ic_view_map" />
</RelativeLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout> </FrameLayout>
</com.google.android.material.appbar.AppBarLayout> </layout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="1"
tools:itemCount="3"
tools:listitem="@layout/item_geofav">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_list_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.osmdroid.views.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<FrameLayout
android:id="@+id/loading_wall"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/translucent"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_gravity="center"/>
</FrameLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp"
android:orientation="vertical">
<include layout="@layout/app_toolbar"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/view_mode_list"
android:layout_width="@dimen/floating_bar_height"
android:layout_height="@dimen/floating_bar_height"
android:layout_marginTop="2dp"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_gravity="right"
android:background="@drawable/unselected_floating_semitransparent_button_background"
android:contentDescription="@string/list_mode"
android:paddingStart="5dp"
android:paddingEnd="5dp"
android:tint="@color/text_color"
android:src="@drawable/ic_view_list" />
</androidx.appcompat.widget.LinearLayoutCompat>
<FrameLayout
android:id="@+id/center_position_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="100dp"
android:layout_gravity="bottom|right">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/center_position"
android:layout_width="@dimen/floating_bar_height"
android:layout_height="@dimen/floating_bar_height"
android:layout_marginBottom="25dp"
android:background="@drawable/unselected_floating_semitransparent_button_background"
android:padding="5dp"
android:tint="@color/text_color"
android:src="@drawable/ic_add_gps"
android:visibility="visible"/>
</FrameLayout>
</FrameLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".fragments.MapFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Map fragment" />
</FrameLayout>

View File

@ -1,119 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="250dp"
android:background="@drawable/infowindow_geofav_background"
android:padding="7dp"
android:orientation="vertical">
<!-- Geofavorite title -->
<TextView
android:id="@+id/bubble_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/defaultBrand"
android:padding="10dp"
android:lines="1"
android:ellipsize="end"
style="@style/TextAppearance.GeofavoriteInfowindow"
android:text="Lorem"/>
<!-- Geofavorite description -->
<TextView
android:id="@+id/bubble_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="Lorem ipsum"/>
<!-- Geofavorite category -->
<TextView
android:id="@+id/bubble_subdescription"
android:layout_width="wrap_content"
android:layout_height="30dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:gravity="center_vertical"
android:lines="1"
android:ellipsize="end"
android:text="Lorem ipsum"
app:drawableLeftCompat="@drawable/ic_category_asc"
android:drawablePadding="8sp"
android:drawableTint="@color/white"
android:textColor="@color/white"
android:textStyle="bold"
android:background="@drawable/rounded_label_background"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/action_icon_share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_share"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_nav"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_delete_grey"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_edit"
app:tint="@color/defaultBrand"/>
</LinearLayout>
<ImageView
android:id="@+id/bubble_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout>
<ImageView
android:layout_width="50dp"
android:layout_height="10dp"
android:layout_marginTop="-1dp"
android:layout_gravity="center_horizontal"
android:src="@drawable/geofav_infowindow_pointer"
app:tint="@color/defaultBrand" />
</LinearLayout>

View File

@ -19,104 +19,69 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="90dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:padding="12dp"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
<TextView <ImageView
android:id="@+id/tv_category" android:layout_width="wrap_content"
android:layout_width="64dp" android:layout_height="match_parent"
android:layout_height="64dp"
android:layout_marginStart="4dp"
android:layout_marginTop="14dp"
android:layout_marginBottom="14dp"
android:layout_weight="0" android:layout_weight="0"
android:background="@drawable/ic_list_pin" android:src="@mipmap/ic_launcher"/>
android:gravity="top|center"
android:paddingTop="5dp"
android:textSize="28dp"
android:textAllCaps="true"
android:textColor="@color/white"
app:drawableTint="@color/defaultBrand" />
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="10dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"> android:layout_weight="1">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textSize="@dimen/note_font_size_item_title"
android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
android:maxLines="1" android:maxLines="1"
android:lines="1" android:lines="1"
android:ellipsize="middle" android:ellipsize="end"
android:textColor="@color/text_color"
android:textSize="@dimen/two_line_primary_text_size"
tools:text="@tools:sample/lorem/random"> tools:text="@tools:sample/lorem/random">
</TextView> </TextView>
<LinearLayout <TextView
android:id="@+id/content"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:layout_marginTop="5dp"
android:textSize="@dimen/note_font_size_item_content"
<TextView android:maxLines="2"
android:id="@+id/content" android:gravity="center_vertical"
android:layout_width="0dp" tools:text="@tools:sample/lorem/random">
android:layout_height="wrap_content" </TextView>
android:layout_weight="1"
android:layout_marginEnd="10dp"
android:textColor="@color/secondary_text_color"
android:textSize="@dimen/two_line_secondary_text_size"
android:maxLines="1"
android:lines="1"
android:ellipsize="end"
android:gravity="center_vertical"
tools:text="@tools:sample/lorem/random">
</TextView>
<TextView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:textColor="@color/secondary_text_color"
android:textSize="@dimen/two_line_secondary_text_size"
android:maxLines="1"
android:lines="1"
android:gravity="center_vertical"
tools:text="00/00/0000">
</TextView>
</LinearLayout>
</LinearLayout> </LinearLayout>
<ImageView <ImageView
android:id="@+id/geofav_nav_bt" android:id="@+id/geofav_share_bt"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0" android:layout_weight="0"
android:padding="8dp" android:padding="10dp"
android:src="@drawable/ic_nav" android:src="@drawable/ic_share"
app:tint="@color/secondary_text_color" /> <!-- TODO: app:tint is not working --> android:tint="@color/list_text" /> <!-- TODO: app:tint is not working -->
<ImageView <ImageView
android:id="@+id/geofav_context_menu_bt" android:id="@+id/geofav_context_menu_bt"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0" android:layout_weight="0"
android:padding="8dp" android:padding="10dp"
android:src="@drawable/ic_more" android:src="@drawable/ic_more"
app:tint="@color/secondary_text_color" /> <!-- TODO: app:tint is not working --> android:tint="@color/list_text" /> <!-- TODO: app:tint is not working -->
</LinearLayout> </LinearLayout>

View File

@ -18,7 +18,6 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
@ -34,8 +33,7 @@
android:contentDescription="@null" android:contentDescription="@null"
android:focusable="false" android:focusable="false"
android:scaleType="center" android:scaleType="center"
android:src="@drawable/ic_time_grey" android:src="@drawable/ic_time_grey"/>
app:tint="@color/text_color"/>
<TextView <TextView
android:id="@+id/navigationItemLabel" android:id="@+id/navigationItemLabel"

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- <!--
~ Nextcloud Maps Geofavorites for Android ~ Nextcloud Maps Geofavorites for Android
~ ~
@ -14,10 +15,8 @@
~ You should have received a copy of the GNU General Public License ~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp" <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
android:width="24dp" <background android:drawable="@drawable/ic_launcher_background"/>
android:viewportWidth="24" <foreground android:drawable="@drawable/ic_launcher_foreground"/>
android:viewportHeight="24"> </adaptive-icon>
<path android:fillColor="#FFFFFF" android:pathData="M9,5V9H21V5M9,19H21V15H9M9,14H21V10H9M4,9H8V5H4M4,19H8V15H4M4,14H8V10H4V14Z" />
</vector>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Maps Geofavorites for Android
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -16,9 +16,8 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/root" android:id="@+id/root"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minWidth="300dp" android:minWidth="300dp"
@ -58,8 +57,7 @@
android:paddingEnd="@dimen/standard_half_padding" android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding" android:paddingBottom="@dimen/standard_half_padding"
android:contentDescription="@string/menu_item_sort_by_title_a_z" android:contentDescription="@string/menu_item_sort_by_title_a_z"
android:src="@drawable/ic_alphabetical_asc" android:src="@drawable/ic_alphabetical_asc" />
app:tint="@color/selector_item_unselected"/>
<TextView <TextView
android:id="@+id/sortByTitleAscendingText" android:id="@+id/sortByTitleAscendingText"
@ -74,8 +72,7 @@
android:paddingBottom="@dimen/standard_half_padding" android:paddingBottom="@dimen/standard_half_padding"
android:singleLine="true" android:singleLine="true"
android:text="@string/menu_item_sort_by_title_a_z" android:text="@string/menu_item_sort_by_title_a_z"
android:textSize="@dimen/two_line_primary_text_size" android:textSize="@dimen/two_line_primary_text_size"/>
android:textColor="@color/text_color"/>
</TableRow> </TableRow>
@ -95,8 +92,7 @@
android:paddingEnd="@dimen/standard_half_padding" android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding" android:paddingBottom="@dimen/standard_half_padding"
android:contentDescription="@string/menu_item_sort_by_date_newest_first" android:contentDescription="@string/menu_item_sort_by_date_newest_first"
android:src="@drawable/ic_modification_asc" android:src="@drawable/ic_modification_asc"/>
app:tint="@color/selector_item_unselected"/>
<TextView <TextView
android:id="@+id/sortByCreationDateDescendingText" android:id="@+id/sortByCreationDateDescendingText"
@ -111,83 +107,7 @@
android:paddingBottom="@dimen/standard_half_padding" android:paddingBottom="@dimen/standard_half_padding"
android:singleLine="true" android:singleLine="true"
android:text="@string/menu_item_sort_by_date_newest_first" android:text="@string/menu_item_sort_by_date_newest_first"
android:textSize="@dimen/two_line_primary_text_size" android:textSize="@dimen/two_line_primary_text_size" />
android:textColor="@color/selector_item_unselected" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_half_margin">
<ImageButton
android:id="@+id/sortByCategoryAscending"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@color/transparent"
android:paddingStart="@dimen/standard_padding"
android:paddingTop="@dimen/standard_half_padding"
android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:contentDescription="@string/menu_item_sort_by_category_a_z"
android:src="@drawable/ic_category_asc"
app:tint="@color/selector_item_unselected"/>
<TextView
android:id="@+id/sortByCategoryAscendingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="middle"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/standard_half_padding"
android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:singleLine="true"
android:text="@string/menu_item_sort_by_category_a_z"
android:textSize="@dimen/two_line_primary_text_size"
android:textColor="@color/selector_item_unselected" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/standard_half_margin"
android:visibility="gone"> <!-- TODO: complete sorting by distance -->
<ImageButton
android:id="@+id/sortByDistanceAscending"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@color/transparent"
android:paddingStart="@dimen/standard_padding"
android:paddingTop="@dimen/standard_half_padding"
android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:contentDescription="@string/menu_item_sort_by_distance_nearest_first"
android:src="@drawable/ic_distance_asc"
app:tint="@color/selector_item_unselected"/>
<TextView
android:id="@+id/sortByDistanceAscendingText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:ellipsize="middle"
android:paddingStart="@dimen/zero"
android:paddingTop="@dimen/standard_half_padding"
android:paddingEnd="@dimen/standard_half_padding"
android:paddingBottom="@dimen/standard_half_padding"
android:singleLine="true"
android:text="@string/menu_item_sort_by_distance_nearest_first"
android:textSize="@dimen/two_line_primary_text_size"
android:textColor="@color/selector_item_unselected" />
</TableRow> </TableRow>
@ -205,9 +125,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/common_cancel" android:text="@string/common_cancel"
android:textAllCaps="false" android:layout_marginTop="@dimen/standard_half_margin"/>
android:layout_marginTop="@dimen/standard_half_margin"
style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"/>
</LinearLayout> </LinearLayout>

View File

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<!-- Share button --> <!-- Detail button -->
<item <item
android:id="@+id/list_context_menu_share" android:id="@+id/list_context_menu_detail"
android:title="@string/list_context_menu_share"/> android:title="@string/list_context_menu_detail"/>
<!-- Delete button --> <!-- Delete button -->
<item <item
android:id="@+id/list_context_menu_delete" android:id="@+id/list_context_menu_delete"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 962 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -1,112 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Maps Geofavorites for Android
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<!-- App strings -->
<string name="app_name">Maps Geofavorites</string>
<string name="welcome">Benvenuto su Nextcloud Maps Geofavorites</string>
<!-- Login Activity -->
<string name="choose_account">Scegli account</string>
<!-- Geofavorites list -->
<string name="new_geobookmark">Nuovo geosegnalibro</string>
<string name="about">Informazioni</string>
<string name="switch_account">Cambia account</string>
<string name="list_mode">Lista</string>
<string name="search_in_all">Cerca per nome</string>
<string name="share_via">Condividi via</string>
<string name="share_message">{title}: {lat}°N, {lng}°E https://www.openstreetmap.org/#map=17/{lat}/{lng}</string>
<string name="share_message_default_title">Posizione condivisa:</string>
<string name="list_context_menu_share">Condividi</string>
<string name="list_context_menu_delete">Elimina</string>
<string name="dialog_delete_title">Elimina geosegnalibro</string>
<string name="dialog_delete_message">Stai per eliminare il geosegnalibro {name}. Procedere?</string>
<string name="dialog_delete_delete">Elimina</string>
<string name="dialog_delete_cancel">Mantieni</string>
<string name="dialog_logout_title">Cambia account</string>
<string name="dialog_logout_message">Vuoi cambiare account?</string>
<string name="list_geofavorite_deleted">Geosegnalibro eliminato</string>
<string name="list_geofavorite_connection_error">Impossibile ottenere la lista dei geosegnalibri</string>
<string name="filtering_unavailable">Il filtro per categoria non è disponibile al momento</string>
<string name="filtering_dialog_title">Filtra per categoria</string>
<string name="filtering_dialog_cancel">Mostra tutti</string>
<!-- Sort dialog -->
<string name="sort_by">Ordina per</string>
<string name="menu_item_sort_by_title_a_z">A - Z</string>
<string name="menu_item_sort_by_date_newest_first">Più recenti</string>
<string name="menu_item_sort_by_category_a_z">Categoria</string>
<string name="menu_item_sort_by_distance_nearest_first">Distanza</string>
<!-- Geofavorites detail -->
<string name="name">Nome</string>
<string name="description">Descrizione</string>
<string name="created">Creato</string>
<string name="modified">Modificato</string>
<string name="category">Categoria</string>
<string name="coords">Coordinate</string>
<string name="accuracy">Accuratezza: {accuracy} m</string>
<string name="accuracy_nosignal">Nessun segnale GPS!</string>
<string name="location_permission_required">Per creare un geosegnalibro è necessario consentire l\'accesso alla posizione.</string>
<string name="category_hint">Scrivi il nome per creare una nuova categoria</string>
<string name="error_saving_geofavorite">Impossibile salvare il geosegnalibro</string>
<string name="error_unsupported_uri">Impossibile ottenere le coordinate dai dati ricevuti</string>
<string name="geofavorite_saved">Geosegnalibro salvato</string>
<string name="incomplete_geofavorite">Geosegnalibro incompleto: nome e categoria sono obbligatori</string>
<string name="coords_copied">Coordinate copiate nella clipboard</string>
<!-- Map picker activity -->
<string name="coordinates_parse_error">Le coordinate dovrebbero essere nel formato xx.xxxxxx</string>
<string name="coordinates_invalid_error">Coordinate non valide</string>
<string name="coordinates_north">°N</string>
<string name="coordinates_east">°E</string>
<!-- About -->
<string name="about_version_title">Versione</string>
<string name="about_version">Stai usando la versione &lt;strong>%1$s&lt;/strong></string>
<string name="about_source_title">Codice sorgente</string>
<string name="about_source">Questo progetto è disponibile su GitHub: &lt;a href="%1$s">%1$s&lt;/a></string>
<string name="about_issues_title">Segnalazioni</string>
<string name="about_issues">Puoi segnalare bugs, proporre migliorie o richiedere nuove funzionalità sul GitHub issue tracker: &lt;a href="%1$s">%1$s&lt;/a></string>
<string name="about_maps_title">Mappe</string>
<string name="about_maps">Questa app usa i server e le tiles di OpenStreetMap. Non accetto donazioni per questa app, ma raccomando piuttosto di donare a &lt;a href="%1$s">OpenStreetMap&lt;/a>, poiché questa applicazione non potrebbe esistere senza di loro.</string>
<string name="about_app_license_title">Licenza</string>
<string name="about_app_license">Questa applicazione è rilasciata sotto licenza GNU GENERAL PUBLIC LICENSE v3+.</string>
<string name="about_app_license_button">Mostra licenza</string>
<!-- Common strings -->
<string name="common_yes">Si</string>
<string name="common_cancel">Annulla</string>
<!-- URLs -->
<string name="url_source" translatable="false">https://github.com/penguin86/nextcloud-maps-client</string>
<string name="url_issues" translatable="false">https://github.com/penguin86/nextcloud-maps-client/issues</string>
<string name="url_license" translatable="false">https://raw.githubusercontent.com/penguin86/nextcloud-maps-client/master/LICENSE</string>
<string name="url_maps" translatable="false">https://donate.openstreetmap.org</string>
<!-- Menu -->
<string name="new_geobookmark_gps">Crea dalla posizione corrente</string>
<string name="new_geobookmark_map">Crea dalla mappa</string>
<!-- Accessibility (content descriptions) -->
<string name="open_fab">Aggiungi</string>
<string name="add_from_gps">Crea dalla posizione corrente</string>
<string name="add_from_map">Crea dalla mappa</string>
</resources>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text_color">#eee</color>
<color name="disabled">#888</color>
<color name="defaultBackground">#000</color>
<color name="translucent">#C000</color>
<!-- Toolbar -->
<color name="toolbar_background">#211e28</color>
</resources>

View File

@ -19,7 +19,7 @@
<resources> <resources>
<style name="AppTheme" parent="BaseTheme"> <style name="AppTheme" parent="BaseTheme">
<item name="android:statusBarColor">?android:attr/colorPrimary</item> <item name="android:statusBarColor">?attr/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
</style> </style>

View File

@ -19,9 +19,9 @@
<resources> <resources>
<style name="AppTheme" parent="BaseTheme"> <style name="AppTheme" parent="BaseTheme">
<item name="android:statusBarColor">?android:attr/colorPrimary</item> <item name="android:statusBarColor">?attr/colorPrimary</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>
<item name="android:navigationBarColor">?android:attr/colorPrimary</item> <item name="android:navigationBarColor">?attr/colorPrimary</item>
<item name="android:windowLightNavigationBar">true</item> <item name="android:windowLightNavigationBar">true</item>
</style> </style>

Some files were not shown because too many files have changed in this diff Show More