Compare commits

...

77 Commits
v0.1 ... master

Author SHA1 Message Date
Daniele Verducci (Slimpenguin)
cd576ada12 Bumped version 2023-02-17 07:17:55 +01:00
Daniele Verducci (Slimpenguin)
cf7d38d872 Fix issue #15 2023-02-15 09:04:33 +01:00
Daniele Verducci (Slimpenguin)
a13e601eea Fixed #15: "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
Daniele Verducci (Slimpenguin)
61263c191e Rev up 2022-01-17 08:51:24 +01:00
Daniele Verducci (Slimpenguin)
b7f61846e3 Fixed drawable tinting bug in geofavs list 2022-01-17 08:19:00 +01:00
Daniele Verducci (Slimpenguin)
4aae6e90be Using pin icons in geofav list
Because circle conventionally is used for user with missing avatar
2022-01-16 13:39:01 +01:00
Daniele Verducci (Slimpenguin)
d5b6158364 Dark mode, styles fix 2022-01-16 10:32:00 +01:00
Daniele Verducci (Slimpenguin)
b9980206f5 WIP Dark theme 2022-01-15 12:29:17 +01:00
Daniele Verducci (Slimpenguin)
6336c4d61b Hidden edit button until the functionality is implemented 2022-01-15 10:56:25 +01:00
Daniele Verducci (Slimpenguin)
864802cdae Accuracy level indicator restyling 2022-01-15 10:01:51 +01:00
Daniele Verducci (Slimpenguin)
907fcb4cb1 issue 9: Button color for sorting uses app accent 2022-01-15 08:49:44 +01:00
Daniele Verducci (Slimpenguin)
d3a21c93c1 issue 8: Defined default accent to colorize elements like text cursor etc... 2022-01-14 08:53:54 +01:00
Daniele Verducci (Slimpenguin)
f57abf150a issue 10: fix mixed names (geofavorite/geobookmark) 2022-01-14 08:43:46 +01:00
Daniele Verducci (Slimpenguin)
a40b64baca Flow fix 2022-01-14 08:39:34 +01:00
Daniele Verducci (Slimpenguin)
15c11a33ee WIP Intent for geobookmark creation 2022-01-12 09:00:05 +01:00
Daniele Verducci (Slimpenguin)
d888485fc2 Fixed coordinates mapping on map picker 2022-01-12 08:24:26 +01:00
Daniele Verducci (Slimpenguin)
d009237679 WIP coordinates parsing
Not working ATM
2022-01-11 08:54:19 +01:00
Daniele Verducci (Slimpenguin)
c3e2496596 WIP adding map picker 2022-01-10 20:02:16 +01:00
Daniele Verducci (Slimpenguin)
607bbd0c2b Italian locale 2022-01-10 07:38:17 +01:00
Daniele Verducci (Slimpenguin)
90c4b998e7 Created empty map pick activity, added FAB actions 2022-01-06 08:26:38 +01:00
Daniele
8f054dd324 Merge branch 'master' of ssh://ichibi.eu:222/home/git/nextcloud-maps-client 2021-11-22 08:55:42 +01:00
Daniele
83b3b8a48d Set OSMDroid UserAgent 2021-11-22 08:55:35 +01:00
Daniele
e64b89bce6 Updated about, hidden accuracy when creating by geouri, alphabetic category order 2021-10-05 19:15:02 +02:00
Daniele
fd14eea4b9 Support for geobookmark creation from external geo uris 2021-10-05 09:29:47 +02:00
Daniele
065ef2bea6 Fixed new element bug 2021-10-05 07:26:16 +02:00
Daniele
308474246b Updated fastlane 2021-10-01 08:20:48 +02:00
Daniele
96b88a398c WIP accepting generic intents with geo:// uri 2021-09-30 20:52:09 +02:00
Daniele
15cea67818 F-Droid badge! 2021-09-30 18:47:25 +02:00
Daniele
9e89879d5c Fixed Nextcloud app name on description 2021-09-25 14:39:50 +02:00
Daniele
a0c397b194 Fastlane fixes 2021-09-25 08:39:24 +02:00
Daniele
cdb146bffb Release v0.3.2 2021-09-22 22:49:03 +02:00
Daniele
7ac35f21cf First categories implementation 2021-09-22 22:47:56 +02:00
Daniele
760f7f572a Style 2021-09-22 21:47:41 +02:00
Daniele
9aa2652059 Sorting by category 2021-09-22 21:41:21 +02:00
Daniele
718c654056 Categories colors in list 2021-09-22 09:24:11 +02:00
Daniele
63b60a46ae Fixed bug in list order for new items 2021-09-22 08:38:23 +02:00
Daniele
a694246a56 Conversion to MVVM completed 2021-09-22 08:20:33 +02:00
Daniele
8b7e89f43b WIP implementing MVVM 2021-09-21 09:25:28 +02:00
Daniele
cf7f96efad Trying basic MVVM implementation based on https://github.com/mitchtabian/MVVMExample1 2021-09-19 09:51:54 +02:00
Daniele
b9dbde7f7c Moved category color generation into geofavorite model 2021-09-17 23:11:00 +02:00
Daniele
4592bb3382 WIP Porting category color code from Nextcloud Maps js code 2021-09-17 08:58:27 +02:00
Daniele
88cf7711b0 Fixed new geobookmark date, changed "open in app" icon 2021-09-17 08:23:37 +02:00
Daniele
82a5eb343b Updated fdroid market description 2021-09-15 22:36:04 +02:00
Daniele
ae37f14067 Release v0.3.1 2021-09-15 20:28:04 +02:00
Daniele
c7212fbeba Pre-release 2021-09-15 20:26:29 +02:00
Daniele
f21e5221ba WIP geobookmark detail graphic 2021-09-15 16:10:00 +02:00
Daniele
fdb314c48b Updated README 2021-09-14 20:27:14 +02:00
Daniele
b8541c994a Added back button in detail 2021-09-14 19:59:08 +02:00
Daniele
2a62da3cf2 Added pin in geofavorite detail map 2021-09-14 19:07:44 +02:00
Daniele
7c5de6ccd7 WIP Implementing map in geopoint detail 2021-09-14 09:27:30 +02:00
Daniele
4ff6c569d0 Downgraded to Java 1.8 to comply with fdroid build system 2021-09-12 08:32:23 +02:00
Daniele
c58041fc20 Added screenshots in README 2021-09-10 19:29:32 +02:00
Daniele
b3169f6525 Added screenshots in README 2021-09-10 19:25:38 +02:00
Daniele
6bca4462a3 Connection error management 2021-09-10 19:02:28 +02:00
Daniele
e558fa28b5 Release v0.2 2021-09-10 09:45:46 +02:00
Daniele
809616aefa Working delete 2021-09-08 09:29:13 +02:00
Daniele
b8551cbb13 Working detail from list! 2021-09-08 08:20:21 +02:00
Daniele
9161b61b6e Added deletion, but the dialog is never displayed 2021-09-07 21:22:30 +02:00
Daniele
9c83707bfc Working context menu callbacks 2021-09-07 20:52:37 +02:00
Daniele
bd3ca1740a Layout fixes 2021-09-07 19:48:31 +02:00
Daniele
5d4e56135e Updated gradle, added link in share 2021-09-07 19:42:36 +02:00
Daniele
bac3639c3f WIP list context menu 2021-09-07 08:57:56 +02:00
Daniele
e89ce8e7da Refactoring, cleaning, share button 2021-09-07 08:24:48 +02:00
Daniele Verducci (ZenPenguin)
79ad2633e7 Renamed listitem layout file 2021-08-30 07:40:58 +02:00
Daniele Verducci (ZenPenguin)
a327cf7a21 Geobookmarks shown as list by default 2021-08-30 07:37:51 +02:00
Daniele Verducci (ZenPenguin)
b3c63df32e Gave up resizing images in readme, removed screenshots 2021-08-29 09:09:08 +02:00
Daniele Verducci (ZenPenguin)
76e99b7700 Resize images in readme 2021-08-29 09:04:56 +02:00
Daniele Verducci (ZenPenguin)
369c842a14 Resize images in readme 2021-08-29 09:01:15 +02:00
Daniele Verducci (ZenPenguin)
29bf3c80d3 Images in readme 2021-08-29 08:56:13 +02:00
117 changed files with 2637 additions and 805 deletions

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

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_5_API_29.avd" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2023-02-15T07:23:10.782369Z" />
</component>
</project>

