24 Commits

Author SHA1 Message Date
be910bcabe WIP refactoring geofavorites list activity into fragment 2021-09-15 09:16:40 +02:00
fdb314c48b Updated README 2021-09-14 20:27:14 +02:00
b8541c994a Added back button in detail 2021-09-14 19:59:08 +02:00
2a62da3cf2 Added pin in geofavorite detail map 2021-09-14 19:07:44 +02:00
7c5de6ccd7 WIP Implementing map in geopoint detail 2021-09-14 09:27:30 +02:00
4ff6c569d0 Downgraded to Java 1.8 to comply with fdroid build system 2021-09-12 08:32:23 +02:00
c58041fc20 Added screenshots in README 2021-09-10 19:29:32 +02:00
b3169f6525 Added screenshots in README 2021-09-10 19:25:38 +02:00
6bca4462a3 Connection error management 2021-09-10 19:02:28 +02:00
e558fa28b5 Release v0.2 2021-09-10 09:45:46 +02:00
809616aefa Working delete 2021-09-08 09:29:13 +02:00
b8551cbb13 Working detail from list! 2021-09-08 08:20:21 +02:00
9161b61b6e Added deletion, but the dialog is never displayed 2021-09-07 21:22:30 +02:00
9c83707bfc Working context menu callbacks 2021-09-07 20:52:37 +02:00
bd3ca1740a Layout fixes 2021-09-07 19:48:31 +02:00
5d4e56135e Updated gradle, added link in share 2021-09-07 19:42:36 +02:00
bac3639c3f WIP list context menu 2021-09-07 08:57:56 +02:00
e89ce8e7da Refactoring, cleaning, share button 2021-09-07 08:24:48 +02:00
79ad2633e7 Renamed listitem layout file 2021-08-30 07:40:58 +02:00
a327cf7a21 Geobookmarks shown as list by default 2021-08-30 07:37:51 +02:00
b3c63df32e Gave up resizing images in readme, removed screenshots 2021-08-29 09:09:08 +02:00
76e99b7700 Resize images in readme 2021-08-29 09:04:56 +02:00
369c842a14 Resize images in readme 2021-08-29 09:01:15 +02:00
29bf3c80d3 Images in readme 2021-08-29 08:56:13 +02:00
55 changed files with 1243 additions and 739 deletions

2
.idea/compiler.xml generated
View File

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

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

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

17
.idea/misc.xml generated
View File

