Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
e64b89bce6 | |||
fd14eea4b9 | |||
065ef2bea6 | |||
308474246b | |||
96b88a398c | |||
15cea67818 | |||
9e89879d5c | |||
a0c397b194 | |||
cdb146bffb | |||
7ac35f21cf | |||
760f7f572a | |||
9aa2652059 | |||
718c654056 | |||
63b60a46ae | |||
a694246a56 | |||
8b7e89f43b | |||
cf7f96efad | |||
b9dbde7f7c | |||
4592bb3382 | |||
88cf7711b0 | |||
82a5eb343b |
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -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="11" />
|
<bytecodeTargetLevel target="1.8" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
6
.idea/misc.xml
generated
6
.idea/misc.xml
generated
@ -4,19 +4,21 @@
|
|||||||
<option name="filePathToZoomLevelMap">
|
<option name="filePathToZoomLevelMap">
|
||||||
<map>
|
<map>
|
||||||
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
|
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
|
||||||
|
<entry key="app/src/main/res/drawable/category_listitem_background.xml" value="0.35104166666666664" />
|
||||||
<entry key="app/src/main/res/drawable/floating_semitransparent_button_background.xml" value="0.512962962962963" />
|
<entry key="app/src/main/res/drawable/floating_semitransparent_button_background.xml" value="0.512962962962963" />
|
||||||
<entry key="app/src/main/res/drawable/ic_map_pin.xml" value="0.6425925925925926" />
|
<entry key="app/src/main/res/drawable/ic_map_pin.xml" value="0.6425925925925926" />
|
||||||
<entry key="app/src/main/res/drawable/ic_more.xml" value="0.6166666666666667" />
|
<entry key="app/src/main/res/drawable/ic_more.xml" value="0.6166666666666667" />
|
||||||
<entry key="app/src/main/res/drawable/ic_nav.xml" value="0.6083333333333333" />
|
<entry key="app/src/main/res/drawable/ic_nav.xml" value="0.6083333333333333" />
|
||||||
<entry key="app/src/main/res/drawable/ic_share.xml" value="0.8828125" />
|
<entry key="app/src/main/res/drawable/ic_share.xml" value="0.8828125" />
|
||||||
<entry key="app/src/main/res/layout/activity_geofavorite_detail.xml" value="0.4" />
|
<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_list_view.xml" value="0.4" />
|
||||||
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5307291666666667" />
|
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5307291666666667" />
|
||||||
<entry key="app/src/main/res/layout/item_geofav.xml" value="0.5307291666666667" />
|
<entry key="app/src/main/res/layout/item_geofav.xml" value="0.5307291666666667" />
|
||||||
|
<entry key="app/src/main/res/layout/sorting_order_fragment.xml" value="0.4740740740740741" />
|
||||||
<entry key="app/src/main/res/menu/list_context_menu.xml" value="0.41944444444444445" />
|
<entry key="app/src/main/res/menu/list_context_menu.xml" value="0.41944444444444445" />
|
||||||
</map>
|
</map>
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="11" project-jdk-type="JavaSDK" />
|
||||||
</project>
|
</project>
|
@ -2,6 +2,12 @@
|
|||||||
|
|
||||||
# Nextcloud Maps Geobookmarks 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)
|
||||||
|
[<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)
|
||||||
|
[<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).
|
||||||
|
|
||||||
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...).
|
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...).
|
||||||
A new geobookmark can be created on current location.
|
A new geobookmark can be created on current location.
|
||||||
|
|
||||||
@ -11,5 +17,3 @@ This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://g
|
|||||||
|
|
||||||
 
|
 
|
||||||
|
|
||||||
Download it from [the releases page](https://github.com/penguin86/nextcloud-maps-client/releases)
|
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ android {
|
|||||||
applicationId "it.danieleverducci.nextcloudmaps"
|
applicationId "it.danieleverducci.nextcloudmaps"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 3
|
versionCode 5
|
||||||
versionName "0.3.1"
|
versionName "0.3.3"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@
|
|||||||
{
|
{
|
||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"versionCode": 2,
|
"versionCode": 3,
|
||||||
"versionName": "0.2",
|
"versionName": "0.3.1",
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -50,7 +50,17 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.detail.GeofavoriteDetailActivity"
|
android:name=".activity.detail.GeofavoriteDetailActivity"
|
||||||
android:theme="@style/AppTheme"/>
|
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>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activity.about.AboutActivity"
|
android:name=".activity.about.AboutActivity"
|
||||||
|
@ -62,6 +62,10 @@ public class AboutActivity extends AppCompatActivity {
|
|||||||
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
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package it.danieleverducci.nextcloudmaps.activity.detail;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
public class CategoriesSpinnerAdapter extends ArrayAdapter<String> {
|
||||||
|
|
||||||
|
public CategoriesSpinnerAdapter(@NonNull Context context) {
|
||||||
|
super(context, android.R.layout.simple_dropdown_item_1line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: implement colors
|
||||||
|
|
||||||
|
public void setCategoriesList(HashSet<String> categories) {
|
||||||
|
clear();
|
||||||
|
addAll(categories);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -37,6 +37,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.appcompat.app.AppCompatActivity;
|
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;
|
||||||
@ -48,12 +50,16 @@ import org.threeten.bp.format.DateTimeFormatter;
|
|||||||
import org.threeten.bp.format.FormatStyle;
|
import org.threeten.bp.format.FormatStyle;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import it.danieleverducci.nextcloudmaps.R;
|
import it.danieleverducci.nextcloudmaps.R;
|
||||||
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
|
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
|
||||||
|
import it.danieleverducci.nextcloudmaps.activity.main.MainActivityViewModel;
|
||||||
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
|
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.GeoUriParser;
|
||||||
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
|
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.Callback;
|
import retrofit2.Callback;
|
||||||
@ -62,13 +68,13 @@ import retrofit2.Response;
|
|||||||
public class GeofavoriteDetailActivity extends AppCompatActivity 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 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) {
|
||||||
@ -108,24 +114,66 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMapClicked() {
|
public void onMapClicked() {
|
||||||
Toast.makeText(GeofavoriteDetailActivity.this, "TODO: Open map activity with pin", Toast.LENGTH_SHORT).show();
|
// TODO: Open map activity with pin
|
||||||
|
startActivity(IntentGenerator.newGeoUriIntent(GeofavoriteDetailActivity.this, mGeofavorite));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (getIntent().hasExtra(ARG_GEOFAVORITE)) {
|
mViewModel = new ViewModelProvider(this).get(GeofavoriteDetailActivityViewModel.class);
|
||||||
|
mViewModel.init();
|
||||||
|
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 = (Geofavorite) getIntent().getSerializableExtra(ARG_GEOFAVORITE);
|
mGeofavorite = mViewModel.getGeofavorite(
|
||||||
|
getIntent().getIntExtra(ARG_GEOFAVORITE, 0)
|
||||||
|
);
|
||||||
mViewHolder.hideAccuracy();
|
mViewHolder.hideAccuracy();
|
||||||
} else {
|
} else {
|
||||||
// New geofavorite
|
// New geofavorite
|
||||||
mGeofavorite = new Geofavorite();
|
mGeofavorite = new Geofavorite();
|
||||||
mGeofavorite.setCategory(DEFAULT_CATEGORY);
|
mGeofavorite.setCategory(Geofavorite.DEFAULT_CATEGORY);
|
||||||
mGeofavorite.setDateCreated(System.currentTimeMillis());
|
mGeofavorite.setDateCreated(System.currentTimeMillis() / 1000);
|
||||||
mGeofavorite.setDateModified(System.currentTimeMillis());
|
mGeofavorite.setDateModified(System.currentTimeMillis() / 1000);
|
||||||
mViewHolder.hideActions();
|
mViewHolder.hideActions();
|
||||||
|
|
||||||
// Precompile location
|
if (getIntent().getData() != null) {
|
||||||
getLocation();
|
// Opened by external generic intent: parse URI
|
||||||
|
try {
|
||||||
|
double[] coords = GeoUriParser.parseUri(getIntent().getData());
|
||||||
|
mGeofavorite.setLat(coords[0]);
|
||||||
|
mGeofavorite.setLng(coords[1]);
|
||||||
|
mViewHolder.hideAccuracy();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
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);
|
||||||
@ -169,35 +217,7 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Call<Geofavorite> call;
|
mViewModel.saveGeofavorite(mGeofavorite);
|
||||||
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()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -281,7 +301,9 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
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);
|
||||||
@ -290,9 +312,15 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
this.binding.actionIconDelete.setOnClickListener(this);
|
this.binding.actionIconDelete.setOnClickListener(this);
|
||||||
this.binding.actionIconNav.setOnClickListener(this);
|
this.binding.actionIconNav.setOnClickListener(this);
|
||||||
|
|
||||||
|
// Set categories adapter
|
||||||
|
CategoriesSpinnerAdapter categoriesAdapter = new CategoriesSpinnerAdapter(binding.root.getContext());
|
||||||
|
this.binding.categoryAt.setAdapter(categoriesAdapter);
|
||||||
|
this.binding.categoryAt.setText(Geofavorite.DEFAULT_CATEGORY);
|
||||||
|
|
||||||
// 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);
|
||||||
|
// this.binding.map.setTilesScaledToDpi(true);
|
||||||
|
|
||||||
// Create marker
|
// Create marker
|
||||||
mapMarker = new Marker(binding.map);
|
mapMarker = new Marker(binding.map);
|
||||||
@ -311,7 +339,7 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
binding.descriptionEt.setText(item.getComment());
|
binding.descriptionEt.setText(item.getComment());
|
||||||
binding.createdTv.setText(item.getLocalDateCreated().format(dateFormatter));
|
binding.createdTv.setText(item.getLocalDateCreated().format(dateFormatter));
|
||||||
binding.modifiedTv.setText(item.getLocalDateCreated().format(dateFormatter));
|
binding.modifiedTv.setText(item.getLocalDateCreated().format(dateFormatter));
|
||||||
binding.categoryTv.setText(item.getCategory()); // TODO: Category spinner from existing categories
|
binding.categoryAt.setText(item.getCategory());
|
||||||
updateViewCoords(item);
|
updateViewCoords(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -331,9 +359,14 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
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) {
|
||||||
binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + ""));
|
binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + ""));
|
||||||
// Color the accuracy background with a scale from red (MINIMUM_ACCEPTABLE_ACCURACY) to green (0 meters)
|
// Color the accuracy background with a scale from red (MINIMUM_ACCEPTABLE_ACCURACY) to green (0 meters)
|
||||||
@ -344,6 +377,10 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
|
|||||||
binding.accuracyTv.setBackgroundColor(Color.rgb(red, green, 0.0f));
|
binding.accuracyTv.setBackgroundColor(Color.rgb(red, green, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setCategories(HashSet<String> categories) {
|
||||||
|
((CategoriesSpinnerAdapter)binding.categoryAt.getAdapter()).setCategoriesList(categories);
|
||||||
|
}
|
||||||
|
|
||||||
public void hideAccuracy() {
|
public void hideAccuracy() {
|
||||||
binding.accuracyTv.setVisibility(View.GONE);
|
binding.accuracyTv.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
package it.danieleverducci.nextcloudmaps.activity.detail;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
mRepo = GeofavoriteRepository.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -21,6 +21,7 @@
|
|||||||
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.text.Html;
|
import android.text.Html;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
@ -34,6 +35,7 @@ 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.DateTimeFormatter;
|
||||||
@ -53,6 +55,8 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<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 Context context;
|
private Context context;
|
||||||
private ItemClickListener itemClickListener;
|
private ItemClickListener itemClickListener;
|
||||||
@ -82,17 +86,6 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
|
|||||||
return geofavoriteListFiltered.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() {
|
||||||
return sortRule;
|
return sortRule;
|
||||||
}
|
}
|
||||||
@ -115,6 +108,9 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
|
|||||||
public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
|
||||||
Geofavorite geofavorite = geofavoriteListFiltered.get(position);
|
Geofavorite geofavorite = geofavoriteListFiltered.get(position);
|
||||||
|
|
||||||
|
holder.tv_category.setText(geofavorite.categoryLetter());
|
||||||
|
holder.tv_category_background.setTint(
|
||||||
|
geofavorite.categoryColor() == 0 ? context.getColor(R.color.defaultBrand) : geofavorite.categoryColor());
|
||||||
holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
|
holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
|
||||||
holder.tv_content.setText(geofavorite.getComment());
|
holder.tv_content.setText(geofavorite.getComment());
|
||||||
holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter));
|
holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter));
|
||||||
@ -165,9 +161,10 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
|
|||||||
};
|
};
|
||||||
|
|
||||||
class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||||
TextView tv_title, tv_content, tv_date;
|
TextView tv_category, tv_title, tv_content, tv_date;
|
||||||
ImageView bt_context_menu;
|
ImageView bt_context_menu;
|
||||||
ImageView bt_nav;
|
ImageView bt_nav;
|
||||||
|
Drawable tv_category_background;
|
||||||
|
|
||||||
ItemClickListener itemClickListener;
|
ItemClickListener itemClickListener;
|
||||||
|
|
||||||
@ -175,11 +172,13 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
|
|||||||
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
|
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
|
||||||
super(itemView);
|
super(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);
|
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_nav = itemView.findViewById(R.id.geofav_nav_bt);
|
||||||
|
tv_category_background = DrawableCompat.wrap(tv_category.getBackground());
|
||||||
|
|
||||||
this.itemClickListener = itemClickListener;
|
this.itemClickListener = itemClickListener;
|
||||||
itemView.setOnClickListener(this);
|
itemView.setOnClickListener(this);
|
||||||
@ -210,6 +209,10 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
|
|||||||
Collections.sort(geofavoriteListFiltered, Geofavorite.ByTitleAZ);
|
Collections.sort(geofavoriteListFiltered, Geofavorite.ByTitleAZ);
|
||||||
} else if (sortRule == SORT_BY_CREATED) {
|
} else if (sortRule == SORT_BY_CREATED) {
|
||||||
Collections.sort(geofavoriteListFiltered, Geofavorite.ByLastCreated);
|
Collections.sort(geofavoriteListFiltered, Geofavorite.ByLastCreated);
|
||||||
|
} else if (sortRule == SORT_BY_CATEGORY) {
|
||||||
|
Collections.sort(geofavoriteListFiltered, Geofavorite.ByCategory);
|
||||||
|
} else if (sortRule == SORT_BY_DISTANCE) {
|
||||||
|
Collections.sort(geofavoriteListFiltered, Geofavorite.ByDistance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import android.text.TextUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.appcompat.widget.AppCompatImageButton;
|
import androidx.appcompat.widget.AppCompatImageButton;
|
||||||
@ -35,6 +36,7 @@ import androidx.core.view.GravityCompat;
|
|||||||
import androidx.drawerlayout.widget.DrawerLayout;
|
import androidx.drawerlayout.widget.DrawerLayout;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||||
@ -54,6 +56,7 @@ 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.main.SortingOrderDialogFragment.OnSortingOrderListener;
|
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener;
|
||||||
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
|
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
|
||||||
|
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
|
||||||
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
|
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
|
||||||
|
|
||||||
import static android.view.View.GONE;
|
import static android.view.View.GONE;
|
||||||
@ -61,11 +64,9 @@ import static android.view.View.VISIBLE;
|
|||||||
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.*;
|
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_CREATED;
|
||||||
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
|
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements MainView, OnSortingOrderListener {
|
public class MainActivity extends AppCompatActivity implements OnSortingOrderListener {
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
@ -84,9 +85,9 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
private StaggeredGridLayoutManager layoutManager;
|
private StaggeredGridLayoutManager layoutManager;
|
||||||
private FloatingActionButton fab;
|
private FloatingActionButton fab;
|
||||||
|
|
||||||
private MainPresenter presenter;
|
|
||||||
private GeofavoriteAdapter geofavoriteAdapter;
|
private GeofavoriteAdapter geofavoriteAdapter;
|
||||||
private ItemClickListener rvItemClickListener;
|
private ItemClickListener rvItemClickListener;
|
||||||
|
private MainActivityViewModel mMainActivityViewModel;
|
||||||
|
|
||||||
NavigationAdapter navigationCommonAdapter;
|
NavigationAdapter navigationCommonAdapter;
|
||||||
|
|
||||||
@ -104,8 +105,6 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
layoutManager = new StaggeredGridLayoutManager(gridViewEnabled ? 2 : 1, StaggeredGridLayoutManager.VERTICAL);
|
layoutManager = new StaggeredGridLayoutManager(gridViewEnabled ? 2 : 1, StaggeredGridLayoutManager.VERTICAL);
|
||||||
recyclerView.setLayoutManager(layoutManager);
|
recyclerView.setLayoutManager(layoutManager);
|
||||||
|
|
||||||
presenter = new MainPresenter(this);
|
|
||||||
|
|
||||||
rvItemClickListener = new ItemClickListener() {
|
rvItemClickListener = new ItemClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(Geofavorite item) {
|
public void onItemClick(Geofavorite item) {
|
||||||
@ -132,8 +131,39 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
recyclerView.setAdapter(geofavoriteAdapter);
|
recyclerView.setAdapter(geofavoriteAdapter);
|
||||||
geofavoriteAdapter.setSortRule(sortRule);
|
geofavoriteAdapter.setSortRule(sortRule);
|
||||||
|
|
||||||
|
|
||||||
|
mMainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
|
||||||
|
mMainActivityViewModel.init();
|
||||||
|
mMainActivityViewModel.getIsUpdating().observe(this, new Observer<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable Boolean aBoolean) {
|
||||||
|
if(aBoolean){
|
||||||
|
swipeRefresh.setRefreshing(true);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
swipeRefresh.setRefreshing(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mMainActivityViewModel.getOnFinished().observe(this, new Observer<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(@Nullable Boolean success) {
|
||||||
|
if(!success){
|
||||||
|
Toast.makeText(MainActivity.this, R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mMainActivityViewModel.getGeofavorites().observe(this, new Observer<List<Geofavorite>>() {
|
||||||
|
@Override
|
||||||
|
public void onChanged(List<Geofavorite> geofavorites) {
|
||||||
|
geofavoriteAdapter.setGeofavoriteList(geofavorites);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mMainActivityViewModel.updateGeofavorites();
|
||||||
|
|
||||||
swipeRefresh = findViewById(R.id.swipe_refresh);
|
swipeRefresh = findViewById(R.id.swipe_refresh);
|
||||||
swipeRefresh.setOnRefreshListener(() -> presenter.getGeofavorites());
|
swipeRefresh.setOnRefreshListener(() ->
|
||||||
|
mMainActivityViewModel.updateGeofavorites());
|
||||||
|
|
||||||
fab = findViewById(R.id.add);
|
fab = findViewById(R.id.add);
|
||||||
fab.setOnClickListener(view -> addGeofavorite());
|
fab.setOnClickListener(view -> addGeofavorite());
|
||||||
@ -185,14 +215,6 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
updateGridIcon(gridViewEnabled);
|
updateGridIcon(gridViewEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onStart() {
|
|
||||||
super.onStart();
|
|
||||||
|
|
||||||
// Update list
|
|
||||||
presenter.getGeofavorites();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupNavigationMenu() {
|
private void setupNavigationMenu() {
|
||||||
ArrayList<NavigationItem> navItems = new ArrayList<>();
|
ArrayList<NavigationItem> navItems = new ArrayList<>();
|
||||||
|
|
||||||
@ -235,19 +257,10 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
SortingOrderDialogFragment.newInstance(sortOrder).show(fragmentTransaction, SortingOrderDialogFragment.SORTING_ORDER_FRAGMENT);
|
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() {
|
private void addGeofavorite() {
|
||||||
startActivityForResult(
|
startActivity(
|
||||||
new Intent(this, GeofavoriteDetailActivity.class), INTENT_ADD);
|
new Intent(this, GeofavoriteDetailActivity.class)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show_about() {
|
private void show_about() {
|
||||||
@ -260,36 +273,6 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showLoading() {
|
|
||||||
swipeRefresh.setRefreshing(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void hideLoading() {
|
|
||||||
swipeRefresh.setRefreshing(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGetResult(List<Geofavorite> geofavorite_list) {
|
|
||||||
geofavoriteAdapter.setGeofavoriteList(geofavorite_list);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onGeofavoriteDeleted(int id) {
|
|
||||||
// Update list
|
|
||||||
runOnUiThread(() -> {
|
|
||||||
geofavoriteAdapter.removeById(id);
|
|
||||||
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
|
@Override
|
||||||
public void onSortingOrderChosen(int sortSelection) {
|
public void onSortingOrderChosen(int sortSelection) {
|
||||||
geofavoriteAdapter.setSortRule(sortSelection);
|
geofavoriteAdapter.setSortRule(sortSelection);
|
||||||
@ -307,6 +290,12 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,9 +317,8 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
.setTitle(R.string.dialog_delete_title)
|
.setTitle(R.string.dialog_delete_title)
|
||||||
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
|
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
|
||||||
public void onClick(DialogInterface dialog, int id) {
|
public void onClick(DialogInterface dialog, int id) {
|
||||||
presenter.deleteGeofavorite(item.getId());
|
mMainActivityViewModel.deleteGeofavorite(item);
|
||||||
dialog.dismiss();
|
dialog.dismiss();
|
||||||
// Callback is onGeofavoriteDeleted
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
|
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
|
||||||
@ -344,7 +332,7 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
|
|||||||
|
|
||||||
private void showGeofavoriteDetailActivity(Geofavorite item) {
|
private void showGeofavoriteDetailActivity(Geofavorite item) {
|
||||||
Intent i = new Intent(this, GeofavoriteDetailActivity.class);
|
Intent i = new Intent(this, GeofavoriteDetailActivity.class);
|
||||||
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item);
|
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item.getId());
|
||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,40 @@
|
|||||||
|
package it.danieleverducci.nextcloudmaps.activity.main;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
|
||||||
|
import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
|
||||||
|
|
||||||
|
public class MainActivityViewModel extends ViewModel {
|
||||||
|
private GeofavoriteRepository mRepo;
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
mRepo = GeofavoriteRepository.getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Geofavorite>> getGeofavorites(){
|
||||||
|
mRepo.updateGeofavorites();
|
||||||
|
return mRepo.getGeofavorites();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateGeofavorites() {
|
||||||
|
mRepo.updateGeofavorites();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteGeofavorite(Geofavorite geofav) {
|
||||||
|
mRepo.deleteGeofavorite(geofav);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> getIsUpdating(){
|
||||||
|
return mRepo.isUpdating();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> getOnFinished(){
|
||||||
|
return mRepo.onFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,33 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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);
|
|
||||||
}
|
|
@ -91,13 +91,14 @@ public class SortingOrderDialogFragment extends DialogFragment {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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[4];
|
mTaggedViews = new View[8];
|
||||||
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);
|
||||||
@ -106,6 +107,14 @@ 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();
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,11 @@
|
|||||||
|
|
||||||
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.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;
|
||||||
@ -38,9 +35,11 @@ import org.threeten.bp.ZoneId;
|
|||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
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:
|
||||||
* {
|
* {
|
||||||
@ -150,18 +149,72 @@ public class Geofavorite implements Serializable {
|
|||||||
this.comment = comment;
|
this.comment = comment;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name);
|
/**
|
||||||
public static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id;
|
* Comparators for list order
|
||||||
|
*/
|
||||||
|
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 Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")");
|
return Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean valid() {
|
public boolean valid() {
|
||||||
return getLat() != 0 && getLng() != 0 && getName() != null && getName().length() > 0;
|
return
|
||||||
|
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() {
|
||||||
|
// If category is default, return null: will be used Nextcloud's accent
|
||||||
|
if (this.category.equals(DEFAULT_CATEGORY))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
float letter1Index = this.category.toLowerCase().charAt(0);
|
||||||
|
float letter2Index = this.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) });
|
||||||
|
}
|
||||||
|
|
||||||
|
public String categoryLetter() {
|
||||||
|
if (category == null || category.length() == 0)
|
||||||
|
return "";
|
||||||
|
if (category.equals(DEFAULT_CATEGORY))
|
||||||
|
return "\u2022";
|
||||||
|
return category.substring(0,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -169,4 +222,5 @@ public class Geofavorite implements Serializable {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "[" + getName() + " (" + getLat() + "," + getLng() + ")]";
|
return "[" + getName() + " (" + getLat() + "," + getLng() + ")]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,164 @@
|
|||||||
|
package it.danieleverducci.nextcloudmaps.repository;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
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<>();
|
||||||
|
|
||||||
|
public static GeofavoriteRepository getInstance() {
|
||||||
|
if(instance == null){
|
||||||
|
instance = new GeofavoriteRepository();
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
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().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().createGeofavorite(geofav);
|
||||||
|
} else {
|
||||||
|
// Update existing geofavorite
|
||||||
|
call = ApiProvider.getAPI().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().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) {
|
||||||
|
categories.add(g.getCategory());
|
||||||
|
}
|
||||||
|
mCategories.postValue(categories);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
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.]+)");
|
||||||
|
|
||||||
|
public static double[] parseUri(Uri uri) throws IllegalArgumentException {
|
||||||
|
if (uri == null)
|
||||||
|
throw new IllegalArgumentException("no uri");
|
||||||
|
|
||||||
|
Matcher m = PATTERN_GEO.matcher(uri.toString());
|
||||||
|
if (m.find() && m.groupCount() == 2) {
|
||||||
|
String sLat = m.group(1);
|
||||||
|
String sLon = m.group(2);
|
||||||
|
try {
|
||||||
|
return new double[]{Double.parseDouble(sLat), Double.parseDouble(sLon)};
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
throw new IllegalArgumentException("unable to parse uri");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("unable to parse uri");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
13
app/src/main/res/drawable/category_listitem_background.xml
Normal file
13
app/src/main/res/drawable/category_listitem_background.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item
|
||||||
|
android:left="8dp"
|
||||||
|
android:right="8dp"
|
||||||
|
android:top="8dp"
|
||||||
|
android:bottom="8dp">
|
||||||
|
<shape
|
||||||
|
android:shape="oval">
|
||||||
|
<solid android:color="@color/defaultBrand"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
16
app/src/main/res/drawable/ic_category_asc.xml
Normal file
16
app/src/main/res/drawable/ic_category_asc.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?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>
|
10
app/src/main/res/drawable/ic_distance_asc.xml
Normal file
10
app/src/main/res/drawable/ic_distance_asc.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/colorControlNormal">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,3L3,10.53v0.98l6.84,2.65L12.48,21h0.98L21,3z"/>
|
||||||
|
</vector>
|
@ -1,10 +1,5 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
android:width="24dp"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:height="24dp"
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:viewportWidth="24"
|
<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"/>
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<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>
|
</vector>
|
||||||
|
@ -115,6 +115,20 @@
|
|||||||
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>
|
||||||
|
@ -138,7 +138,8 @@
|
|||||||
android:textAppearance="@style/TextAppearance.AppCompat.Title"
|
android:textAppearance="@style/TextAppearance.AppCompat.Title"
|
||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:drawableLeft="@drawable/ic_edit"
|
android:drawableLeft="@drawable/ic_edit"
|
||||||
android:drawablePadding="5dp"/>
|
android:drawablePadding="5dp"
|
||||||
|
android:drawableTint="@color/defaultBrand"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/description_et"
|
android:id="@+id/description_et"
|
||||||
@ -152,8 +153,24 @@
|
|||||||
android:background="@android:color/transparent"
|
android:background="@android:color/transparent"
|
||||||
android:drawableLeft="@drawable/ic_edit"
|
android:drawableLeft="@drawable/ic_edit"
|
||||||
android:drawablePadding="5dp"
|
android:drawablePadding="5dp"
|
||||||
|
android:drawableTint="@color/defaultBrand"
|
||||||
android:hint="@string/description"/>
|
android:hint="@string/description"/>
|
||||||
|
|
||||||
|
<AutoCompleteTextView
|
||||||
|
android:id="@+id/category_at"
|
||||||
|
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_category_asc"
|
||||||
|
android:drawablePadding="5dp"
|
||||||
|
android:drawableTint="@color/defaultBrand"
|
||||||
|
android:hint="@string/category"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -180,19 +197,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAlignment="textEnd" />
|
android:textAlignment="textEnd" />
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:text="@string/category" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/category_tv"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
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"
|
||||||
@ -206,6 +210,13 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAlignment="textEnd" />
|
android:textAlignment="textEnd" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/submit_bt"
|
android:id="@+id/submit_bt"
|
||||||
style="@style/Widget.AppCompat.Button.Colored"
|
style="@style/Widget.AppCompat.Button.Colored"
|
||||||
|
@ -26,19 +26,23 @@
|
|||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true">
|
android:focusable="true">
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:layout_width="32dp"
|
android:id="@+id/tv_category"
|
||||||
android:layout_height="32dp"
|
android:layout_width="64dp"
|
||||||
android:layout_marginStart="12dp"
|
android:layout_height="64dp"
|
||||||
android:contentDescription="@null"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_weight="0"
|
android:layout_weight="0"
|
||||||
android:src="@mipmap/ic_launcher"/>
|
android:background="@drawable/category_listitem_background"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="28dp"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
<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="16dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
@ -111,6 +111,77 @@
|
|||||||
|
|
||||||
</TableRow>
|
</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"/>
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
</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"/>
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
</TableRow>
|
||||||
|
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
@ -44,7 +44,8 @@
|
|||||||
<string name="sort_by">Sort by</string>
|
<string name="sort_by">Sort by</string>
|
||||||
<string name="menu_item_sort_by_title_a_z">A - Z</string>
|
<string name="menu_item_sort_by_title_a_z">A - Z</string>
|
||||||
<string name="menu_item_sort_by_date_newest_first">Newest first</string>
|
<string name="menu_item_sort_by_date_newest_first">Newest first</string>
|
||||||
<string name="menu_item_sort_by_distance">Distance</string>
|
<string name="menu_item_sort_by_category_a_z">Category</string>
|
||||||
|
<string name="menu_item_sort_by_distance_nearest_first">Distance</string>
|
||||||
|
|
||||||
<!-- Geobookmarks detail -->
|
<!-- Geobookmarks detail -->
|
||||||
<string name="name">Name</string>
|
<string name="name">Name</string>
|
||||||
@ -57,7 +58,9 @@
|
|||||||
<string name="location_permission_required">Location permission is required to create a geofavorite.</string>
|
<string name="location_permission_required">Location permission is required to create a geofavorite.</string>
|
||||||
<string name="confirm">Save</string>
|
<string name="confirm">Save</string>
|
||||||
<string name="error_saving_geofavorite">Unable to save geofavorite</string>
|
<string name="error_saving_geofavorite">Unable to save geofavorite</string>
|
||||||
<string name="incomplete_geofavorite">Incomplete geofavorite: Name and GPS coordinates are mandatory</string>
|
<string name="error_unsupported_uri">Unable to obtain coordinates from shared data</string>
|
||||||
|
<string name="geofavorite_saved">Geofavorite saved</string>
|
||||||
|
<string name="incomplete_geofavorite">Incomplete geofavorite: Name and category are mandatory</string>
|
||||||
|
|
||||||
<!-- About -->
|
<!-- About -->
|
||||||
<string name="about_version_title">Version</string>
|
<string name="about_version_title">Version</string>
|
||||||
@ -66,6 +69,8 @@
|
|||||||
<string name="about_source">This project is hosted on GitHub: <a href="%1$s">%1$s</a></string>
|
<string name="about_source">This project is hosted on GitHub: <a href="%1$s">%1$s</a></string>
|
||||||
<string name="about_issues_title">Issues</string>
|
<string name="about_issues_title">Issues</string>
|
||||||
<string name="about_issues">You can report bugs, enhancement proposals and feature requests at the GitHub issue tracker: <a href="%1$s">%1$s</a></string>
|
<string name="about_issues">You can report bugs, enhancement proposals and feature requests at the GitHub issue tracker: <a href="%1$s">%1$s</a></string>
|
||||||
|
<string name="about_maps_title">Maps</string>
|
||||||
|
<string name="about_maps">This app uses Open Street Maps tiles and servers to display the map. I do not accept any donation for this app, but strongly encourage to make any donation to <a href="%1$s">OpenStreetMap</a>, as this application could not exist without them.</string>
|
||||||
<string name="about_app_license_title">App license</string>
|
<string name="about_app_license_title">App license</string>
|
||||||
<string name="about_app_license">This application is licensed under the GNU GENERAL PUBLIC LICENSE v3+.</string>
|
<string name="about_app_license">This application is licensed under the GNU GENERAL PUBLIC LICENSE v3+.</string>
|
||||||
<string name="about_app_license_button">View license</string>
|
<string name="about_app_license_button">View license</string>
|
||||||
@ -76,8 +81,9 @@
|
|||||||
|
|
||||||
<!-- URLs -->
|
<!-- URLs -->
|
||||||
<string name="url_source" translatable="false">https://github.com/penguin86/nextcloud-maps-client</string>
|
<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/new/choose</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_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>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- Settings -->
|
||||||
<string name="setting_sort_by">SETTING_SORT_BY</string>
|
<string name="setting_sort_by">SETTING_SORT_BY</string>
|
||||||
|
1
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/2.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Added geofavorite detail and deletion
|
1
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Added map in geofavorite detail
|
1
fastlane/metadata/android/en-US/changelogs/4.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/4.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
First categories implementation
|
1
fastlane/metadata/android/en-US/changelogs/5.txt
Normal file
1
fastlane/metadata/android/en-US/changelogs/5.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Fixed bug when opening newly-created element
|
@ -1,3 +1,9 @@
|
|||||||
Shows your Nextcloud Maps geobookmarks list. Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
|
UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geobookmarks list.
|
||||||
A new geobookmark can be created on current location.
|
Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
|
||||||
|
A new geobookmark can be created on current location or by sharing a geo: uri from another app.
|
||||||
|
|
||||||
Requires Maps app to be installed on the Nextcloud instance.
|
Requires Maps app to be installed on the Nextcloud instance.
|
||||||
|
As per Nextcloud's guidelines, the login is implemented using Nextcloud's Single Sign On module and thus requires Nextcloud app installed.
|
||||||
|
|
||||||
|
Promo banner by Gasteaud, Public domain, via Wikimedia Commons.
|
||||||
|
|
||||||
|
BIN
fastlane/metadata/android/en-US/images/featureGraphic.png
Normal file
BIN
fastlane/metadata/android/en-US/images/featureGraphic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
fastlane/metadata/android/en-US/images/promoGraphic.png
Normal file
BIN
fastlane/metadata/android/en-US/images/promoGraphic.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 57 KiB |
@ -1 +1 @@
|
|||||||
Manage Nextcloud Maps Geobookmarks on your Android phone
|
Manage Nextcloud Maps Geobookmarks on your phone
|
||||||
|
Reference in New Issue
Block a user