26
.idea/misc.xml generated
View File

@ -1,4 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
<entry key="app/src/main/res/drawable/category_listitem_background.xml" value="0.35104166666666664" />
<entry key="app/src/main/res/drawable/coordinates_label_background.xml" value="0.3614583333333333" />
<entry key="app/src/main/res/drawable/floating_semitransparent_button_background.xml" value="0.512962962962963" />
<entry key="app/src/main/res/drawable/ic_list_pin.xml" value="0.3614583333333333" />
<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_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/round_button_background.xml" value="0.3614583333333333" />
<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.4" />
<entry key="app/src/main/res/layout/activity_login.xml" value="0.2630208333333333" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/layout/activity_map_picker.xml" value="0.33016304347826086" />
<entry key="app/src/main/res/layout/item_geofav.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/layout/item_navigation.xml" value="0.8" />
<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" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -1,9 +1,20 @@
# Nextcloud Maps Geobookmarks Android app
![Nextcloud Maps Geofavorites Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
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.
# 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://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).
UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geofavorites list.
Geofavorites can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
A new geofavorite can be created on current location, by sharing a "geo:" uri from another app or manually picking from the map.
**Requires Maps app to be installed on the Nextcloud instance.**
This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://github.com/matiasdelellis/app-tutorial-android) to implement [Nextcloud single sign on](https://github.com/nextcloud/Android-SingleSignOn).
![Screenshot 1](screenshots/1.png) ![Screenshot 1](screenshots/2.png)

View File

@ -18,14 +18,14 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 30
compileSdkVersion 31
defaultConfig {
applicationId "it.danieleverducci.nextcloudmaps"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "0.1"
targetSdkVersion 31
versionCode 8
versionName "0.3.6"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
@ -54,21 +54,28 @@ repositories {
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'com.android.support:design:30.0.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'com.android.support:design:31.0.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.cardview:cardview:1.0.0"
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation "androidx.preference:preference:1.1.1"
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Retrofif2
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
// Nextcloud SSO
implementation "com.github.nextcloud:Android-SingleSignOn:0.5.6"
implementation "com.github.nextcloud:Android-SingleSignOn:0.6.1"
// OSMDroid
implementation 'org.osmdroid:osmdroid-android:6.1.10'
//Threeten-Backport (ports Java 8 Date API on Java 6+)
implementation 'org.threeten:threetenbp:1.5.1'
}

View File

@ -21,35 +21,96 @@
package="it.danieleverducci.nextcloudmaps">
<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" />
<queries>
<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>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.login.LoginActivity">
<activity android:name=".activity.login.LoginActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".activity.main.MainActivity">
<activity android:name=".activity.main.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name=".activity.main.GeofavoriteDetailActivity"
android:theme="@style/AppTheme"/>
android:name=".activity.detail.GeofavoriteDetailActivity"
android:exported="true">
<!-- standard "geo" scheme -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="geo"/>
</intent-filter>
<!-- Google Maps -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="google.navigation"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="maps.google.com"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="www.google.com"
android:pathPrefix="/maps"
android:scheme="https"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data
android:host="www.openstreetmap.org"
android:scheme="https"/>
</intent-filter>
</activity>
<activity android:name=".activity.mappicker.MapPickerActivity"
android:exported="true"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
</intent-filter>
</activity>
<activity
android:name=".activity.about.AboutActivity"

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

View File

@ -30,8 +30,9 @@ import androidx.appcompat.widget.Toolbar;
import it.danieleverducci.nextcloudmaps.BuildConfig;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
public class AboutActivity extends AppCompatActivity {
public class AboutActivity extends NextcloudMapsStyledActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -62,6 +63,10 @@ public class AboutActivity extends AppCompatActivity {
TextView tvIssues = findViewById(R.id.about_issues);
tvIssues.setText(Html.fromHtml(getString(R.string.about_issues, 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

View File

@ -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();
}
}

View File

@ -0,0 +1,442 @@
/*
* 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/>.
*/
package it.danieleverducci.nextcloudmaps.activity.detail;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.app.ActivityCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.config.IConfigurationProvider;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.overlay.Marker;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.FormatStyle;
import java.util.HashSet;
import it.danieleverducci.nextcloudmaps.BuildConfig;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.mappicker.MapPickerActivity;
import it.danieleverducci.nextcloudmaps.databinding.ActivityGeofavoriteDetailBinding;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
import it.danieleverducci.nextcloudmaps.utils.MapUtils;
public class GeofavoriteDetailActivity extends NextcloudMapsStyledActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
public static final String TAG = "GeofavDetail";
public static final String ARG_GEOFAVORITE = "geofav";
private static final int PERMISSION_REQUEST_CODE = 9999;
private ViewHolder mViewHolder;
private Geofavorite mGeofavorite;
private GeofavoriteDetailActivityViewModel mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MapUtils.configOsmdroid(this);
mViewHolder = new ViewHolder(getLayoutInflater());
setContentView(mViewHolder.getRootView());
mViewHolder.setOnSubmitListener(new OnSubmitListener() {
@Override
public void onBackPressed() {
finish();
}
@Override
public void onMapEditPressed() {
//TODO
}
@Override
public void onActionIconShareClicked() {
startActivity(Intent.createChooser(IntentGenerator.newShareIntent(GeofavoriteDetailActivity.this, mGeofavorite), getString(R.string.share_via)));
}
@Override
public void onActionIconNavClicked() {
startActivity(IntentGenerator.newGeoUriIntent(GeofavoriteDetailActivity.this, mGeofavorite));
}
@Override
public void onActionIconDeleteClicked() {
// TODO
}
@Override
public void onSubmit() {
saveGeofavorite();
}
@Override
public void onMapClicked() {
// TODO: Open map activity with pin
startActivity(IntentGenerator.newGeoUriIntent(GeofavoriteDetailActivity.this, mGeofavorite));
}
});
mViewModel = new ViewModelProvider(this).get(GeofavoriteDetailActivityViewModel.class);
mViewModel.init(getApplicationContext());
mViewModel.getCategories().observe(this, new Observer<HashSet<String>>() {
@Override
public void onChanged(HashSet<String> categories) {
mViewHolder.setCategories(categories);
}
});
mViewModel.getIsUpdating().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean updating) {
mViewHolder.setUpdating(updating);
}
});
mViewModel.getOnFinished().observe(this, new Observer<Boolean>() {
@Override
public void onChanged(@Nullable Boolean success) {
if(success){
Toast.makeText(GeofavoriteDetailActivity.this, R.string.geofavorite_saved, Toast.LENGTH_SHORT).show();
finish();
} else {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show();
}
}
});
if (getIntent().hasExtra(ARG_GEOFAVORITE) && getIntent().getIntExtra(ARG_GEOFAVORITE, 0) != 0) {
// Opening geofavorite from list
mGeofavorite = mViewModel.getGeofavorite(
getIntent().getIntExtra(ARG_GEOFAVORITE, 0)
);
mViewHolder.hideAccuracy();
} else {
// New geofavorite
mGeofavorite = new Geofavorite();
mGeofavorite.setCategory(Geofavorite.DEFAULT_CATEGORY);
mGeofavorite.setDateCreated(System.currentTimeMillis() / 1000);
mGeofavorite.setDateModified(System.currentTimeMillis() / 1000);
mViewHolder.hideActions();
if (getIntent().getData() != null) {
// Opened by external generic intent: parse URI
try {
double[] coords = GeoUriParser.parseUri(getIntent().getData(), false);
mGeofavorite.setLat(coords[0]);
mGeofavorite.setLng(coords[1]);
mViewHolder.hideAccuracy();
} catch (IllegalArgumentException e) {
Log.e(TAG, e.getMessage());
Toast.makeText(this, R.string.error_unsupported_uri, Toast.LENGTH_SHORT).show();
finish();
}
} else {
// Precompile location with current one
getLocation();
}
}
mViewHolder.updateView(mGeofavorite);
}
@Override
protected void onResume() {
super.onResume();
// OSMDroid config
Configuration.getInstance().load(getApplicationContext(),
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
mViewHolder.onResume();
}
@Override
protected void onPause() {
// OSMDroid config
Configuration.getInstance().save(getApplicationContext(),
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
mViewHolder.onPause();
super.onPause();
}
/**
* Called when the submit button is clicked
* @param v The button
*/
public void onSubmit(View v) {
saveGeofavorite();
}
/**
* Checks fields and sends updated geofavorite to Nextcloud instance
*/
private void saveGeofavorite() {
mViewHolder.updateModel(mGeofavorite);
if (!mGeofavorite.valid()) {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.incomplete_geofavorite, Toast.LENGTH_SHORT).show();
return;
}
mViewModel.saveGeofavorite(mGeofavorite);
}
/**
* Obtains the current location (requesting user's permission, if necessary)
* and calls updateLocationField()
*/
private void getLocation() {
// Check if user granted location permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// User didn't grant permission. Ask it.
requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_CODE);
return;
}
LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
// Try to use last available location
Location lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnown != null)
updateLocationField(lastKnown);
// Register for location updates in case the user moves before saving
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this
);
}
/**
* Compiles the geofavorite location field with the provided location object
* @param location to set in the geofavorite
*/
private void updateLocationField(Location location) {
// Update model
mGeofavorite.setLat(location.getLatitude());
mGeofavorite.setLng(location.getLongitude());
// Update view
mViewHolder.updateViewCoords(mGeofavorite);
mViewHolder.setAccuracy(location.getAccuracy());
}
/** Location updates callbacks **/
@Override
public void onLocationChanged(@NonNull Location location) {
updateLocationField(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(@NonNull String provider) {}
@Override
public void onProviderDisabled(@NonNull String provider) {}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {}
/** Position permission request result **/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE && permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLocation();
} else {
Toast.makeText(this, R.string.location_permission_required, Toast.LENGTH_LONG).show();
finish();
}
}
}
private class ViewHolder implements View.OnClickListener {
private final ActivityGeofavoriteDetailBinding binding;
private DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG);
private OnSubmitListener listener;
private Marker mapMarker;
public ViewHolder(LayoutInflater inflater) {
this.binding = ActivityGeofavoriteDetailBinding.inflate(inflater);
this.binding.submitBt.setOnClickListener(this);
this.binding.mapBt.setOnClickListener(this);
this.binding.backBt.setOnClickListener(this);
this.binding.actionIconShare.setOnClickListener(this);
this.binding.actionIconDelete.setOnClickListener(this);
this.binding.actionIconNav.setOnClickListener(this);
// Set categories adapter
CategoriesSpinnerAdapter categoriesAdapter = new CategoriesSpinnerAdapter(binding.root.getContext());
this.binding.categoryAt.setAdapter(categoriesAdapter);
this.binding.categoryAt.setText(Geofavorite.DEFAULT_CATEGORY);
// Set map properties
this.binding.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
this.binding.map.setMultiTouchControls(true);
MapUtils.setTheme(this.binding.map);
// this.binding.map.setTilesScaledToDpi(true);
// Create marker
mapMarker = new Marker(binding.map);
mapMarker.setIcon(AppCompatResources.getDrawable(GeofavoriteDetailActivity.this, R.drawable.ic_map_pin));
mapMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM);
binding.map.getOverlays().add(mapMarker);
}
public View getRootView() {
return this.binding.root;
}
public void updateView(Geofavorite item) {
binding.collapsingToolbar.setTitle(item.getName() != null ? item.getName() : getString(R.string.new_geobookmark));
binding.nameEt.setText(item.getName() != null ? item.getName() : "");
binding.descriptionEt.setText(item.getComment() != null ? item.getComment() : "");
binding.createdTv.setText(item.getLocalDateCreated().format(dateFormatter));
binding.modifiedTv.setText(item.getLocalDateCreated().format(dateFormatter));
binding.categoryAt.setText(item.getCategory() != null ? item.getCategory() : Geofavorite.DEFAULT_CATEGORY);
updateViewCoords(item);
}
public void updateViewCoords(Geofavorite item) {
binding.coordsTv.setText(item.getCoordinatesString());
// Center map
GeoPoint position = new GeoPoint(item.getLat(), item.getLng());
IMapController mapController = binding.map.getController();
mapController.setZoom(19.0f);
mapController.setCenter(position);
// Set pin
mapMarker.setPosition(position);
}
public void updateModel(Geofavorite item) {
item.setName(binding.nameEt.getText().toString());
item.setComment(binding.descriptionEt.getText().toString());
item.setCategory(binding.categoryAt.getText().toString());
item.setDateModified(System.currentTimeMillis() / 1000);
}
public void setUpdating(boolean updating) {
binding.progress.setVisibility(updating ? View.VISIBLE : View.GONE);
}
public void setAccuracy(float accuracy) {
// Display accuracy in meters
binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + ""));
// Display accuracy in progress bar
int accuracyPercent = (int)accuracy > 100 ? 0 : Math.abs((int)accuracy - 100);
binding.accuracyProgress.setIndeterminate(false);
binding.accuracyProgress.setProgress(accuracyPercent);
}
public void setCategories(HashSet<String> categories) {
((CategoriesSpinnerAdapter)binding.categoryAt.getAdapter()).setCategoriesList(categories);
}
public void hideAccuracy() {
binding.accuracyTv.setVisibility(View.GONE);
binding.accuracyProgressContainer.setVisibility(View.GONE);
}
public void hideActions() {
binding.actionIcons.setVisibility(View.GONE);
}
public void setOnSubmitListener(OnSubmitListener listener) {
this.listener = listener;
}
public void onResume() {
binding.map.onResume();
}
public void onPause() {
binding.map.onPause();
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.submit_bt && this.listener != null) {
this.listener.onSubmit();
}
if (v.getId() == R.id.map_bt && this.listener != null) {
this.listener.onMapClicked();
}
if (v.getId() == R.id.back_bt && this.listener != null) {
this.listener.onBackPressed();
}
if (v.getId() == R.id.manual_pos_bt && this.listener != null) {
this.listener.onMapEditPressed();
}
// Actions
if (v.getId() == R.id.action_icon_share && this.listener != null) {
this.listener.onActionIconShareClicked();
}
if (v.getId() == R.id.action_icon_nav && this.listener != null) {
this.listener.onActionIconNavClicked();
}
if (v.getId() == R.id.action_icon_delete && this.listener != null) {
this.listener.onActionIconDeleteClicked();
}
}
}
protected interface OnSubmitListener {
void onSubmit();
void onMapClicked();
void onBackPressed();
void onMapEditPressed();
void onActionIconShareClicked();
void onActionIconNavClicked();
void onActionIconDeleteClicked();
}
}