@ -1,4 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" /> <component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
<entry key="app/src/main/res/layout/activity_geofavorite_detail.xml" value="0.4" />
<entry key="app/src/main/res/layout/activity_list_view.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5218150087260035" />
<entry key="app/src/main/res/layout/fragment_geofavorite_list.xml" value="0.1734375" />
<entry key="app/src/main/res/layout/fragment_map.xml" value="0.19010416666666666" />
<entry key="app/src/main/res/layout/item_geofav.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/menu/list_context_menu.xml" value="0.41944444444444445" />
</map>
</option>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project> </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,3 +1,5 @@
![Nextcloud Maps Geobookmarks Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
# Nextcloud Maps Geobookmarks Android app # Nextcloud Maps Geobookmarks Android app
Android app to show your Nextcloud Maps geobookmarks list. Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...). Android app to show your Nextcloud Maps geobookmarks list. Geobookmarks can be opened in all apps supporting geo links (i.e. Google Maps, Organic Maps etc...).
@ -7,3 +9,7 @@ A new geobookmark can be created on current location.
This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://github.com/matiasdelellis/app-tutorial-android) to implement [Nextcloud single sign on](https://github.com/nextcloud/Android-SingleSignOn). This work is heavily based on [matiasdelellis's Nextcloud SSO example](https://github.com/matiasdelellis/app-tutorial-android) to implement [Nextcloud single sign on](https://github.com/nextcloud/Android-SingleSignOn).
![Screenshot 1](screenshots/1.png) ![Screenshot 1](screenshots/2.png)
Download it from [the releases page](https://github.com/penguin86/nextcloud-maps-client/releases)

View File

@ -24,8 +24,8 @@ android {
applicationId "it.danieleverducci.nextcloudmaps" applicationId "it.danieleverducci.nextcloudmaps"
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion 30
versionCode 1 versionCode 2
versionName "0.1" versionName "0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
@ -55,15 +55,16 @@ dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"]) implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'com.android.support:design:30.0.1' implementation 'com.android.support:design:30.0.1'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "androidx.cardview:cardview:1.0.0" 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" implementation "androidx.preference:preference:1.1.1"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// Retrofif2 // Retrofif2
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:retrofit:2.9.0'
@ -71,4 +72,8 @@ dependencies {
// Nextcloud SSO // Nextcloud SSO
implementation "com.github.nextcloud:Android-SingleSignOn:0.5.6" implementation "com.github.nextcloud:Android-SingleSignOn:0.5.6"
// OSMDroid
compile 'org.osmdroid:osmdroid-android:6.1.10'
} }

View File

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

View File

@ -30,6 +30,7 @@
<application <application
android:allowBackup="true" android:allowBackup="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme"> android:theme="@style/AppTheme">
@ -48,7 +49,7 @@
</activity> </activity>
<activity <activity
android:name=".activity.main.GeofavoriteDetailActivity" android:name=".activity.detail.GeofavoriteDetailActivity"
android:theme="@style/AppTheme"/> android:theme="@style/AppTheme"/>
<activity <activity

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,355 @@
/*
* 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.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.preference.PreferenceManager;
import org.osmdroid.api.IMapController;
import org.osmdroid.config.Configuration;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.CustomZoomButtonsController;
import org.osmdroid.views.overlay.Marker;
import java.util.Date;
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 DEFAULT_CATEGORY = "Personal";
public static final int MINIMUM_ACCEPTABLE_ACCURACY = 50; // In meters
public static final String ARG_GEOFAVORITE = "geofav";
private static final int PERMISSION_REQUEST_CODE = 9999;
private ViewHolder mViewHolder;
private Geofavorite mGeofavorite;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// OSMDroid config
Configuration.getInstance().load(getApplicationContext(),
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));
mViewHolder = new ViewHolder(getLayoutInflater());
setContentView(mViewHolder.getRootView());
mViewHolder.setOnSubmitListener(new OnSubmitListener() {
@Override
public void onBackPressed() {
finish();
}
@Override
public void onSubmit() {
saveGeofavorite();
}
@Override
public void onMapClicked() {
Toast.makeText(GeofavoriteDetailActivity.this, "TODO: Open map activity with pin", Toast.LENGTH_SHORT).show();
}
});
if (getIntent().hasExtra(ARG_GEOFAVORITE)) {
// Opening geofavorite from list
mGeofavorite = (Geofavorite) getIntent().getSerializableExtra(ARG_GEOFAVORITE);
mViewHolder.hideAccuracy();
} else {
// New geofavorite
mGeofavorite = new Geofavorite();
mGeofavorite.setCategory(DEFAULT_CATEGORY);
mGeofavorite.setDateCreated(System.currentTimeMillis());
mGeofavorite.setDateModified(System.currentTimeMillis());
// Precompile location
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;
}
Call<Geofavorite> call;
if (mGeofavorite.getId() == 0) {
// New geofavorite
call = ApiProvider.getAPI().createGeofavorite(mGeofavorite);
} else {
// Update existing geofavorite
call = ApiProvider.getAPI().updateGeofavorite(mGeofavorite.getId(), mGeofavorite);
}
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
if (response.isSuccessful())
finish();
else
onGeofavoriteSaveFailed();
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
onGeofavoriteSaveFailed();
Log.e(TAG, "Unable to update geofavorite: " + t.getMessage());
}
});
}
private void onGeofavoriteSaveFailed() {
runOnUiThread(() ->
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show()
);
}
/**
* 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 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);
// Set map properties
this.binding.map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
this.binding.map.setMultiTouchControls(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.nameEt.setText(item.getName());
binding.descriptionEt.setText(item.getComment());
binding.createdTv.setText(new Date(item.getDateCreated() * 1000).toString());
binding.modifiedTv.setText(new Date(item.getDateModified() * 1000).toString());
binding.categoryTv.setText(item.getCategory()); // TODO: Category spinner from existing categories
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.setDateModified(System.currentTimeMillis() / 1000);
}
public void setAccuracy(float accuracy) {
binding.accuracyTv.setText(getString(R.string.accuracy).replace("{accuracy}", ((int)accuracy) + ""));
// Color the accuracy background with a scale from red (MINIMUM_ACCEPTABLE_ACCURACY) to green (0 meters)
float red = accuracy / MINIMUM_ACCEPTABLE_ACCURACY;
if (red > 1.0f) red = 1.0f;
float green = 1.0f - red;
if (Build.VERSION.SDK_INT >= 26)
binding.accuracyTv.setBackgroundColor(Color.rgb(red, green, 0.0f));
}
public void hideAccuracy() {
binding.accuracyTv.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();
}
}
}
protected interface OnSubmitListener {
void onSubmit();
void onMapClicked();
void onBackPressed();
}
}

View File

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

View File

@ -21,16 +21,23 @@
package it.danieleverducci.nextcloudmaps.activity.main; package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.text.Html; import android.text.Html;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter; import android.widget.Filter;
import android.widget.Filterable; import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
@ -41,7 +48,9 @@ import java.util.List;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.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_TITLE = 0;
public static final int SORT_BY_CREATED = 1; public static final int SORT_BY_CREATED = 1;
@ -53,6 +62,9 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
private List<Geofavorite> geofavoriteListFiltered = new ArrayList<>(); private List<Geofavorite> geofavoriteListFiltered = new ArrayList<>();
private int sortRule = SORT_BY_CREATED; 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) { public GeofavoriteAdapter(Context context, ItemClickListener itemClickListener) {
this.context = context; this.context = context;
this.itemClickListener = itemClickListener; this.itemClickListener = itemClickListener;
@ -70,6 +82,17 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
return geofavoriteListFiltered.get(position); return geofavoriteListFiltered.get(position);
} }
public void removeById(int id) {
for (Geofavorite g : geofavoriteList) {
if (g.getId() == id) {
geofavoriteList.remove(g);
geofavoriteListFiltered.remove(g);
break;
}
}
notifyDataSetChanged();
}
public int getSortRule() { public int getSortRule() {
return sortRule; return sortRule;
} }
@ -83,13 +106,13 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
@NonNull @NonNull
@Override @Override
public RecyclerViewAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public GeofavoriteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_note, parent, false); View view = LayoutInflater.from(context).inflate(R.layout.item_geofav, parent, false);
return new RecyclerViewAdapter(view, itemClickListener); return new GeofavoriteViewHolder(view, itemClickListener);
} }
@Override @Override
public void onBindViewHolder(@NonNull RecyclerViewAdapter holder, int position) { public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
Geofavorite geofavorite = geofavoriteListFiltered.get(position); Geofavorite geofavorite = geofavoriteListFiltered.get(position);
holder.tv_title.setText(Html.fromHtml(geofavorite.getName())); holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
@ -140,26 +163,43 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
} }
}; };
class RecyclerViewAdapter extends RecyclerView.ViewHolder implements View.OnClickListener { class GeofavoriteViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView tv_title, tv_content; TextView tv_title, tv_content;
ImageView bt_context_menu;
ImageView bt_share;
ItemClickListener itemClickListener; ItemClickListener itemClickListener;
RecyclerViewAdapter(@NonNull View itemView, ItemClickListener itemClickListener) {
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
super(itemView); super(itemView);
tv_title = itemView.findViewById(R.id.title); tv_title = itemView.findViewById(R.id.title);
tv_content = itemView.findViewById(R.id.content); tv_content = itemView.findViewById(R.id.content);
bt_context_menu = itemView.findViewById(R.id.geofav_context_menu_bt);
bt_share = itemView.findViewById(R.id.geofav_share_bt);
this.itemClickListener = itemClickListener; this.itemClickListener = itemClickListener;
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
tv_content.setOnClickListener(this); bt_context_menu.setOnClickListener(this);
bt_share.setOnClickListener(this);
} }
@Override @Override
public void onClick(View view) { public void onClick(View view) {
itemClickListener.onItemClick(view, getAdapterPosition()); switch (view.getId()) {
case R.id.geofav_context_menu_bt:
onOverflowIconClicked(view, getAdapterPosition());
break;
case R.id.geofav_share_bt:
if (itemClickListener != null)
itemClickListener.onItemShareClick(get(getAdapterPosition()));
break;
default:
if (itemClickListener != null)
itemClickListener.onItemClick(get(getAdapterPosition()));
}
} }
} }
@ -171,7 +211,37 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
} }
} }
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_detail && itemClickListener != null)
itemClickListener.onItemDetailsClick(gf);
if (item.getItemId() == R.id.list_context_menu_delete)
itemClickListener.onItemDeleteClick(gf);
return true;
}
public interface ItemClickListener {
void onItemClick(Geofavorite item);
void onItemShareClick(Geofavorite item);
void onItemDetailsClick(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,15 @@
package it.danieleverducci.nextcloudmaps.activity.main; package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton; import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatImageView; import androidx.appcompat.widget.AppCompatImageView;
@ -31,6 +33,7 @@ import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.core.view.GravityCompat; import androidx.core.view.GravityCompat;
import androidx.drawerlayout.widget.DrawerLayout; import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
@ -47,10 +50,10 @@ import java.util.List;
import it.danieleverducci.nextcloudmaps.R; import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity; import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity; import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity;
import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem; import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem;
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener; import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite; import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import static android.view.View.GONE; import static android.view.View.GONE;
@ -59,66 +62,34 @@ import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED; import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE; import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
public class MainActivity extends AppCompatActivity implements MainView, OnSortingOrderListener { public class MainActivity extends AppCompatActivity implements MainView, GeofavoriteActivity {
private static final int INTENT_ADD = 100; private static final int INTENT_ADD = 100;
private static final int INTENT_EDIT = 200; 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 = "add";
private static final String NAVIGATION_KEY_SHOW_ABOUT = "about"; private static final String NAVIGATION_KEY_SHOW_ABOUT = "about";
private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account"; private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account";
private SharedPreferences preferences;
private DrawerLayout drawerLayout; private DrawerLayout drawerLayout;
private Toolbar toolbar; private Toolbar toolbar;
private MaterialCardView homeToolbar; private MaterialCardView homeToolbar;
private SearchView searchView; private SearchView searchView;
private SwipeRefreshLayout swipeRefresh;
private RecyclerView recyclerView;
private StaggeredGridLayoutManager layoutManager;
private FloatingActionButton fab; private FloatingActionButton fab;
private MainPresenter presenter; private MainPresenter presenter;
private GeofavoriteAdapter geofavoriteAdapter;
private ItemClickListener itemClickListener;
NavigationAdapter navigationCommonAdapter; NavigationAdapter navigationCommonAdapter;
private ApiProvider mApi;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
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);
recyclerView = findViewById(R.id.recycler_view);
layoutManager = new StaggeredGridLayoutManager(gridViewEnabled ? 2 : 1, StaggeredGridLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
presenter = new MainPresenter(this); presenter = new MainPresenter(this);
itemClickListener = ((view, position) -> {
Geofavorite geofavorite = geofavoriteAdapter.get(position);
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(geofavorite.getGeoUri());
startActivity(i);
});
geofavoriteAdapter = new GeofavoriteAdapter(getApplicationContext(), itemClickListener);
recyclerView.setAdapter(geofavoriteAdapter);
geofavoriteAdapter.setSortRule(sortRule);
swipeRefresh = findViewById(R.id.swipe_refresh);
swipeRefresh.setOnRefreshListener(() -> presenter.getGeofavorites());
fab = findViewById(R.id.add); fab = findViewById(R.id.add);
fab.setOnClickListener(view -> addGeofavorite()); fab.setOnClickListener(view -> addGeofavorite());
@ -159,16 +130,7 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
AppCompatImageButton menuButton = findViewById(R.id.menu_button); AppCompatImageButton menuButton = findViewById(R.id.menu_button);
menuButton.setOnClickListener(view -> drawerLayout.openDrawer(GravityCompat.START)); menuButton.setOnClickListener(view -> drawerLayout.openDrawer(GravityCompat.START));
AppCompatImageView viewButton = findViewById(R.id.view_mode);
viewButton.setOnClickListener(view -> {
boolean gridEnabled = layoutManager.getSpanCount() == 1;
onGridIconChosen(gridEnabled);
});
updateSortingIcon(sortRule); updateSortingIcon(sortRule);
updateGridIcon(gridViewEnabled);
mApi = new ApiProvider(getApplicationContext());
} }
@Override @Override
@ -248,54 +210,71 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
@Override @Override
public void showLoading() { public void showLoading() {
swipeRefresh.setRefreshing(true); //todo notify GeofavoriteListConsumer.geoFavoriteLoading(true)
} }
@Override @Override
public void hideLoading() { public void hideLoading() {
swipeRefresh.setRefreshing(false); //todo notify GeofavoriteListConsumer.geoFavoriteLoading(false)
} }
@Override @Override
public void onGetResult(List<Geofavorite> geofavorite_list) { public void onGetResult(List<Geofavorite> geofavorite_list) {
geofavoriteAdapter.setGeofavoriteList(geofavorite_list); //todo notify GeofavoriteListConsumer
}
@Override
public void onGeofavoriteDeleted(int id) {
// Notify fragment
runOnUiThread(() -> {
//todo notify GeofavoriteListConsumer
Toast.makeText(MainActivity.this, R.string.list_geofavorite_deleted, Toast.LENGTH_LONG).show();
});
} }
@Override @Override
public void onErrorLoading(String message) { public void onErrorLoading(String message) {
Toast.makeText(MainActivity.this, message, Toast.LENGTH_LONG).show(); Toast.makeText(MainActivity.this, R.string.list_geofavorite_connection_error, Toast.LENGTH_LONG).show();
Log.e(TAG, "Unable to obtain geofavorites list: " + message);
} }
@Override @Override
public void onSortingOrderChosen(int sortSelection) { public void showGeofavoriteDeteleDialog(Geofavorite item) {
geofavoriteAdapter.setSortRule(sortSelection); AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
updateSortingIcon(sortSelection); builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName()))
.setTitle(R.string.dialog_delete_title)
preferences.edit().putInt(getString(R.string.setting_sort_by), sortSelection).apply(); .setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
presenter.deleteGeofavorite(item.getId());
dialog.dismiss();
// Callback is onGeofavoriteDeleted
}
})
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog ad = builder.create();
ad.show();
} }
public void updateSortingIcon(int sortSelection) { @Override
AppCompatImageView sortButton = findViewById(R.id.sort_mode); public void updateGeofavorites() {
switch (sortSelection) { presenter.getGeofavorites();
case SORT_BY_TITLE:
sortButton.setImageResource(R.drawable.ic_alphabetical_asc);
break;
case SORT_BY_CREATED:
sortButton.setImageResource(R.drawable.ic_modification_asc);
break;
}
} }
public void onGridIconChosen(boolean gridEnabled) { @Override
layoutManager.setSpanCount(gridEnabled ? 2 : 1); public void getGeofavorites() {
updateGridIcon(gridEnabled);
preferences.edit().putBoolean(getString(R.string.setting_grid_view_enabled), gridEnabled).apply();
} }
public void updateGridIcon(boolean gridEnabled) { @Override
AppCompatImageView viewButton = findViewById(R.id.view_mode); public void showGeofavoriteDetailActivity(Geofavorite item) {
viewButton.setImageResource(gridEnabled ? R.drawable.ic_view_list : R.drawable.ic_view_module); Intent i = new Intent(this, GeofavoriteDetailActivity.class);
i.putExtra(GeofavoriteDetailActivity.ARG_GEOFAVORITE, item);
startActivity(i);
} }
} }

View File

@ -50,6 +50,11 @@ public class MainPresenter {
view.hideLoading(); view.hideLoading();
if (response.isSuccessful() && response.body() != null) { if (response.isSuccessful() && response.body() != null) {
view.onGetResult(response.body()); view.onGetResult(response.body());
} else {
((AppCompatActivity) view).runOnUiThread(() -> {
view.hideLoading();
view.onErrorLoading(response.raw().message());
});
} }
}); });
} }
@ -63,4 +68,22 @@ public class MainPresenter {
} }
}); });
} }
public void deleteGeofavorite(int id) {
view.showLoading();
Call<Geofavorite> call = ApiProvider.getAPI().deleteGeofavorite(id);
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
view.hideLoading();
view.onGeofavoriteDeleted(id);
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
view.hideLoading();
view.onErrorLoading(t.getLocalizedMessage());
}
});
}
} }

View File

@ -28,5 +28,6 @@ public interface MainView {
void showLoading(); void showLoading();
void hideLoading(); void hideLoading();
void onGetResult(List<Geofavorite> geofavorites); void onGetResult(List<Geofavorite> geofavorites);
void onGeofavoriteDeleted(int id);
void onErrorLoading(String message); void onErrorLoading(String message);
} }

View File

@ -48,12 +48,12 @@ public class ApiProvider {
initSsoApi(new NextcloudAPI.ApiConnectedListener() { initSsoApi(new NextcloudAPI.ApiConnectedListener() {
@Override @Override
public void onConnected() { public void onConnected() {
// Ignore.. Log.d(TAG, "Connected to Nextcloud instance");
} }
@Override @Override
public void onError(Exception ex) { public void onError(Exception ex) {
// Ignore... Log.d(TAG, "Unable to connect to Nextcloud instance: " + ex.toString());
} }
}); });
} }

View File

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

View File

@ -0,0 +1,152 @@
package it.danieleverducci.nextcloudmaps.fragments;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_CREATED;
import static it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter.SORT_BY_TITLE;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.fragment.app.Fragment;
import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteActivity;
import it.danieleverducci.nextcloudmaps.activity.main.GeofavoriteAdapter;
import it.danieleverducci.nextcloudmaps.activity.main.MainActivity;
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment;
import it.danieleverducci.nextcloudmaps.databinding.FragmentGeofavoriteListBinding;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
/**
* Geofavorites list
*/
public class GeofavoriteListFragment extends Fragment implements GeofavoriteListConsumer, SortingOrderDialogFragment.OnSortingOrderListener {
private FragmentGeofavoriteListBinding binding;
private GeofavoriteAdapter geofavoriteAdapter;
private SharedPreferences preferences;
public GeofavoriteListFragment() {
// Required empty public constructor
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (binding == null) {
binding = FragmentGeofavoriteListBinding.inflate(
LayoutInflater.from(container.getContext()),
container,
false
);
}
preferences = PreferenceManager.getDefaultSharedPreferences(getContext());
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(1, StaggeredGridLayoutManager.VERTICAL);
binding.recyclerView.setLayoutManager(layoutManager);
int sortRule = preferences.getInt(getString(R.string.setting_sort_by), SORT_BY_CREATED);
GeofavoriteAdapter.ItemClickListener rvItemClickListener = new GeofavoriteAdapter.ItemClickListener() {
@Override
public void onItemClick(Geofavorite geofavorite) {
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(geofavorite.getGeoUri());
startActivity(i);
}
@Override
public void onItemShareClick(Geofavorite item) {
Intent i = new Intent();
i.setAction(Intent.ACTION_SEND);
i.setType("text/plain");
String shareMessage = getString(R.string.share_message)
.replace("{lat}", ""+item.getLat())
.replace("{lng}", ""+item.getLng());
i.putExtra(Intent.EXTRA_TEXT, shareMessage );
startActivity(Intent.createChooser(i, getString(R.string.share_via)));
}
@Override
public void onItemDetailsClick(Geofavorite item) {
if (getActivity() instanceof GeofavoriteActivity)
((GeofavoriteActivity)getActivity()).showGeofavoriteDetailActivity(item);
else
throw new IllegalStateException("Expected activity implementing GeofavoriteAcivity");
}
@Override
public void onItemDeleteClick(Geofavorite item) {
if (getActivity() instanceof GeofavoriteActivity)
((GeofavoriteActivity)getActivity()).showGeofavoriteDeteleDialog(item);
else
throw new IllegalStateException("Expected activity implementing GeofavoriteAcivity");
}
};
geofavoriteAdapter = new GeofavoriteAdapter(getContext(), rvItemClickListener);
binding.recyclerView.setAdapter(geofavoriteAdapter);
geofavoriteAdapter.setSortRule(sortRule);
binding.swipeRefresh.setOnRefreshListener(() ->
((GeofavoriteActivity)getActivity()).updateGeofavorites());
return binding.root;
}
@Override
public void onGeofavoritesUpdated(List<Geofavorite> geofavorites) {
if (geofavoriteAdapter != null)
geofavoriteAdapter.setGeofavoriteList(geofavorites);
}
@Override
public void onGeofavoriteRemoved(Geofavorite geofavorite) {
// Update list
geofavoriteAdapter.removeById(geofavorite.getId());
}
@Override
public void onGeofavoriteAdded(Geofavorite geofavorite) {
}
@Override
public void onGeofavoriteLoading(boolean isLoading) {
binding.swipeRefresh.setRefreshing(isLoading);
}
@Override
public void onSortingOrderChosen(int sortSelection) {
geofavoriteAdapter.setSortRule(sortSelection);
updateSortingIcon(sortSelection);
preferences.edit().putInt(getString(R.string.setting_sort_by), sortSelection).apply();
}
public void updateSortingIcon(int sortSelection) {
// TODO: Gestire il bottone come bottone generico da aggiungere all'activity
AppCompatImageView sortButton = findViewById(R.id.sort_mode);
switch (sortSelection) {
case SORT_BY_TITLE:
sortButton.setImageResource(R.drawable.ic_alphabetical_asc);
break;
case SORT_BY_CREATED:
sortButton.setImageResource(R.drawable.ic_modification_asc);
break;
}
}
}

View File

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

View File

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

View File

@ -25,6 +25,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter; import androidx.databinding.BindingAdapter;
import com.google.gson.annotations.Expose; import com.google.gson.annotations.Expose;
@ -90,13 +92,13 @@ public class Geofavorite implements Serializable {
} }
public void setName(String name) { public void setName(String name) {
this.name = name; if (!name.equals(this.name))
this.name = name;
} }
public long getDateModified() { public long getDateModified() {
return dateModified; return dateModified;
} }
public void setDateModified(long dateModified) { public void setDateModified(long dateModified) {
this.dateModified = dateModified; this.dateModified = dateModified;
} }
@ -104,7 +106,6 @@ public class Geofavorite implements Serializable {
public long getDateCreated() { public long getDateCreated() {
return dateCreated; return dateCreated;
} }
public void setDateCreated(long dateCreated) { public void setDateCreated(long dateCreated) {
this.dateCreated = dateCreated; this.dateCreated = dateCreated;
} }
@ -112,7 +113,6 @@ public class Geofavorite implements Serializable {
public double getLat() { public double getLat() {
return lat; return lat;
} }
public void setLat(double lat) { public void setLat(double lat) {
this.lat = lat; this.lat = lat;
} }
@ -120,7 +120,6 @@ public class Geofavorite implements Serializable {
public double getLng() { public double getLng() {
return lng; return lng;
} }
public void setLng(double lng) { public void setLng(double lng) {
this.lng = lng; this.lng = lng;
} }
@ -128,7 +127,6 @@ public class Geofavorite implements Serializable {
public String getCategory() { public String getCategory() {
return category; return category;
} }
public void setCategory(String category) { public void setCategory(String category) {
this.category = category; this.category = category;
} }
@ -137,24 +135,20 @@ public class Geofavorite implements Serializable {
public String getComment() { public String getComment() {
return comment; return comment;
} }
public void setComment(String comment) { public void setComment(String comment) {
this.comment = comment; this.comment = comment;
} }
public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name); public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name);
public static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id; public static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id;
public String getCoordinatesString() {
return this.lat + " N, " + this.lng + " E";
}
public Uri getGeoUri() { public Uri getGeoUri() {
return Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")"); return Uri.parse("geo:" + this.lat + "," + this.lng + "(" + this.name + ")");
} }
@BindingAdapter("formatDate")
public static void formatDate(@NonNull TextView textView, long timestamp) {
textView.setText((new Date(timestamp)).toString());
}
public boolean valid() { public boolean valid() {
return getLat() != 0 && getLng() != 0 && getName() != null && getName().length() > 0; return getLat() != 0 && getLng() != 0 && getName() != null && getName().length() > 0;
} }

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

@ -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:width="108dp"
android:height="108dp" android:height="108dp"
android:viewportWidth="108" android:viewportWidth="108"
android:viewportHeight="108"> android:viewportHeight="108"
<group android:scaleX="1.5525" android:tint="#FFFFFF">
android:scaleY="1.5525" <group android:scaleX="2.61"
android:translateX="29.16" android:scaleY="2.61"
android:translateY="29.16"> android:translateX="22.68"
android:translateY="22.68">
<path <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="@android:color/white"
android:fillColor="#FFF"/> 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> </group>
</vector> </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="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

@ -1,23 +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:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#FFFFFF" android:pathData="M9,5V9H21V5M9,19H21V15H9M9,14H21V10H9M4,9H8V5H4M4,19H8V15H4M4,14H8V10H4V14Z" />
</vector>

View File

@ -1,25 +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:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#FFFFFF"
android:pathData="M16,5V11H21V5M10,11H15V5H10M16,18H21V12H16M10,18H15V12H10M4,18H9V12H4M4,11H9V5H4V11Z" />
</vector>

View File

@ -2,99 +2,150 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <FrameLayout
<variable
name="geofavorite"
type="it.danieleverducci.nextcloudmaps.model.Geofavorite"/>
</data>
<ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/root"> android:id="@+id/root">
<LinearLayout <ScrollView
android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="match_parent">
<ImageView
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="200dp"
android:padding="50dp"
app:srcCompat="@drawable/ic_app"
android:background="@color/defaultBrand" />
<LinearLayout <LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="20dp"> android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText <FrameLayout
android:id="@+id/name_et"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="200dp">
android:ems="10" <org.osmdroid.views.MapView
android:hint="@string/name" android:id="@+id/map"
android:lines="1" android:layout_width="match_parent"
android:maxLines="1" android:layout_height="match_parent"
android:singleLine="true" android:focusable="false"
android:ellipsize="end" android:clickable="false"/>
android:text="@{geofavorite.name}"/> <View
android:id="@+id/map_bt"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<EditText <LinearLayout
android:id="@+id/description_et"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:ems="10" android:orientation="vertical"
android:gravity="start|top" android:padding="20dp">
android:inputType="textMultiLine"
android:hint="@string/description"
android:ellipsize="end"
android:text="@{geofavorite.comment}" />
<TextView <EditText
android:layout_width="match_parent" android:id="@+id/name_et"
android:layout_height="wrap_content" android:layout_width="match_parent"
android:layout_marginTop="20dp" android:layout_height="wrap_content"
android:textStyle="bold" android:ems="10"
android:text="@string/created" /> android:hint="@string/name"
android:lines="1"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"/>
<TextView <EditText
android:id="@+id/created_tv" android:id="@+id/description_et"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="textEnd" android:ems="10"
app:formatDate="@{geofavorite.dateCreated}" /> android:gravity="start|top"
android:inputType="textMultiLine"
android:lines="5"
android:maxLines="10"
android:hint="@string/description"
android:ellipsize="end" />
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="20dp" android:layout_marginTop="20dp"
android:text="@string/coords" android:textStyle="bold"
android:textStyle="bold" /> android:text="@string/created" />
<TextView <TextView
android:id="@+id/coords_tv" android:id="@+id/created_tv"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textAlignment="textEnd" android:textAlignment="textEnd" />
android:text="@{geofavorite.lat +` °N, ` + geofavorite.lng + ` °E`}" />
<Button <TextView
android:id="@+id/submit_bt" android:layout_width="match_parent"
style="@style/Widget.AppCompat.Button.Colored" android:layout_height="wrap_content"
android:layout_width="match_parent" android:layout_marginTop="20dp"
android:layout_height="60dp" android:textStyle="bold"
android:layout_margin="50dp" android:text="@string/modified" />
android:text="@string/confirm"
app:backgroundTint="@color/defaultBrand" <TextView
android:onClick="onSubmit"/> android:id="@+id/modified_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textStyle="bold"
android:text="@string/category" />
<TextView
android:id="@+id/category_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/coords"
android:textStyle="bold" />
<TextView
android:id="@+id/coords_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:id="@+id/accuracy_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:textAlignment="center"
android:text="@string/accuracy"
android:textColor="@android:color/white"
android:background="@android:color/darker_gray"/>
<Button
android:id="@+id/submit_bt"
style="@style/Widget.AppCompat.Button.Colored"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_margin="50dp"
android:text="@string/confirm"
app:backgroundTint="@color/defaultBrand"
android:onClick="onSubmit"/>
</LinearLayout>
</LinearLayout> </LinearLayout>
</ScrollView>
</LinearLayout> <!-- Back button -->
</ScrollView> <ImageView
android:id="@+id/back_bt"
android:layout_width="48dp"
android:layout_height="48dp"
android:padding="12dp"
android:src="@drawable/ic_back_grey"
app:tint="@color/white"
android:background="@color/defaultBrandAlpha"/>
</FrameLayout>
</layout> </layout>

View File

@ -1,166 +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/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_list_view"
android:background="@color/primary"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways"
app:toolbarId="@+id/toolbar" >
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"
app:contentInsetStartWithNavigation="0dp"
app:titleMarginStart="0dp"
tools:title="@string/app_name">
<androidx.appcompat.widget.SearchView
android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.appcompat.widget.SearchView>
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.card.MaterialCardView
android:id="@+id/home_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacer_2x"
android:layout_marginTop="@dimen/spacer_1hx"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_marginBottom="@dimen/spacer_1hx"
app:cardBackgroundColor="@color/appbar"
app:cardCornerRadius="@dimen/spacer_1x"
app:cardElevation="2dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menu_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:paddingStart="@dimen/spacer_1x"
android:paddingTop="@dimen/spacer_2x"
android:paddingEnd="@dimen/spacer_1x"
android:paddingBottom="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:src="@drawable/ic_menu_grey"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/search_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="@dimen/spacer_1x"
android:layout_marginEnd="@dimen/spacer_1x"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="start"
android:lines="1"
android:textSize="16sp"
android:text="@string/search_in_all"/>
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/sort_mode"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="center_vertical|end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:padding="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:translationX="@dimen/spacer_1x"
android:src="@drawable/ic_alphabetical_asc" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/view_mode"
android:layout_width="?attr/actionBarSize"
android:layout_height="?attr/actionBarSize"
android:layout_gravity="center_vertical|end"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/list_mode"
android:padding="@dimen/spacer_2x"
android:tint="?attr/colorAccent"
android:translationX="@dimen/spacer_1x"
android:src="@drawable/ic_view_module" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/spacer_1hx"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2"
tools:itemCount="3"
tools:listitem="@layout/item_note">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/add"
android:layout_margin="16dp"
android:layout_gravity="bottom|end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add"
app:backgroundTint="@color/defaultBrand"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

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

View File

@ -0,0 +1,55 @@
<?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/>.
-->
<layout 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">
<FrameLayout
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/spacer_1hx"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="1"
tools:itemCount="3"
tools:listitem="@layout/item_geofav">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
</layout>

View File

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

View File

@ -18,11 +18,11 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="90dp"
android:gravity="center_vertical"
android:padding="12dp" android:padding="12dp"
android:clickable="true" android:clickable="true"
android:focusable="true"> android:focusable="true">
@ -31,19 +31,19 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="0" android:layout_weight="0"
android:layout_marginRight="10dp"
android:src="@mipmap/ic_launcher"/> android:src="@mipmap/ic_launcher"/>
<LinearLayout <LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_weight="1">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1"
android:textSize="@dimen/note_font_size_item_title" android:textSize="@dimen/note_font_size_item_title"
android:textStyle="bold" android:textStyle="bold"
android:singleLine="true" android:singleLine="true"
@ -60,11 +60,28 @@
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:textSize="@dimen/note_font_size_item_content" android:textSize="@dimen/note_font_size_item_content"
android:maxLines="2" android:maxLines="2"
android:lines="2" android:gravity="center_vertical"
tools:text="@tools:sample/lorem/random"> tools:text="@tools:sample/lorem/random">
</TextView> </TextView>
</LinearLayout> </LinearLayout>
<ImageView
android:id="@+id/geofav_share_bt"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0"
android:padding="10dp"
android:src="@drawable/ic_share"
android:tint="@color/list_text" /> <!-- 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="10dp"
android:src="@drawable/ic_more"
android:tint="@color/list_text" /> <!-- TODO: app:tint is not working -->
</LinearLayout> </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">
<!-- Detail button -->
<item
android:id="@+id/list_context_menu_detail"
android:title="@string/list_context_menu_detail"/>
<!-- 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

@ -17,15 +17,18 @@
--> -->
<resources> <resources>
<!-- Colors --> <!-- Generic Colors -->
<color name="primary">#ffffff</color> <color name="primary">#ffffff</color>
<color name="accent">#121212</color> <color name="accent">#121212</color>
<color name="transparent">#00000000</color> <color name="transparent">#00000000</color>
<color name="defaultBrand">#0082C9</color> <color name="defaultBrand">#0082C9</color>
<color name="defaultBrandAlpha">#550082C9</color>
<color name="appbar">@android:color/white</color> <color name="appbar">@android:color/white</color>
<color name="defaultTint">#202124</color> <color name="defaultTint">#202124</color>
<!-- List Colors -->
<color name="list_text">#aaa</color>
<!-- Generic Colors -->
<color name="white">#fff</color>
</resources> </resources>

View File

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

View File

@ -29,6 +29,16 @@
<string name="switch_account">Switch account</string> <string name="switch_account">Switch account</string>
<string name="list_mode">List</string> <string name="list_mode">List</string>
<string name="search_in_all">Search by name</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_detail">Details</string>
<string name="list_context_menu_delete">Delete</string>
<string name="dialog_delete_title">Delete geobookmark</string>
<string name="dialog_delete_message">You are about to delete geobookmark {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 --> <!-- Sort dialog -->
<string name="sort_by">Sort by</string> <string name="sort_by">Sort by</string>
@ -40,7 +50,10 @@
<string name="name">Name</string> <string name="name">Name</string>
<string name="description">Description</string> <string name="description">Description</string>
<string name="created">Created</string> <string name="created">Created</string>
<string name="modified">Modified</string>
<string name="category">Category</string>
<string name="coords">Coordinates</string> <string name="coords">Coordinates</string>
<string name="accuracy">Accuracy: {accuracy} m</string>
<string name="location_permission_required">Location permission is required to create a geofavorite.</string> <string name="location_permission_required">Location permission is required to create a geofavorite.</string>
<string name="confirm">Save</string> <string name="confirm">Save</string>
<string name="error_saving_geofavorite">Unable to save geofavorite</string> <string name="error_saving_geofavorite">Unable to save geofavorite</string>
@ -68,6 +81,5 @@
<!-- Settings --> <!-- Settings -->
<string name="setting_sort_by">SETTING_SORT_BY</string> <string name="setting_sort_by">SETTING_SORT_BY</string>
<string name="setting_grid_view_enabled">SETTING_GRID_VIEW_ENABLED</string>
</resources> </resources>

View File

@ -20,4 +20,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip

BIN
screenshots/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
screenshots/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB