Compare commits

...

11 Commits

Author SHA1 Message Date
Daniele Verducci (Slimpenguin)
cd576ada12 Bumped version 2023-02-17 07:17:55 +01:00
Daniele Verducci (Slimpenguin)
cf7d38d872 Fix issue 2023-02-15 09:04:33 +01:00
Daniele Verducci (Slimpenguin)
a13e601eea Fixed : "Unfortunately, Maps Geofavorites keeps stopping" and "Maps Geofavorites keeps stopping"
Caused by null fields returned from web APIs
2023-02-15 08:07:17 +01:00
Daniele Verducci (Slimpenguin)
7efd2a2f8b Cleanup, better uri parser 2022-02-25 07:32:51 +01:00
Daniele Verducci (Slimpenguin)
4f64ef00b9 Better geo: uri support, osm url support 2022-02-24 08:46:55 +01:00
Daniele
e595b12705 Rev up! 2022-02-23 22:22:33 +01:00
Daniele
73f597fb54 Open Google Maps on Google devices 2022-02-23 22:06:28 +01:00
Daniele Verducci (Slimpenguin)
3ac332535d WIP adding google maps uri support 2022-02-23 20:01:18 +01:00
Daniele Verducci (Slimpenguin)
4f59359f5e Reimplemented API Provider
To try to fix NullPointerException on mApi reported in Play Store
2022-02-20 09:27:23 +01:00
Daniele Verducci (Slimpenguin)
713e47b20a Fix crash when using corrupted dataset with null category
It may happen on imports from Google Maps
2022-02-20 07:46:48 +01:00
Daniele Verducci (Slimpenguin)
9dfcfa064a Renamed Geobookmark to Geofavorite, added italian store translation 2022-01-18 08:27:09 +01:00
40 changed files with 207 additions and 200 deletions

17
.idea/deploymentTargetDropDown.xml generated Normal 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_5_API_29.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-02-15T07:23:10.782369Z" />
</component>
</project>