View File

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

View File

@ -39,12 +39,13 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.nextcloud.android.sso.ui.UiExceptionManager;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.api.API;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
public class LoginActivity extends AppCompatActivity {
public class LoginActivity extends NextcloudMapsStyledActivity {
protected ApiProvider mApi;
protected ProgressBar progress;
protected Button button;
@ -114,9 +115,6 @@ public class LoginActivity extends AppCompatActivity {
}
private void accountAccessDone() {
Context l_context = getApplicationContext();
mApi = new ApiProvider(l_context);
Intent intent = new Intent(LoginActivity.this, MainActivity.class);
startActivity(intent);

View File

@ -21,18 +21,26 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView;
import org.threeten.bp.format.DateTimeFormatter;
import org.threeten.bp.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -41,18 +49,26 @@ import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.RecyclerViewAdapter> implements Filterable {
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.GeofavoriteViewHolder> implements Filterable {
public static final String TAG = "GeofavoriteAdapter";
public static final int SORT_BY_TITLE = 0;
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 ItemClickListener itemClickListener;
private DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT);
private List<Geofavorite> geofavoriteList = new ArrayList<>();
private List<Geofavorite> geofavoriteListFiltered = new ArrayList<>();
private int sortRule = SORT_BY_CREATED;
// Contains the position of the element containing the overflow menu clicked
private int overflowMenuSelectedPosition = -1;
public GeofavoriteAdapter(Context context, ItemClickListener itemClickListener) {
this.context = context;
this.itemClickListener = itemClickListener;
@ -83,17 +99,21 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
@NonNull
@Override
public RecyclerViewAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_note, parent, false);
return new RecyclerViewAdapter(view, itemClickListener);
public GeofavoriteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_geofav, parent, false);
return new GeofavoriteViewHolder(view, itemClickListener);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewAdapter holder, int position) {
public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
Geofavorite geofavorite = geofavoriteListFiltered.get(position);
holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
holder.tv_content.setText(geofavorite.getComment());
holder.tv_category.setText(geofavorite.categoryLetter());
holder.setCategoryColor(
geofavorite.categoryColor() == 0 ? context.getColor(R.color.defaultBrand) : geofavorite.categoryColor());
holder.tv_title.setText(Html.fromHtml(geofavorite.getName() == null ? "" : geofavorite.getName()));
holder.tv_content.setText(geofavorite.getComment() == null ? "" : geofavorite.getComment());
holder.tv_date.setText(geofavorite.getLocalDateCreated().format(dateFormatter));
}
@Override
@ -140,38 +160,98 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
}
};
class RecyclerViewAdapter extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tv_title, tv_content;
class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tv_category, tv_title, tv_content, tv_date;
ImageView bt_context_menu;
ImageView bt_nav;
ItemClickListener itemClickListener;
RecyclerViewAdapter(@NonNull View itemView, ItemClickListener itemClickListener) {
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
super(itemView);
tv_category = itemView.findViewById(R.id.tv_category);
tv_title = itemView.findViewById(R.id.title);
tv_content = itemView.findViewById(R.id.content);
tv_date = itemView.findViewById(R.id.date);
bt_context_menu = itemView.findViewById(R.id.geofav_context_menu_bt);
bt_nav = itemView.findViewById(R.id.geofav_nav_bt);
this.itemClickListener = itemClickListener;
itemView.setOnClickListener(this);
tv_content.setOnClickListener(this);
bt_context_menu.setOnClickListener(this);
bt_nav.setOnClickListener(this);
}
@Override
public void onClick(View view) {
itemClickListener.onItemClick(view, getAdapterPosition());
switch (view.getId()) {
case R.id.geofav_context_menu_bt:
onOverflowIconClicked(view, getBindingAdapterPosition());
break;
case R.id.geofav_nav_bt:
if (itemClickListener != null)
itemClickListener.onItemNavClick(get(getBindingAdapterPosition()));
break;
default:
if (itemClickListener != null)
itemClickListener.onItemClick(get(getBindingAdapterPosition()));
}
}
public void setCategoryColor(int ccTint) {
Drawable bg = DrawableCompat.wrap(this.tv_category.getContext().getDrawable(R.drawable.ic_list_pin));
this.tv_category.setBackground(bg);
DrawableCompat.setTint(bg, ccTint);
}
}
private void performSort() {
if (sortRule == SORT_BY_TITLE) {
Collections.sort(geofavoriteListFiltered, Geofavorite.ByTitleAZ);
} else if (sortRule == SORT_BY_CREATED) {
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);
}
}
public interface ItemClickListener {
void onItemClick(View view, int position);
private void onOverflowIconClicked(View view, int position) {
// Save selected item
overflowMenuSelectedPosition = position;
// Open menu
PopupMenu popup = new PopupMenu(context, view);
popup.inflate(R.menu.list_context_menu);
popup.setOnMenuItemClickListener(this::optionsItemSelected);
popup.show();
}
private boolean optionsItemSelected(MenuItem item) {
if (overflowMenuSelectedPosition < 0) {
Log.e(TAG, "No overflow menu selected position saved!");
return false;
}
Geofavorite gf = get(overflowMenuSelectedPosition);
overflowMenuSelectedPosition = -1;
if (item.getItemId() == R.id.list_context_menu_share && itemClickListener != null)
itemClickListener.onItemShareClick(gf);
if (item.getItemId() == R.id.list_context_menu_delete && itemClickListener != null)
itemClickListener.onItemDeleteClick(gf);
return true;
}
public interface ItemClickListener {
void onItemClick(Geofavorite item);
void onItemShareClick(Geofavorite item);
void onItemNavClick(Geofavorite item);
void onItemDeleteClick(Geofavorite item);
}
}

View File

@ -1,188 +0,0 @@
/*
* 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/>.
*/
package it.danieleverducci.nextcloudmaps.activity.main;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.databinding.ActivityGeofavoriteDetailBinding;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class GeofavoriteDetailActivity extends AppCompatActivity implements LocationListener, ActivityCompat.OnRequestPermissionsResultCallback {
public static final String TAG = "GeofavDetail";
public static final String ARG_GEOFAVORITE_ID = "geofavid";
private static final int PERMISSION_REQUEST_CODE = 9999;
private ActivityGeofavoriteDetailBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityGeofavoriteDetailBinding.inflate(getLayoutInflater());
setContentView(binding.root);
int id = getIntent().getIntExtra(ARG_GEOFAVORITE_ID, 0);
if (id == 0) {
// New geofavorite
Geofavorite gf = new Geofavorite();
gf.setDateCreated(System.currentTimeMillis());
gf.setDateModified(System.currentTimeMillis());
binding.setGeofavorite(gf);
// Precompile location
getLocation();
} else {
// TODO: Load geofavorite from cache for edit
}
}
/**
* Called when the submit button is clicked
* @param v The button
*/
public void onSubmit(View v) {
saveGeofavorite();
}
/**
* Checks fields and sends updated geofavorite to Nextcloud instance
*/
private void saveGeofavorite() {
Geofavorite gf = binding.getGeofavorite();
gf.setName(binding.nameEt.getText().toString());
gf.setComment(binding.descriptionEt.getText().toString());
gf.setDateModified(System.currentTimeMillis());
if (!gf.valid()) {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.incomplete_geofavorite, Toast.LENGTH_SHORT).show();
return;
}
Call<Geofavorite> call;
if (gf.getId() == 0) {
// New geofavorite
call = ApiProvider.getAPI().createGeofavorite(gf);
} else {
// Update existing geofavorite
call = ApiProvider.getAPI().updateGeofavorite(gf.getId(), gf);
}
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
finish();
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show();
Log.e(TAG, "Unable to update geofavorite: " + t.getMessage());
}
});
}
/**
* Obtains the current location (requesting user's permission, if necessary)
* and calls updateLocationField()
*/
private void getLocation() {
// Check if user granted location permission
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// User didn't grant permission. Ask it.
requestPermissions(new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_CODE);
return;
}
LocationManager locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
// Try to use last available location
Location lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
if (lastKnown != null)
updateLocationField(lastKnown);
// Register for location updates in case the user moves before saving
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this
);
}
/**
* Compiles the geofavorite location field with the provided location object
* @param location to set in the geofavorite
*/
private void updateLocationField(Location location) {
binding.getGeofavorite().setLat(location.getLatitude());
binding.getGeofavorite().setLng(location.getLongitude());
binding.notifyChange();
}
/** Location updates callbacks **/
@Override
public void onLocationChanged(@NonNull Location location) {
updateLocationField(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(@NonNull String provider) {}
@Override
public void onProviderDisabled(@NonNull String provider) {}
@Override
public void onPointerCaptureChanged(boolean hasCapture) {}
/** Position permission request result **/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE && permissions[0].equals(Manifest.permission.ACCESS_FINE_LOCATION)) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLocation();
} else {
Toast.makeText(this, R.string.location_permission_required, Toast.LENGTH_LONG).show();
finish();
}
}
}
}