@ -1,6 +1,6 @@
![Nextcloud Maps Geobookmarks Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) ![Nextcloud Maps Geofavorites Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
# Nextcloud Maps Geobookmarks Android app # Nextcloud Maps Geofavorites 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://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://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)
@ -8,8 +8,8 @@
(Always prefer [F-Droid](https://f-droid.org) build, when possible). (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 geobookmarks list. UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geofavorites list.
Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...). 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. 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.**

@ -18,14 +18,14 @@
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
android { android {
compileSdkVersion 30 compileSdkVersion 31
defaultConfig { defaultConfig {
applicationId "it.danieleverducci.nextcloudmaps" applicationId "it.danieleverducci.nextcloudmaps"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion 31
versionCode 6 versionCode 8
versionName "0.3.4" versionName "0.3.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -54,7 +54,7 @@ repositories {
dependencies { dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'com.android.support:design:30.0.1' implementation 'com.android.support:design:31.0.0'
implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.cardview:cardview:1.0.0" implementation "androidx.cardview:cardview:1.0.0"
@ -70,7 +70,7 @@ dependencies {
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:0.5.6" implementation "com.github.nextcloud:Android-SingleSignOn:0.6.1"
// OSMDroid // OSMDroid
implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.osmdroid:osmdroid-android:6.1.10'

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

@ -21,10 +21,13 @@
package="it.danieleverducci.nextcloudmaps"> 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
@ -35,21 +38,24 @@
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>
</activity> </activity>
<activity <activity
android:name=".activity.detail.GeofavoriteDetailActivity"> android:name=".activity.detail.GeofavoriteDetailActivity"
android:exported="true">
<!-- standard "geo" scheme --> <!-- standard "geo" scheme -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
@ -82,13 +88,24 @@
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data <data
android:host="maps.google.com" android:host="www.google.com"
android:scheme="http"/> 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> </intent-filter>
</activity> </activity>
<activity android:name=".activity.mappicker.MapPickerActivity" <activity android:name=".activity.mappicker.MapPickerActivity"
android:exported="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

@ -27,6 +27,7 @@ import android.location.LocationListener;
import android.location.LocationManager; import android.location.LocationManager;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
@ -64,7 +65,6 @@ import it.danieleverducci.nextcloudmaps.utils.MapUtils;
public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback { public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
public static final String TAG = "GeofavDetail"; public static final String TAG = "GeofavDetail";
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;
@ -119,7 +119,7 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
}); });
mViewModel = new ViewModelProvider(this).get(GeofavoriteDetailActivityViewModel.class); mViewModel = new ViewModelProvider(this).get(GeofavoriteDetailActivityViewModel.class);
mViewModel.init(); mViewModel.init(getApplicationContext());
mViewModel.getCategories().observe(this, new Observer<HashSet<String>>() { mViewModel.getCategories().observe(this, new Observer<HashSet<String>>() {
@Override @Override
public void onChanged(HashSet<String> categories) { public void onChanged(HashSet<String> categories) {
@ -161,11 +161,12 @@ public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity imple
if (getIntent().getData() != null) { if (getIntent().getData() != null) {
// Opened by external generic intent: parse URI // Opened by external generic intent: parse URI
try { try {
double[] coords = GeoUriParser.parseUri(getIntent().getData()); double[] coords = GeoUriParser.parseUri(getIntent().getData(), false);
mGeofavorite.setLat(coords[0]); mGeofavorite.setLat(coords[0]);
mGeofavorite.setLng(coords[1]); mGeofavorite.setLng(coords[1]);
mViewHolder.hideAccuracy(); mViewHolder.hideAccuracy();
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.error_unsupported_uri, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.error_unsupported_uri, Toast.LENGTH_SHORT).show();
finish(); finish();
} }
@ -335,11 +336,11 @@ 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.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(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.categoryAt.setText(item.getCategory()); binding.categoryAt.setText(item.getCategory() != null ? item.getCategory() : Geofavorite.DEFAULT_CATEGORY);
updateViewCoords(item); updateViewCoords(item);
} }

@ -1,5 +1,7 @@
package it.danieleverducci.nextcloudmaps.activity.detail; package it.danieleverducci.nextcloudmaps.activity.detail;
import android.content.Context;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
@ -14,8 +16,8 @@ import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
public class GeofavoriteDetailActivityViewModel extends ViewModel { public class GeofavoriteDetailActivityViewModel extends ViewModel {
private GeofavoriteRepository mRepo; private GeofavoriteRepository mRepo;
public void init() { public void init(Context applicationContext) {
mRepo = GeofavoriteRepository.getInstance(); mRepo = GeofavoriteRepository.getInstance(applicationContext);
} }
public Geofavorite getGeofavorite(int id) { public Geofavorite getGeofavorite(int id) {

@ -41,11 +41,11 @@ 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.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 NextcloudMapsStyledActivity {
protected ApiProvider mApi;
protected ProgressBar progress; protected ProgressBar progress;
protected Button button; protected Button button;
@ -115,9 +115,6 @@ 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);

@ -111,8 +111,8 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
holder.tv_category.setText(geofavorite.categoryLetter()); holder.tv_category.setText(geofavorite.categoryLetter());
holder.setCategoryColor( holder.setCategoryColor(
geofavorite.categoryColor() == 0 ? context.getColor(R.color.defaultBrand) : geofavorite.categoryColor()); 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() == null ? "" : geofavorite.getName()));
holder.tv_content.setText(geofavorite.getComment()); holder.tv_content.setText(geofavorite.getComment() == null ? "" : geofavorite.getComment());
holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter)); holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter));
} }

@ -58,6 +58,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.activity.mappicker.MapPickerActivity; import it.danieleverducci.nextcloudmaps.activity.mappicker.MapPickerActivity;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser; import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator; import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
@ -140,7 +141,7 @@ public class MainActivity extends NextcloudMapsStyledActivity implements OnSorti
mMainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class); mMainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
mMainActivityViewModel.init(); mMainActivityViewModel.init(getApplicationContext());
mMainActivityViewModel.getIsUpdating().observe(this, new Observer<Boolean>() { mMainActivityViewModel.getIsUpdating().observe(this, new Observer<Boolean>() {
@Override @Override
public void onChanged(@Nullable Boolean aBoolean) { public void onChanged(@Nullable Boolean aBoolean) {
@ -155,7 +156,7 @@ public class MainActivity extends NextcloudMapsStyledActivity implements OnSorti
mMainActivityViewModel.getOnFinished().observe(this, new Observer<Boolean>() { mMainActivityViewModel.getOnFinished().observe(this, new Observer<Boolean>() {
@Override @Override
public void onChanged(@Nullable Boolean success) { public void onChanged(@Nullable Boolean success) {
if(!success){ if(success == null || !success){
Toast.makeText(MainActivity.this, R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show(); Toast.makeText(MainActivity.this, R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show();
} }
} }
@ -297,9 +298,11 @@ public class MainActivity extends NextcloudMapsStyledActivity implements OnSorti
} }
private void switch_account() { private void switch_account() {
ApiProvider.logout();
SingleAccountHelper.setCurrentAccount(this, null); SingleAccountHelper.setCurrentAccount(this, null);
Intent intent = new Intent(MainActivity.this, LoginActivity.class); Intent intent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(intent); startActivity(intent);
finish();
} }
@Override @Override
@ -342,7 +345,7 @@ public class MainActivity extends NextcloudMapsStyledActivity implements OnSorti
private void showGeofavoriteDeteleDialog(Geofavorite item) { private void showGeofavoriteDeteleDialog(Geofavorite item) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName())) builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName() != null ? item.getName() : ""))
.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) {

@ -1,5 +1,7 @@
package it.danieleverducci.nextcloudmaps.activity.main; package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
@ -12,8 +14,8 @@ import it.danieleverducci.nextcloudmaps.repository.GeofavoriteRepository;
public class MainActivityViewModel extends ViewModel { public class MainActivityViewModel extends ViewModel {
private GeofavoriteRepository mRepo; private GeofavoriteRepository mRepo;
public void init() { public void init(Context applicationContext) {
mRepo = GeofavoriteRepository.getInstance(); mRepo = GeofavoriteRepository.getInstance(applicationContext);
} }
public LiveData<List<Geofavorite>> getGeofavorites(){ public LiveData<List<Geofavorite>> getGeofavorites(){

@ -15,25 +15,19 @@ import android.view.inputmethod.InputMethodManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat; import androidx.core.app.ActivityCompat;
import androidx.preference.PreferenceManager;
import org.osmdroid.api.IGeoPoint; import org.osmdroid.api.IGeoPoint;
import org.osmdroid.api.IMapController; import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.events.MapListener; import org.osmdroid.events.MapListener;
import org.osmdroid.events.ScrollEvent; import org.osmdroid.events.ScrollEvent;
import org.osmdroid.events.ZoomEvent; import org.osmdroid.events.ZoomEvent;
import org.osmdroid.util.GeoPoint; import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController; import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.MapView; import org.osmdroid.views.MapView;
import org.osmdroid.views.Projection;
import java.util.Locale; import java.util.Locale;
import it.danieleverducci.nextcloudmaps.BuildConfig;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity; import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity; import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
@ -64,7 +58,7 @@ public class MapPickerActivity extends NextcloudMapsStyledActivity {
@Override @Override
public void onConfirmButtonPressed() { public void onConfirmButtonPressed() {
double[] coords = mViewHolder.getCurrentCoordinates(); double[] coords = mViewHolder.getCurrentCoordinates();
Uri geoUri = GeoUriParser.createUri(coords[0], coords[1], null); Uri geoUri = GeoUriParser.createGeoUri(coords[0], coords[1], null);
Intent i = new Intent(MapPickerActivity.this, GeofavoriteDetailActivity.class); Intent i = new Intent(MapPickerActivity.this, GeofavoriteDetailActivity.class);
i.setData(geoUri); i.setData(geoUri);
startActivity(i); startActivity(i);

@ -24,6 +24,7 @@ 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;
@ -35,47 +36,27 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import retrofit2.NextcloudRetrofitApiBuilder; import retrofit2.NextcloudRetrofitApiBuilder;
public class ApiProvider { public class ApiProvider {
private final String TAG = ApiProvider.class.getCanonicalName(); private static final String TAG = ApiProvider.class.getCanonicalName();
@NonNull
protected Context context;
protected static API mApi; protected static API mApi;
protected static String ssoAccountName; @Nullable
public static API getAPI(Context context) {
if (mApi == null) {
try {
SingleSignOnAccount ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
NextcloudAPI nextcloudAPI = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create());
public ApiProvider(Context context) { mApi = new NextcloudRetrofitApiBuilder(nextcloudAPI, API.mApiEndpoint).create(API.class);
this.context = context; } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
initSsoApi(new NextcloudAPI.ApiConnectedListener() { Log.d(TAG, "setAccout() called with: ex = [" + e + "]");
@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 String getAccountName() { public static void logout() {
return ssoAccountName; mApi = null;
} }
} }

@ -164,7 +164,10 @@ public class Geofavorite implements Serializable {
} }
public Uri getGeoUri() { public Uri getGeoUri() {
return GeoUriParser.createUri(this.lat, this.lng, this.name); return GeoUriParser.createGeoUri(this.lat, this.lng, this.name);
}
public Uri getGmapsUri() {
return GeoUriParser.createGmapsUri(this.lat, this.lng);
} }
public boolean valid() { public boolean valid() {
@ -199,7 +202,7 @@ public class Geofavorite implements Serializable {
*/ */
public int categoryColor() { public int categoryColor() {
// If category is default, return null: will be used Nextcloud's accent // If category is default, return null: will be used Nextcloud's accent
if (this.category.equals(DEFAULT_CATEGORY)) if (this.category == null || this.category.equals(DEFAULT_CATEGORY) || this.category.length() == 0)
return 0; return 0;
float letter1Index = this.category.toLowerCase().charAt(0); float letter1Index = this.category.toLowerCase().charAt(0);
@ -212,9 +215,7 @@ public class Geofavorite implements Serializable {
} }
public String categoryLetter() { public String categoryLetter() {
if (category == null || category.length() == 0) if (category == null || category.length() == 0 || category.equals(DEFAULT_CATEGORY))
return "";
if (category.equals(DEFAULT_CATEGORY))
return "\u2022"; return "\u2022";
return category.substring(0,1); return category.substring(0,1);
} }

@ -1,9 +1,9 @@
package it.danieleverducci.nextcloudmaps.repository; package it.danieleverducci.nextcloudmaps.repository;
import android.content.Context;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList; import java.util.ArrayList;
@ -29,9 +29,15 @@ public class GeofavoriteRepository {
private MutableLiveData<Boolean> mIsUpdating = new MutableLiveData<>(false); private MutableLiveData<Boolean> mIsUpdating = new MutableLiveData<>(false);
private SingleLiveEvent<Boolean> mOnFinished = new SingleLiveEvent<>(); private SingleLiveEvent<Boolean> mOnFinished = new SingleLiveEvent<>();
public static GeofavoriteRepository getInstance() { private Context applicationContext;
public GeofavoriteRepository(Context applicationContext) {
this.applicationContext = applicationContext;
}
public static GeofavoriteRepository getInstance(Context applicationContext) {
if(instance == null){ if(instance == null){
instance = new GeofavoriteRepository(); instance = new GeofavoriteRepository(applicationContext);
} }
return instance; return instance;
} }
@ -59,7 +65,7 @@ public class GeofavoriteRepository {
public void updateGeofavorites() { public void updateGeofavorites() {
mIsUpdating.postValue(true); mIsUpdating.postValue(true);
// Obtain geofavorites // Obtain geofavorites
Call<List<Geofavorite>> call = ApiProvider.getAPI().getGeofavorites(); Call<List<Geofavorite>> call = ApiProvider.getAPI(this.applicationContext).getGeofavorites();
call.enqueue(new Callback<List<Geofavorite>>() { call.enqueue(new Callback<List<Geofavorite>>() {
@Override @Override
public void onResponse(@NonNull Call<List<Geofavorite>> call, @NonNull Response<List<Geofavorite>> response) { public void onResponse(@NonNull Call<List<Geofavorite>> call, @NonNull Response<List<Geofavorite>> response) {
@ -94,10 +100,10 @@ public class GeofavoriteRepository {
Call<Geofavorite> call; Call<Geofavorite> call;
if (geofav.getId() == 0) { if (geofav.getId() == 0) {
// New geofavorite // New geofavorite
call = ApiProvider.getAPI().createGeofavorite(geofav); call = ApiProvider.getAPI(this.applicationContext).createGeofavorite(geofav);
} else { } else {
// Update existing geofavorite // Update existing geofavorite
call = ApiProvider.getAPI().updateGeofavorite(geofav.getId(), geofav); call = ApiProvider.getAPI(this.applicationContext).updateGeofavorite(geofav.getId(), geofav);
} }
call.enqueue(new Callback<Geofavorite>() { call.enqueue(new Callback<Geofavorite>() {
@Override @Override
@ -129,7 +135,7 @@ public class GeofavoriteRepository {
public void deleteGeofavorite(Geofavorite geofav) { public void deleteGeofavorite(Geofavorite geofav) {
mIsUpdating.postValue(true); mIsUpdating.postValue(true);
// Delete Geofavorite // Delete Geofavorite
Call<Geofavorite> call = ApiProvider.getAPI().deleteGeofavorite(geofav.getId()); Call<Geofavorite> call = ApiProvider.getAPI(this.applicationContext).deleteGeofavorite(geofav.getId());
call.enqueue(new Callback<Geofavorite>() { call.enqueue(new Callback<Geofavorite>() {
@Override @Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) { public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
@ -156,7 +162,9 @@ public class GeofavoriteRepository {
private void updateCategories(List<Geofavorite> geofavs) { private void updateCategories(List<Geofavorite> geofavs) {
HashSet<String> categories = new HashSet<>(); HashSet<String> categories = new HashSet<>();
for (Geofavorite g : geofavs) { for (Geofavorite g : geofavs) {
categories.add(g.getCategory()); String cat = g.getCategory();
if (cat != null)
categories.add(cat);
} }
mCategories.postValue(categories); mCategories.postValue(categories);
} }

@ -10,35 +10,79 @@ import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeoUriParser { public class GeoUriParser {
private static final Pattern PATTERN_GEO = Pattern.compile("geo:(-?[\\d.]+),(-?[\\d.]+)"); 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})"
);
public static double[] parseUri(Uri uri) throws IllegalArgumentException { /**
* 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) if (uri == null)
throw new IllegalArgumentException("no uri"); throw new IllegalArgumentException("no uri");
Matcher m = PATTERN_GEO.matcher(uri.toString()); // Try to extract coordinates in uri string with regexp
if (m.find() && m.groupCount() == 2) { Pattern pattern = strict ? PATTERN_GEO : PATTERN_BROAD;
String sLat = m.group(1); Matcher m = pattern.matcher(uri.toString());
String sLon = m.group(2); if (!m.find() || m.groupCount() != 2)
try { throw new IllegalArgumentException("unable to parse uri: unable to find coordinates in uri");
return new double[]{Double.parseDouble(sLat), Double.parseDouble(sLon)};
} catch (NumberFormatException e) { // Obtain coordinates from regexp result
throw new IllegalArgumentException("unable to parse uri"); String sLat = m.group(1);
} String sLon = m.group(2);
} else { double[] coords = null;
throw new IllegalArgumentException("unable to parse uri"); 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 createUri(double lat, double lon, String name) { public static Uri createGeoUri(double lat, double lon, String name) {
// Check coords validity String error = checkCoordsValidity(lat, lon);
if (lon <= -180 || lon >= 180 ) if (error != null)
throw new IllegalArgumentException("Invalid longitude: " + lon); throw new IllegalArgumentException(error);
if (lat <= -90 || lat >= 90)
throw new IllegalArgumentException("Invalid latitude: " + lat);
String uriStr = "geo:" + lat + "," + lon; String uriStr = "geo:" + lat + "," + lon;
if (name != null) if (name != null)
uriStr += "(" + name + ")"; uriStr += "(" + name + ")";
return Uri.parse(uriStr); 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;
}
} }

@ -2,6 +2,7 @@ package it.danieleverducci.nextcloudmaps.utils;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log; import android.util.Log;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
@ -22,7 +23,16 @@ public class IntentGenerator {
public static Intent newGeoUriIntent(Context context, Geofavorite item) { public static Intent newGeoUriIntent(Context context, Geofavorite item) {
Intent i = new Intent(); Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW); i.setAction(Intent.ACTION_VIEW);
i.setData(item.getGeoUri()); i.setData(isGoogleMapsInstalled(context) ? item.getGmapsUri() : item.getGeoUri());
return i; 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;
}
}
} }

@ -1,22 +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/>.
-->
<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>

@ -1,22 +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/>.
-->
<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.

Before

(image error) Size: 3.8 KiB

Binary file not shown.

Before

(image error) Size: 5.4 KiB

Binary file not shown.

Before

(image error) Size: 2.7 KiB

Binary file not shown.

Before

(image error) Size: 3.3 KiB

Binary file not shown.

Before

(image error) Size: 5.5 KiB

Binary file not shown.

Before

(image error) Size: 7.9 KiB

Binary file not shown.

Before

(image error) Size: 8.7 KiB

Binary file not shown.

Before

(image error) Size: 12 KiB

Binary file not shown.

Before

(image error) Size: 12 KiB

Binary file not shown.

Before

(image error) Size: 18 KiB

@ -18,13 +18,13 @@
<resources> <resources>
<!-- App strings --> <!-- App strings -->
<string name="app_name">Maps Geobookmarks</string> <string name="app_name">Maps Geofavorites</string>
<string name="welcome">Benvenuto su Nextcloud Maps Geobookmarks</string> <string name="welcome">Benvenuto su Nextcloud Maps Geofavorites</string>
<!-- Login Activity --> <!-- Login Activity -->
<string name="choose_account">Scegli account</string> <string name="choose_account">Scegli account</string>
<!-- Geobookmarks list --> <!-- Geofavorites list -->
<string name="new_geobookmark">Nuovo geosegnalibro</string> <string name="new_geobookmark">Nuovo geosegnalibro</string>
<string name="about">Informazioni</string> <string name="about">Informazioni</string>
<string name="switch_account">Cambia account</string> <string name="switch_account">Cambia account</string>
@ -48,7 +48,7 @@
<string name="menu_item_sort_by_category_a_z">Categoria</string> <string name="menu_item_sort_by_category_a_z">Categoria</string>
<string name="menu_item_sort_by_distance_nearest_first">Distanza</string> <string name="menu_item_sort_by_distance_nearest_first">Distanza</string>
<!-- Geobookmarks detail --> <!-- Geofavorites detail -->
<string name="name">Nome</string> <string name="name">Nome</string>
<string name="description">Descrizione</string> <string name="description">Descrizione</string>
<string name="created">Creato</string> <string name="created">Creato</string>

@ -17,13 +17,13 @@
<resources> <resources>
<!-- App strings --> <!-- App strings -->
<string name="app_name">Maps Geobookmarks</string> <string name="app_name">Maps Geofavorites</string>
<string name="welcome">Welcome to Nextcloud Maps Geobookmarks</string> <string name="welcome">Welcome to Nextcloud Maps Geofavorites</string>
<!-- Login Activity --> <!-- Login Activity -->
<string name="choose_account">Choose account</string> <string name="choose_account">Choose account</string>
<!-- Geobookmarks list --> <!-- Geofavorites list -->
<string name="new_geobookmark">New geofavorite</string> <string name="new_geobookmark">New geofavorite</string>
<string name="about">About</string> <string name="about">About</string>
<string name="switch_account">Switch account</string> <string name="switch_account">Switch account</string>
@ -47,7 +47,7 @@
<string name="menu_item_sort_by_category_a_z">Category</string> <string name="menu_item_sort_by_category_a_z">Category</string>
<string name="menu_item_sort_by_distance_nearest_first">Distance</string> <string name="menu_item_sort_by_distance_nearest_first">Distance</string>
<!-- Geobookmarks detail --> <!-- Geofavorites detail -->
<string name="name">Name</string> <string name="name">Name</string>
<string name="description">Description</string> <string name="description">Description</string>
<string name="created">Created</string> <string name="created">Created</string>

@ -25,7 +25,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.2' classpath 'com.android.tools.build:gradle:7.1.1'
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files

@ -0,0 +1,4 @@
Fix crash when using corrupted dataset with null category (It may happen on imports from Google Maps)
Reimplemented API Provider to try to fix NullPointerException on mApi reported in Play Store
Open Google Maps on Google devices
Support share from: google maps urls, OpenStreetMap urls (only if containing coordinates)

@ -1,5 +1,5 @@
UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geobookmarks list. UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geofavorites list.
Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...). 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. 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.

@ -1 +1 @@
Manage Nextcloud Maps Geobookmarks on your phone Manage Nextcloud Maps Geofavorites on your phone

@ -1 +1 @@
Nextcloud Maps Geobookmarks Nextcloud Maps Geofavorites

@ -0,0 +1,3 @@
Client per Nextcloud Maps non ufficiale e FOSS al suo stato iniziale di sviluppo.
Mostra la lista dei geosegnalibri di Nextcloud Maps e permette di aprirli in qualunque app supporti i "geo:" urls (i.e. Google Maps, Organic Maps etc...).
È possibile creare nuovi geosegnalibri nella posizione GPS attuale, scegliendo la posizione dalla mappa o condividendola da altre app sotto forma di "geo:" uri.

@ -0,0 +1 @@
Un semplice client per Nextcloud Maps

@ -0,0 +1 @@
Nextcloud Maps Geofavorites

@ -1,23 +1,6 @@
# #Sun Feb 20 08:50:37 CET 2022
# Nextcloud 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/>.
#
#Mon Oct 05 23:27:18 ART 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip zipStoreBase=GRADLE_USER_HOME