View File

@ -17,13 +17,17 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatImageView;
@ -33,6 +37,7 @@ import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
@ -46,25 +51,31 @@ import java.util.ArrayList;
import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.NextcloudMapsStyledActivity;
import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity;
import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem;
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener;
import it.danieleverducci.nextcloudmaps.activity.mappicker.MapPickerActivity;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
import it.danieleverducci.nextcloudmaps.utils.IntentGenerator;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.*;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
import androidx.lifecycle.Observer;
public class MainActivity extends AppCompatActivity implements MainView, OnSortingOrderListener {
public class MainActivity extends NextcloudMapsStyledActivity 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 NAVIGATION_KEY_ADD_GEOFAVORITE = "add";
private static final String NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS = "add_from_gps";
private static final String NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP = "add_from_map";
private static final String NAVIGATION_KEY_SHOW_ABOUT = "about";
private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account";
@ -79,48 +90,97 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
private StaggeredGridLayoutManager layoutManager;
private FloatingActionButton fab;
private MainPresenter presenter;
private GeofavoriteAdapter geofavoriteAdapter;
private ItemClickListener itemClickListener;
private ItemClickListener rvItemClickListener;
private MainActivityViewModel mMainActivityViewModel;
private boolean isFabOpen = false;
NavigationAdapter navigationCommonAdapter;
private ApiProvider mApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
int sortRule = preferences.getInt(getString(R.string.setting_sort_by), SORT_BY_CREATED);
boolean gridViewEnabled = preferences.getBoolean(getString(R.string.setting_grid_view_enabled), true);
boolean gridViewEnabled = preferences.getBoolean(getString(R.string.setting_grid_view_enabled), false);
recyclerView = findViewById(R.id.recycler_view);
layoutManager = new StaggeredGridLayoutManager(gridViewEnabled ? 2 : 1, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
presenter = new MainPresenter(this);
rvItemClickListener = new ItemClickListener() {
@Override
public void onItemClick(Geofavorite item) {
showGeofavoriteDetailActivity(item);
}
itemClickListener = ((view, position) -> {
Geofavorite geofavorite = geofavoriteAdapter.get(position);
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(geofavorite.getGeoUri());
startActivity(i);
});
@Override
public void onItemShareClick(Geofavorite item) {
startActivity(Intent.createChooser(IntentGenerator.newShareIntent(MainActivity.this, item), getString(R.string.share_via)));
}
geofavoriteAdapter = new GeofavoriteAdapter(getApplicationContext(), itemClickListener);
@Override
public void onItemNavClick(Geofavorite item) {
startActivity(IntentGenerator.newGeoUriIntent(MainActivity.this, item));
}
@Override
public void onItemDeleteClick(Geofavorite item) {
showGeofavoriteDeteleDialog(item);
}
};
geofavoriteAdapter = new GeofavoriteAdapter(this, rvItemClickListener);
recyclerView.setAdapter(geofavoriteAdapter);
geofavoriteAdapter.setSortRule(sortRule);
swipeRefresh = findViewById(R.id.swipe_refresh);
swipeRefresh.setOnRefreshListener(() -> presenter.getGeofavorites());
fab = findViewById(R.id.add);
fab.setOnClickListener(view -> addGeofavorite());
mMainActivityViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
mMainActivityViewModel.init(getApplicationContext());
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 == null || !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.setOnRefreshListener(() ->
mMainActivityViewModel.updateGeofavorites());
fab = findViewById(R.id.open_fab);
fab.setOnClickListener(view -> openFab(!this.isFabOpen));
fab = findViewById(R.id.add_from_gps);
fab.setOnClickListener(view -> addGeofavoriteFromGps());
fab = findViewById(R.id.add_from_map);
fab.setOnClickListener(view -> addGeofavoriteFromMap());
toolbar = findViewById(R.id.toolbar);
homeToolbar = findViewById(R.id.home_toolbar);
@ -167,16 +227,12 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
updateSortingIcon(sortRule);
updateGridIcon(gridViewEnabled);
mApi = new ApiProvider(getApplicationContext());
}
@Override
protected void onStart() {
super.onStart();
// Update list
presenter.getGeofavorites();
protected void onPause() {
openFab(false);
super.onPause();
}
private void setupNavigationMenu() {
@ -184,8 +240,11 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
navigationCommonAdapter = new NavigationAdapter(this, item -> {
switch (item.id) {
case NAVIGATION_KEY_ADD_GEOFAVORITE:
addGeofavorite();
case NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS:
addGeofavoriteFromGps();
break;
case NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP:
addGeofavoriteFromMap();
break;
case NAVIGATION_KEY_SHOW_ABOUT:
show_about();
@ -196,7 +255,8 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
}
});
navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE, getString(R.string.new_geobookmark), R.drawable.ic_add));
navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_GPS, getString(R.string.new_geobookmark_gps), R.drawable.ic_add_gps));
navItems.add(new NavigationItem(NAVIGATION_KEY_ADD_GEOFAVORITE_FROM_MAP, getString(R.string.new_geobookmark_map), R.drawable.ic_add_map));
navItems.add(new NavigationItem(NAVIGATION_KEY_SHOW_ABOUT, getString(R.string.about), R.drawable.ic_info_grey));
navItems.add(new NavigationItem(NAVIGATION_KEY_SWITCH_ACCOUNT, getString(R.string.switch_account), R.drawable.ic_logout_grey));
navigationCommonAdapter.setItems(navItems);
@ -221,19 +281,16 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
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 addGeofavoriteFromGps() {
startActivity(
new Intent(this, GeofavoriteDetailActivity.class)
);
}
private void addGeofavorite() {
startActivityForResult(
new Intent(this, GeofavoriteDetailActivity.class), INTENT_ADD);
private void addGeofavoriteFromMap() {
startActivity(
new Intent(this, MapPickerActivity.class)
);
}
private void show_about() {
@ -241,29 +298,11 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
}
private void switch_account() {
ApiProvider.logout();
SingleAccountHelper.setCurrentAccount(this, null);
Intent intent = new Intent(MainActivity.this, LoginActivity.class);
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 onErrorLoading(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show();
finish();
}
@Override
@ -283,6 +322,12 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
case SORT_BY_CREATED:
sortButton.setImageResource(R.drawable.ic_modification_asc);
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;
}
}
@ -298,4 +343,48 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
viewButton.setImageResource(gridEnabled ? R.drawable.ic_view_list : R.drawable.ic_view_module);
}
private void showGeofavoriteDeteleDialog(Geofavorite item) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName() != null ? item.getName() : ""))
.setTitle(R.string.dialog_delete_title)
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
mMainActivityViewModel.deleteGeofavorite(item);
dialog.dismiss();
}
})
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog ad = builder.create();
ad.show();
}
private void showGeofavoriteDetailActivity(Geofavorite item) {
Intent i = new Intent(this, GeofavoriteDetailActivity.class);
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item.getId());
startActivity(i);
}
private void openFab(boolean open) {
View fab = findViewById(R.id.open_fab);
View addFromGpsFab = findViewById(R.id.add_from_gps);
View addFromMapFab = findViewById(R.id.add_from_map);
if (open) {
this.isFabOpen = true;
fab.animate().rotation(45.0f);
addFromGpsFab.animate().translationY(-getResources().getDimension(R.dimen.fab_vertical_offset));
addFromMapFab.animate().translationY(-getResources().getDimension(R.dimen.fab_vertical_offset) * 2);
} else {
this.isFabOpen = false;
fab.animate().rotation(0f);
addFromGpsFab.animate().translationY(0);
addFromMapFab.animate().translationY(0);
}
}
}

View File

@ -0,0 +1,42 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context;
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(Context applicationContext) {
mRepo = GeofavoriteRepository.getInstance(applicationContext);
}
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();
}
}

View File

@ -1,66 +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());
}
});
}
@Override
public void onFailure(@NonNull Call<List<Geofavorite>> call, @NonNull Throwable t) {
((AppCompatActivity) view).runOnUiThread(() -> {
view.hideLoading();
view.onErrorLoading(t.getLocalizedMessage());
});
}
});
}
}

View File

@ -1,32 +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 onErrorLoading(String message);
}

View File

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

View File

@ -32,6 +32,7 @@ import android.widget.ImageButton;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.DialogFragment;
@ -91,13 +92,14 @@ public class SortingOrderDialogFragment extends DialogFragment {
/**
* find all relevant UI elements and set their values.
* TODO: this is REALLY ugly.
*
* @param view the parent view
*/
private void setupDialogElements(View view) {
mCancel = view.findViewById(R.id.cancel);
mTaggedViews = new View[4];
mTaggedViews = new View[8];
mTaggedViews[0] = view.findViewById(R.id.sortByTitleAscending);
mTaggedViews[0].setTag(GeofavoriteAdapter.SORT_BY_TITLE);
mTaggedViews[1] = view.findViewById(R.id.sortByTitleAscendingText);
@ -106,6 +108,14 @@ public class SortingOrderDialogFragment extends DialogFragment {
mTaggedViews[2].setTag(GeofavoriteAdapter.SORT_BY_CREATED);
mTaggedViews[3] = view.findViewById(R.id.sortByCreationDateDescendingText);
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();
}
@ -121,10 +131,10 @@ public class SortingOrderDialogFragment extends DialogFragment {
if (view instanceof ImageButton) {
Drawable normalDrawable = ((ImageButton) view).getDrawable();
Drawable wrapDrawable = DrawableCompat.wrap(normalDrawable);
DrawableCompat.setTint(wrapDrawable, this.getResources().getColor(R.color.defaultTint));
DrawableCompat.setTint(wrapDrawable, ContextCompat.getColor(getContext(), R.color.selector_item_selected));
}
if (view instanceof TextView) {
((TextView)view).setTextColor(this.getResources().getColor(R.color.defaultTint));
((TextView)view).setTextColor(ContextCompat.getColor(getContext(), R.color.selector_item_selected));
((TextView)view).setTypeface(Typeface.DEFAULT_BOLD);
}
}

View File

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

View File

@ -24,6 +24,7 @@ import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.gson.GsonBuilder;
import com.nextcloud.android.sso.api.NextcloudAPI;
@ -35,47 +36,27 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import retrofit2.NextcloudRetrofitApiBuilder;
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 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) {
this.context = context;
initSsoApi(new NextcloudAPI.ApiConnectedListener() {
@Override
public void onConnected() {
// Ignore..
mApi = new NextcloudRetrofitApiBuilder(nextcloudAPI, API.mApiEndpoint).create(API.class);
} catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
Log.d(TAG, "setAccout() called with: ex = [" + e + "]");
}
@Override
public void onError(Exception ex) {
// Ignore...
}
});
}
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;
}
public static String getAccountName() {
return ssoAccountName;
public static void logout() {
mApi = null;
}
}

View File

@ -20,21 +20,28 @@
package it.danieleverducci.nextcloudmaps.model;
import android.graphics.Color;
import android.net.Uri;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.BindingAdapter;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import org.threeten.bp.Instant;
import org.threeten.bp.LocalDate;
import org.threeten.bp.ZoneId;
import java.io.Serializable;
import java.util.Comparator;
import java.util.Date;
import it.danieleverducci.nextcloudmaps.utils.GeoUriParser;
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:
* {
@ -90,20 +97,26 @@ public class Geofavorite implements Serializable {
}
public void setName(String name) {
this.name = name;
if (!name.equals(this.name))
this.name = name;
}
public long getDateModified() {
return dateModified;
}
public void setDateModified(long dateModified) {
this.dateModified = dateModified;
}
public LocalDate getLocalDateModified() {
return Instant.ofEpochSecond(getDateCreated()).atZone(ZoneId.systemDefault()).toLocalDate();
}
public long getDateCreated() {
return dateCreated;
}
public LocalDate getLocalDateCreated() {
return Instant.ofEpochSecond(getDateCreated()).atZone(ZoneId.systemDefault()).toLocalDate();
}
public void setDateCreated(long dateCreated) {
this.dateCreated = dateCreated;
@ -112,7 +125,6 @@ public class Geofavorite implements Serializable {
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
@ -120,7 +132,6 @@ public class Geofavorite implements Serializable {
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
@ -128,7 +139,6 @@ public class Geofavorite implements Serializable {
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
@ -137,26 +147,77 @@ public class Geofavorite implements Serializable {
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name);
/**
* 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 static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id;
public Uri getGeoUri() {
return Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")");
public String getCoordinatesString() {
return this.lat + " N, " + this.lng + " E";
}
@BindingAdapter("formatDate")
public static void formatDate(@NonNull TextView textView, long timestamp) {
textView.setText((new Date(timestamp)).toString());
public Uri getGeoUri() {
return GeoUriParser.createGeoUri(this.lat, this.lng, this.name);
}
public Uri getGmapsUri() {
return GeoUriParser.createGmapsUri(this.lat, this.lng);
}
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 == null || this.category.equals(DEFAULT_CATEGORY) || this.category.length() == 0)
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 || category.equals(DEFAULT_CATEGORY))
return "\u2022";
return category.substring(0,1);
}
@NonNull
@ -164,4 +225,5 @@ public class Geofavorite implements Serializable {
public String toString() {
return "[" + getName() + " (" + getLat() + "," + getLng() + ")]";
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -1,27 +0,0 @@
<!--
~ 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/>.
-->
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="32"
android:viewportHeight="32">
<path
android:pathData="m6,2c-2.216,0 -4,1.784 -4,4v20c0,2.216 1.784,4 4,4h20c2.216,0 4,-1.784 4,-4v-16.719l-0.906,0.9068 -5.282,-5.2818 2.907,-2.9062h-20.719zM21.812,6.9062l5.282,5.2818 -8.313,8.312 -8.781,3.5 3.5,-8.781 8.312,-8.3128zM14.406,16.094l-2.656,4.406 1.75,1.75 4.406,-2.656 -3.5,-3.5z"
android:fillColor="#FFF"/>
</vector>

View 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/defaultBrandAlpha"/>
</shape>
</item>
</layer-list>

View 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="M20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06c-1.13,0.12 -2.19,0.46 -3.16,0.97l1.5,1.5C10.16,5.19 11.06,5 12,5c3.87,0 7,3.13 7,7 0,0.94 -0.19,1.84 -0.52,2.65l1.5,1.5c0.5,-0.96 0.84,-2.02 0.97,-3.15L23,13v-2h-2.06zM3,4.27l2.04,2.04C3.97,7.62 3.25,9.23 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21 21,19.73 4.27,3 3,4.27zM16.27,17.54C15.09,18.45 13.61,19 12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46,-4.27l9.81,9.81z"/>
</vector>

View File

@ -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="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM20.94,11c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94L13,1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11L1,11v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94L11,23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94L23,13v-2h-2.06zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z"/>
</vector>

View File

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

View File

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

View File

@ -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>

View 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>

View File

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

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View File

@ -2,13 +2,14 @@
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="1.5525"
android:scaleY="1.5525"
android:translateX="29.16"
android:translateY="29.16">
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:pathData="m6,2c-2.216,0 -4,1.784 -4,4v20c0,2.216 1.784,4 4,4h20c2.216,0 4,-1.784 4,-4v-16.719l-0.906,0.9068 -5.282,-5.2818 2.907,-2.9062h-20.719zM21.812,6.9062l5.282,5.2818 -8.313,8.312 -8.781,3.5 3.5,-8.781 8.312,-8.3128zM14.406,16.094l-2.656,4.406 1.75,1.75 4.406,-2.656 -3.5,-3.5z"
android:fillColor="#FFF"/>
android:fillColor="@android:color/white"
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</group>
</vector>

View File

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

View File

@ -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="M13.95,13H9V8.05l5.61,-5.61C13.78,2.16 12.9,2 12,2c-4.2,0 -8,3.22 -8,8.2c0,3.32 2.67,7.25 8,11.8c5.33,-4.55 8,-8.48 8,-11.8c0,-1.01 -0.16,-1.94 -0.45,-2.8L13.95,13z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M11,11l2.12,0l6.16,-6.16l-2.12,-2.12l-6.16,6.16z"/>
<path
android:fillColor="@android:color/white"
android:pathData="M20.71,2L20,1.29C19.8,1.1 19.55,1 19.29,1c-0.13,0 -0.48,0.07 -0.71,0.29l-0.72,0.72l2.12,2.12l0.72,-0.72C21.1,3.02 21.1,2.39 20.71,2z"/>
</vector>

View File

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

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

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

View File

@ -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="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
</vector>

View File

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

View File

@ -115,6 +115,20 @@
android:layout_height="wrap_content"
android:padding="10dp"
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>
</com.google.android.material.appbar.AppBarLayout>

View File

@ -2,29 +2,87 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="geofavorite"
type="it.danieleverducci.nextcloudmaps.model.Geofavorite"/>
</data>
<ScrollView
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/root">
<LinearLayout
android:orientation="vertical"
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="250dp"
android:fitsSystemWindows="true">
<ImageView
android:id="@+id/map"
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
android:layout_width="match_parent"
android:layout_height="200dp"
android:padding="50dp"
app:srcCompat="@drawable/ic_app"
android:background="@color/defaultBrand" />
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:contentScrim="@color/defaultBrand"
app:layout_scrollFlags="scroll|snap|exitUntilCollapsed"
app:title="@string/new_geobookmark"
app:expandedTitleTextAppearance="@style/TextAppearance.GeofavoriteDetail.Header.Expanded"
app:collapsedTitleTextAppearance="@style/TextAppearance.GeofavoriteDetail.Header.Collapsed">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_collapseMode="parallax">
<org.osmdroid.views.MapView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:clickable="false"/>
<View
android:id="@+id/map_bt"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:contentInsetStart="0dp"
app:layout_collapseMode="pin"
android:background="@android:color/transparent">
<!-- Back button -->
<ImageView
android:id="@+id/back_bt"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="start"
android:padding="16dp"
android:src="@drawable/ic_back_grey"
app:tint="@color/white"
android:background="@drawable/floating_semitransparent_button_background"/>
<!-- Manual position button -->
<ImageView
android:id="@+id/manual_pos_bt"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="end"
android:padding="16dp"
android:src="@drawable/ic_manual_pos"
app:tint="@color/white"
android:background="@drawable/floating_semitransparent_button_background"
android:visibility="gone"/> <!-- TODO: Implement edit -->
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<LinearLayout
android:layout_width="match_parent"
@ -32,33 +90,132 @@
android:orientation="vertical"
android:padding="20dp">
<LinearLayout
android:id="@+id/accuracy_progress_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:src="@drawable/ic_accuracy_fail"
app:tint="@color/disabled"/>
<ProgressBar
android:id="@+id/accuracy_progress"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:indeterminate="true"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" />
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:src="@drawable/ic_accuracy_ok"/>
</LinearLayout>
<TextView
android:id="@+id/accuracy_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:textAlignment="center"
android:text="@string/accuracy_nosignal"
android:textColor="@color/defaultBrand"/>
<LinearLayout
android:id="@+id/action_icons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/action_icon_share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_share"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_nav"
app:tint="@color/defaultBrand"/>
<ImageView
android:id="@+id/action_icon_delete"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:src="@drawable/ic_delete_grey"
app:tint="@color/defaultBrand"
android:visibility="gone"/> <!-- TODO Implement delete -->
</LinearLayout>
<EditText
android:id="@+id/name_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:ems="10"
android:hint="@string/name"
android:lines="1"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@{geofavorite.name}"/>
android:textColor="@color/defaultBrand"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:background="@android:color/transparent"
android:drawableLeft="@drawable/ic_edit"
android:drawablePadding="5dp"
android:drawableTint="@color/defaultBrand"/>
<EditText
android:id="@+id/description_et"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
android:hint="@string/description"
android:ellipsize="end"
android:text="@{geofavorite.comment}" />
android:maxLines="10"
android:background="@android:color/transparent"
android:drawableLeft="@drawable/ic_edit"
android:drawablePadding="5dp"
android:drawableTint="@color/defaultBrand"
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
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginTop="40dp"
android:textStyle="bold"
android:text="@string/created" />
@ -66,8 +223,20 @@
android:id="@+id/created_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd"
app:formatDate="@{geofavorite.dateCreated}" />
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textStyle="bold"
android:text="@string/modified" />
<TextView
android:id="@+id/modified_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
@ -80,8 +249,14 @@
android:id="@+id/coords_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd"
android:text="@{geofavorite.lat +` °N, ` + geofavorite.lng + ` °E`}" />
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
android:id="@+id/submit_bt"
@ -95,6 +270,7 @@
</LinearLayout>
</LinearLayout>
</ScrollView>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

View File

@ -21,7 +21,6 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_list_view"
android:background="@color/primary"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -65,7 +64,6 @@
android:layout_marginTop="@dimen/spacer_1hx"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_marginBottom="@dimen/spacer_1hx"
app:cardBackgroundColor="@color/appbar"
app:cardCornerRadius="@dimen/spacer_1x"
app:cardElevation="2dp"
app:strokeWidth="0dp">
@ -77,14 +75,15 @@
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menu_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:background="?attr/selectableItemBackgroundBorderless"
android:layout_gravity="center_vertical|start"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:tint="@color/text_color"
android:src="@drawable/ic_menu_grey"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_text"
@ -106,8 +105,11 @@
android:layout_gravity="center_vertical|end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:padding="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x"
android:tint="@color/text_color"
android:translationX="@dimen/spacer_1x"
android:src="@drawable/ic_alphabetical_asc" />
<androidx.appcompat.widget.AppCompatImageView
@ -117,10 +119,13 @@
android:layout_gravity="center_vertical|end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:padding="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x" android:tint="@color/text_color"
android:translationX="@dimen/spacer_1x"
android:src="@drawable/ic_view_module" />
android:src="@drawable/ic_view_module"
android:visibility="gone"/> <!-- TODO: Replace with Map View icon -->
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@ -145,17 +150,40 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2"
app:spanCount="1"
tools:itemCount="3"
tools:listitem="@layout/item_note">
tools:listitem="@layout/item_geofav">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- Add from map FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add"
android:id="@+id/add_from_map"
android:layout_margin="24dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_add_map"
app:backgroundTint="@color/defaultBrand"/>
<!-- Add from current position FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add_from_gps"
android:layout_margin="24dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:fabSize="mini"
android:src="@drawable/ic_add_gps"
app:backgroundTint="@color/defaultBrand"/>
<!-- Main FAB -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/open_fab"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"

View File

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

View File

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

View File

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

View File

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

View File

@ -1,70 +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/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="12dp"
android:clickable="true"
android:focusable="true">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0"
android:layout_marginRight="10dp"
android:src="@mipmap/ic_launcher"/>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/note_font_size_item_title"
android:textStyle="bold"
android:singleLine="true"
android:maxLines="1"
android:lines="1"
android:ellipsize="end"
tools:text="@tools:sample/lorem/random">
</TextView>
<TextView
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:textSize="@dimen/note_font_size_item_content"
android:maxLines="2"
android:lines="2"
tools:text="@tools:sample/lorem/random">
</TextView>
</LinearLayout>
</LinearLayout>

View File

@ -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>

View File

@ -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

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

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

View File

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

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text_color">#eee</color>
<color name="disabled">#888</color>
<color name="defaultBackground">#000</color>
</resources>

View File

@ -17,15 +17,23 @@
-->
<resources>
<!-- Colors -->
<!-- Generic Colors -->
<color name="primary">#ffffff</color>
<color name="accent">#121212</color>
<color name="transparent">#00000000</color>
<color name="defaultBrand">#0082C9</color>
<color name="defaultBrandAlpha">#550082C9</color>
<color name="disabled">#666</color>
<color name="systemBar">@color/defaultBackground</color>
<color name="defaultBackground">#fff</color>
<color name="appbar">@android:color/white</color>
<!-- List Colors -->
<color name="text_color">#333</color>
<color name="secondary_text_color">@color/disabled</color>
<color name="defaultTint">#202124</color>
<!-- Selectors text/tint colors -->
<color name="selector_item_selected">@color/defaultBrand</color>
<color name="selector_item_unselected">@color/disabled</color>
<!-- Generic Colors -->
<color name="white">#fff</color>
</resources>

View File

@ -28,14 +28,15 @@
<dimen name="drawer_header_logo_size">42dp</dimen>
<!-- Font Sizes -->
<dimen name="note_font_size_item_title">18sp</dimen>
<dimen name="note_font_size_item_content">14sp</dimen>
<dimen name="zero">0dp</dimen>
<dimen name="standard_margin">16dp</dimen>
<dimen name="standard_half_margin">8dp</dimen>
<dimen name="standard_padding">16dp</dimen>
<dimen name="standard_half_padding">8dp</dimen>
<dimen name="two_line_primary_text_size">16sp</dimen>
<dimen name="two_line_secondary_text_size">12sp</dimen>
<!-- FAB dimensions -->
<dimen name="fab_vertical_offset">75dp</dimen>
</resources>

View File

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

View File

@ -17,34 +17,57 @@
<resources>
<!-- App strings -->
<string name="app_name">Maps Geobookmarks</string>
<string name="welcome">Welcome to Nextcloud Maps Geobookmarks</string>
<string name="app_name">Maps Geofavorites</string>
<string name="welcome">Welcome to Nextcloud Maps Geofavorites</string>
<!-- Login Activity -->
<string name="choose_account">Choose account</string>
<!-- Geobookmarks list -->
<string name="new_geobookmark">New geobookmark</string>
<!-- Geofavorites list -->
<string name="new_geobookmark">New geofavorite</string>
<string name="about">About</string>
<string name="switch_account">Switch account</string>
<string name="list_mode">List</string>
<string name="search_in_all">Search by name</string>
<string name="share_via">Share via</string>
<string name="share_message">Check out this place: {lat}°N, {lng}°E https://www.openstreetmap.org/#map=17/{lat}/{lng}</string>
<string name="list_context_menu_share">Share</string>
<string name="list_context_menu_delete">Delete</string>
<string name="dialog_delete_title">Delete geofavorite</string>
<string name="dialog_delete_message">You are about to delete geofavorite {name}. Proceed?</string>
<string name="dialog_delete_delete">Delete</string>
<string name="dialog_delete_cancel">Maintain</string>
<string name="list_geofavorite_deleted">Geofavorite deleted</string>
<string name="list_geofavorite_connection_error">Unable to obtain geofavorites list</string>
<!-- Sort dialog -->
<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_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 -->
<!-- Geofavorites detail -->
<string name="name">Name</string>
<string name="description">Description</string>
<string name="created">Created</string>
<string name="modified">Modified</string>
<string name="category">Category</string>
<string name="coords">Coordinates</string>
<string name="accuracy">Accuracy: {accuracy} m</string>
<string name="accuracy_nosignal">No GPS signal!</string>
<string name="location_permission_required">Location permission is required to create a geofavorite.</string>
<string name="confirm">Save</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>
<!-- Map picker activity -->
<string name="coordinates_parse_error">Coordinates should be in format xx.xxxxxx</string>
<string name="coordinates_invalid_error">Invalid coordinates</string>
<string name="coordinates_north">°N</string>
<string name="coordinates_east">°E</string>
<!-- About -->
<string name="about_version_title">Version</string>
@ -53,6 +76,8 @@
<string name="about_source">This project is hosted on GitHub: &lt;a href="%1$s">%1$s&lt;/a></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: &lt;a href="%1$s">%1$s&lt;/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 &lt;a href="%1$s">OpenStreetMap&lt;/a>, as this application could not exist without them.</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_button">View license</string>
@ -63,11 +88,16 @@
<!-- URLs -->
<string name="url_source" translatable="false">https://github.com/penguin86/nextcloud-maps-client</string>
<string name="url_issues" translatable="false">https://github.com/penguin86/nextcloud-maps-client/issues/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_maps" translatable="false">https://donate.openstreetmap.org</string>
<!-- Settings -->
<string name="setting_sort_by">SETTING_SORT_BY</string>
<string name="setting_grid_view_enabled">SETTING_GRID_VIEW_ENABLED</string>
<!-- Menu -->
<string name="new_geobookmark_gps">New from current position</string>
<string name="new_geobookmark_map">New from map</string>
</resources>

View File

@ -18,34 +18,34 @@
<resources>
<!-- Base application theme. -->
<style name="BaseTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/primary</item>
<item name="colorPrimaryDark">@color/primary</item>
<item name="colorAccent">@color/accent</item>
<item name="android:actionModeBackground">?attr/colorPrimary</item>
<item name="colorAccent">@color/defaultBrand</item>
<item name="colorControlNormal">?attr/colorAccent</item>
<item name="windowActionModeOverlay">true</item>
<item name="toolbarStyle">@style/toolbarStyle</item>
<!-- App bar -->
<item name="colorPrimary">@color/systemBar</item>
<!-- Generic background -->
<item name="android:windowBackground">@color/defaultBackground</item>
</style>
<style name="AppTheme" parent="BaseTheme"/>
<style name="EditorTheme" parent="Theme.AppCompat.DayNight.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@android:color/transparent</item>
<item name="colorPrimaryDark">@color/accent</item>
<item name="colorAccent">@color/accent</item>
</style>
<style name="SplashTheme" parent="Theme.AppCompat.Light.NoActionBar">
<style name="SplashTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
</style>
<style name="toolbarStyle" parent="@style/Widget.AppCompat.Toolbar">
<item name="android:background">?attr/colorPrimary</item>
<!-- Geofavorite detail text appearances -->
<style name="TextAppearance.GeofavoriteDetail.Header.Expanded" parent="TextAppearance.Design.CollapsingToolbar.Expanded">
<item name="android:textSize">36sp</item>
<item name="android:textColor">@color/defaultBrand</item>
</style>
<style name="TextAppearance.GeofavoriteDetail.Header.Collapsed" parent="TextAppearance.AppCompat.Title">
<item name="android:textColor">@color/white</item>
</style>
</resources>

View File

@ -25,7 +25,7 @@ buildscript {
jcenter()
}
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
// in the individual module build.gradle files

View File

@ -1 +1 @@
First alpha release: working geobookmarks list, open geobookmarks in other apps via geo link, add geobookmark on current location.
First alpha release: working geobookmarks list, open geobookmarks in other apps via geo link, add geofavorite on current location.

View File

@ -0,0 +1 @@
Added geofavorite detail and deletion

View File

@ -0,0 +1 @@
Added map in geofavorite detail

View File

@ -0,0 +1 @@
First categories implementation

View File

@ -0,0 +1 @@
Fixed bug when opening newly-created element

View File

@ -0,0 +1,5 @@
Add geofavorite from map or coordinates
Night mode (dark theme)
New, less ambiguous, category icons (the old circle with letter inside is conventionally associated with user accounts)
Graphic fixes
Italian translation

View File

@ -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)

View File

@ -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...).
A new geobookmark can be created on current location.
UNOFFICIAL and FOSS Nextcloud Maps client at its earliest stages of developement. Shows your Nextcloud Maps geofavorites list.
Geofavorites can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
A new geofavorite can be created on current location, by sharing a "geo:" uri from another app or manually picking from the map.
Requires Maps app to be installed on the Nextcloud instance.
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

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