16 Commits
v0.1 ... v0.2

40 changed files with 467 additions and 238 deletions

13
.idea/misc.xml generated
View File

@ -1,4 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="../../../../layout/custom_preview.xml" value="0.5661458333333333" />
<entry key="app/src/main/res/layout/activity_geofavorite_detail.xml" value="0.4" />
<entry key="app/src/main/res/layout/activity_list_view.xml" value="0.5307291666666667" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.5307291666666667" />
<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_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK" />
</project>

View File

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

View File

@ -1,3 +1,5 @@
![Nextcloud Maps Geobookmarks Logo](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png)
# 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...).

View File

@ -24,8 +24,8 @@ android {
applicationId "it.danieleverducci.nextcloudmaps"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "0.1"
versionCode 2
versionName "0.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -15,14 +15,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package it.danieleverducci.nextcloudmaps.activity.main;
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;
@ -34,6 +36,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.util.Date;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.databinding.ActivityGeofavoriteDetailBinding;
@ -45,31 +49,38 @@ 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";
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 ActivityGeofavoriteDetailBinding binding;
private ViewHolder mViewHolder;
private Geofavorite mGeofavorite;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityGeofavoriteDetailBinding.inflate(getLayoutInflater());
setContentView(binding.root);
mViewHolder = new ViewHolder(getLayoutInflater());
mViewHolder.setOnSubmitListener(this::saveGeofavorite);
setContentView(mViewHolder.getRootView());
int id = getIntent().getIntExtra(ARG_GEOFAVORITE_ID, 0);
if (id == 0) {
if (getIntent().hasExtra(ARG_GEOFAVORITE)) {
// Opening geofavorite from list
mGeofavorite = (Geofavorite) getIntent().getSerializableExtra(ARG_GEOFAVORITE);
mViewHolder.hideAccuracy();
} else {
// New geofavorite
Geofavorite gf = new Geofavorite();
gf.setDateCreated(System.currentTimeMillis());
gf.setDateModified(System.currentTimeMillis());
binding.setGeofavorite(gf);
mGeofavorite = new Geofavorite();
mGeofavorite.setCategory(DEFAULT_CATEGORY);
mGeofavorite.setDateCreated(System.currentTimeMillis());
mGeofavorite.setDateModified(System.currentTimeMillis());
// Precompile location
getLocation();
} else {
// TODO: Load geofavorite from cache for edit
}
mViewHolder.updateView(mGeofavorite);
}
/**
@ -84,38 +95,44 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
* 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());
mViewHolder.updateModel(mGeofavorite);
if (!gf.valid()) {
if (!mGeofavorite.valid()) {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.incomplete_geofavorite, Toast.LENGTH_SHORT).show();
return;
}
Call<Geofavorite> call;
if (gf.getId() == 0) {
if (mGeofavorite.getId() == 0) {
// New geofavorite
call = ApiProvider.getAPI().createGeofavorite(gf);
call = ApiProvider.getAPI().createGeofavorite(mGeofavorite);
} else {
// Update existing geofavorite
call = ApiProvider.getAPI().updateGeofavorite(gf.getId(), gf);
call = ApiProvider.getAPI().updateGeofavorite(mGeofavorite.getId(), mGeofavorite);
}
call.enqueue(new Callback<Geofavorite>() {
@Override
public void onResponse(Call<Geofavorite> call, Response<Geofavorite> response) {
finish();
if (response.isSuccessful())
finish();
else
onGeofavoriteSaveFailed();
}
@Override
public void onFailure(Call<Geofavorite> call, Throwable t) {
Toast.makeText(GeofavoriteDetailActivity.this, R.string.error_saving_geofavorite, Toast.LENGTH_SHORT).show();
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()
@ -145,9 +162,12 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
* @param location to set in the geofavorite
*/
private void updateLocationField(Location location) {
binding.getGeofavorite().setLat(location.getLatitude());
binding.getGeofavorite().setLng(location.getLongitude());
binding.notifyChange();
// Update model
mGeofavorite.setLat(location.getLatitude());
mGeofavorite.setLng(location.getLongitude());
// Update view
mViewHolder.updateViewCoords(mGeofavorite.getCoordinatesString());
mViewHolder.setAccuracy(location.getAccuracy());
}
@ -185,4 +205,68 @@ public class GeofavoriteDetailActivity extends AppCompatActivity implements Loca
}
}
}
private class ViewHolder implements View.OnClickListener {
private final ActivityGeofavoriteDetailBinding binding;
private OnSubmitListener listener;
public ViewHolder(LayoutInflater inflater) {
this.binding = ActivityGeofavoriteDetailBinding.inflate(inflater);
this.binding.submitBt.setOnClickListener(this);
}
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
binding.coordsTv.setText(item.getCoordinatesString());
}
public void updateViewCoords(String coords) {
binding.coordsTv.setText(coords);
}
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;
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.submit_bt && this.listener != null) {
this.listener.onSubmit();
}
}
}
protected interface OnSubmitListener {
void onSubmit();
}
}

View File

@ -21,16 +21,23 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
@ -41,7 +48,9 @@ import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.RecyclerViewAdapter> implements Filterable {
public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.GeofavoriteViewHolder> implements Filterable {
public static final String TAG = "GeofavoriteAdapter";
public static final int SORT_BY_TITLE = 0;
public static final int SORT_BY_CREATED = 1;
@ -53,6 +62,9 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
private List<Geofavorite> geofavoriteListFiltered = new ArrayList<>();
private int sortRule = SORT_BY_CREATED;
// Contains the position of the element containing the overflow menu clicked
private int overflowMenuSelectedPosition = -1;
public GeofavoriteAdapter(Context context, ItemClickListener itemClickListener) {
this.context = context;
this.itemClickListener = itemClickListener;
@ -70,6 +82,17 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
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() {
return sortRule;
}
@ -83,13 +106,13 @@ public class GeofavoriteAdapter extends RecyclerView.Adapter<GeofavoriteAdapter.
@NonNull
@Override
public RecyclerViewAdapter onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_note, parent, false);
return new RecyclerViewAdapter(view, itemClickListener);
public GeofavoriteViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_geofav, parent, false);
return new GeofavoriteViewHolder(view, itemClickListener);
}
@Override
public void onBindViewHolder(@NonNull RecyclerViewAdapter holder, int position) {
public void onBindViewHolder(@NonNull GeofavoriteViewHolder holder, int position) {
Geofavorite geofavorite = geofavoriteListFiltered.get(position);
holder.tv_title.setText(Html.fromHtml(geofavorite.getName()));
@ -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;
ImageView bt_context_menu;
ImageView bt_share;
ItemClickListener itemClickListener;
RecyclerViewAdapter(@NonNull View itemView, ItemClickListener itemClickListener) {
GeofavoriteViewHolder(@NonNull View itemView, ItemClickListener itemClickListener) {
super(itemView);
tv_title = itemView.findViewById(R.id.title);
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;
itemView.setOnClickListener(this);
tv_content.setOnClickListener(this);
bt_context_menu.setOnClickListener(this);
bt_share.setOnClickListener(this);
}
@Override
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

@ -17,13 +17,15 @@
package it.danieleverducci.nextcloudmaps.activity.main;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.AppCompatImageView;
@ -47,10 +49,10 @@ import java.util.List;
import it.danieleverducci.nextcloudmaps.R;
import it.danieleverducci.nextcloudmaps.activity.about.AboutActivity;
import it.danieleverducci.nextcloudmaps.activity.detail.GeofavoriteDetailActivity;
import it.danieleverducci.nextcloudmaps.activity.login.LoginActivity;
import it.danieleverducci.nextcloudmaps.activity.main.NavigationAdapter.NavigationItem;
import it.danieleverducci.nextcloudmaps.activity.main.SortingOrderDialogFragment.OnSortingOrderListener;
import it.danieleverducci.nextcloudmaps.api.ApiProvider;
import it.danieleverducci.nextcloudmaps.model.Geofavorite;
import static android.view.View.GONE;
@ -64,6 +66,8 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
private static final int INTENT_ADD = 100;
private static final int INTENT_EDIT = 200;
private static final String TAG = "MainActivity";
private static final String NAVIGATION_KEY_ADD_GEOFAVORITE = "add";
private static final String NAVIGATION_KEY_SHOW_ABOUT = "about";
private static final String NAVIGATION_KEY_SWITCH_ACCOUNT = "switch_account";
@ -81,12 +85,10 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
private MainPresenter presenter;
private GeofavoriteAdapter geofavoriteAdapter;
private ItemClickListener itemClickListener;
private ItemClickListener rvItemClickListener;
NavigationAdapter navigationCommonAdapter;
private ApiProvider mApi;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -95,7 +97,7 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
int sortRule = preferences.getInt(getString(R.string.setting_sort_by), SORT_BY_CREATED);
boolean gridViewEnabled = preferences.getBoolean(getString(R.string.setting_grid_view_enabled), true);
boolean gridViewEnabled = preferences.getBoolean(getString(R.string.setting_grid_view_enabled), false);
recyclerView = findViewById(R.id.recycler_view);
layoutManager = new StaggeredGridLayoutManager(gridViewEnabled ? 2 : 1, StaggeredGridLayoutManager.VERTICAL);
@ -103,17 +105,40 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
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);
});
rvItemClickListener = new ItemClickListener() {
@Override
public void onItemClick(Geofavorite geofavorite) {
Intent i = new Intent();
i.setAction(Intent.ACTION_VIEW);
i.setData(geofavorite.getGeoUri());
startActivity(i);
}
geofavoriteAdapter = new GeofavoriteAdapter(getApplicationContext(), itemClickListener);
@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) {
showGeofavoriteDetailActivity(item);
}
@Override
public void onItemDeleteClick(Geofavorite item) {
showGeofavoriteDeteleDialog(item);
}
};
geofavoriteAdapter = new GeofavoriteAdapter(getApplicationContext(), rvItemClickListener);
recyclerView.setAdapter(geofavoriteAdapter);
geofavoriteAdapter.setSortRule(sortRule);
swipeRefresh = findViewById(R.id.swipe_refresh);
@ -167,8 +192,6 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
updateSortingIcon(sortRule);
updateGridIcon(gridViewEnabled);
mApi = new ApiProvider(getApplicationContext());
}
@Override
@ -261,9 +284,19 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
geofavoriteAdapter.setGeofavoriteList(geofavorite_list);
}
@Override
public void onGeofavoriteDeleted(int id) {
// Update list
runOnUiThread(() -> {
geofavoriteAdapter.removeById(id);
Toast.makeText(MainActivity.this, R.string.list_geofavorite_deleted, Toast.LENGTH_LONG).show();
});
}
@Override
public void onErrorLoading(String message) {
Toast.makeText(MainActivity.this, 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
@ -298,4 +331,30 @@ public class MainActivity extends AppCompatActivity implements MainView, OnSorti
viewButton.setImageResource(gridEnabled ? R.drawable.ic_view_list : R.drawable.ic_view_module);
}
private void showGeofavoriteDeteleDialog(Geofavorite item) {
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setMessage(getString(R.string.dialog_delete_message).replace("{name}", item.getName()))
.setTitle(R.string.dialog_delete_title)
.setPositiveButton(R.string.dialog_delete_delete, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
presenter.deleteGeofavorite(item.getId());
dialog.dismiss();
// Callback is onGeofavoriteDeleted
}
})
.setNegativeButton(R.string.dialog_delete_cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
AlertDialog ad = builder.create();
ad.show();
}
private void showGeofavoriteDetailActivity(Geofavorite item) {
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();
if (response.isSuccessful() && response.body() != null) {
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 hideLoading();
void onGetResult(List<Geofavorite> geofavorites);
void onGeofavoriteDeleted(int id);
void onErrorLoading(String message);
}

View File

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

View File

@ -25,6 +25,8 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import androidx.databinding.BindingAdapter;
import com.google.gson.annotations.Expose;
@ -90,13 +92,13 @@ public class Geofavorite implements Serializable {
}
public void setName(String name) {
this.name = name;
if (!name.equals(this.name))
this.name = name;
}
public long getDateModified() {
return dateModified;
}
public void setDateModified(long dateModified) {
this.dateModified = dateModified;
}
@ -104,7 +106,6 @@ public class Geofavorite implements Serializable {
public long getDateCreated() {
return dateCreated;
}
public void setDateCreated(long dateCreated) {
this.dateCreated = dateCreated;
}
@ -112,7 +113,6 @@ public class Geofavorite implements Serializable {
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
@ -120,7 +120,6 @@ public class Geofavorite implements Serializable {
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
@ -128,7 +127,6 @@ public class Geofavorite implements Serializable {
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
@ -137,24 +135,20 @@ public class Geofavorite implements Serializable {
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public static Comparator<Geofavorite> ByTitleAZ = (note, t1) -> note.name.compareTo(t1.name);
public static Comparator<Geofavorite> ByLastCreated = (note, t1) -> t1.id - note.id;
public String getCoordinatesString() {
return this.lat + " N, " + this.lng + " E";
}
public Uri getGeoUri() {
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() {
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:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="1.5525"
android:scaleY="1.5525"
android:translateX="29.16"
android:translateY="29.16">
android:viewportHeight="108"
android:tint="#FFFFFF">
<group android:scaleX="2.61"
android:scaleY="2.61"
android:translateX="22.68"
android:translateY="22.68">
<path
android:pathData="m6,2c-2.216,0 -4,1.784 -4,4v20c0,2.216 1.784,4 4,4h20c2.216,0 4,-1.784 4,-4v-16.719l-0.906,0.9068 -5.282,-5.2818 2.907,-2.9062h-20.719zM21.812,6.9062l5.282,5.2818 -8.313,8.312 -8.781,3.5 3.5,-8.781 8.312,-8.3128zM14.406,16.094l-2.656,4.406 1.75,1.75 4.406,-2.656 -3.5,-3.5z"
android:fillColor="#FFF"/>
android:fillColor="@android:color/white"
android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
</group>
</vector>

View File

@ -0,0 +1,5 @@
<vector android:height="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

@ -2,12 +2,6 @@
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="geofavorite"
type="it.danieleverducci.nextcloudmaps.model.Geofavorite"/>
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -41,8 +35,7 @@
android:lines="1"
android:maxLines="1"
android:singleLine="true"
android:ellipsize="end"
android:text="@{geofavorite.name}"/>
android:ellipsize="end"/>
<EditText
android:id="@+id/description_et"
@ -51,9 +44,10 @@
android:ems="10"
android:gravity="start|top"
android:inputType="textMultiLine"
android:lines="5"
android:maxLines="10"
android:hint="@string/description"
android:ellipsize="end"
android:text="@{geofavorite.comment}" />
android:ellipsize="end" />
<TextView
android:layout_width="match_parent"
@ -66,8 +60,33 @@
android:id="@+id/created_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd"
app:formatDate="@{geofavorite.dateCreated}" />
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:textStyle="bold"
android:text="@string/modified" />
<TextView
android:id="@+id/modified_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd" />
<TextView
android:layout_width="match_parent"
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"
@ -80,8 +99,17 @@
android:id="@+id/coords_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="textEnd"
android:text="@{geofavorite.lat +` °N, ` + geofavorite.lng + ` °E`}" />
android:textAlignment="textEnd" />
<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"

View File

@ -145,9 +145,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.StaggeredGridLayoutManager"
app:spanCount="2"
app:spanCount="1"
tools:itemCount="3"
tools:listitem="@layout/item_note">
tools:listitem="@layout/item_geofav">
</androidx.recyclerview.widget.RecyclerView>
</androidx.core.widget.NestedScrollView>

View File

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

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,14 @@
-->
<resources>
<!-- Colors -->
<!-- Generic Colors -->
<color name="primary">#ffffff</color>
<color name="accent">#121212</color>
<color name="transparent">#00000000</color>
<color name="defaultBrand">#0082C9</color>
<color name="appbar">@android:color/white</color>
<color name="defaultTint">#202124</color>
<!-- List Colors -->
<color name="list_text">#aaa</color>
</resources>

View File

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

View File

@ -29,6 +29,16 @@
<string name="switch_account">Switch account</string>
<string name="list_mode">List</string>
<string name="search_in_all">Search by name</string>
<string name="share_via">Share via</string>
<string name="share_message">Check out this place: {lat}°N, {lng}°E https://www.openstreetmap.org/#map=17/{lat}/{lng}</string>
<string name="list_context_menu_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 -->
<string name="sort_by">Sort by</string>
@ -40,7 +50,10 @@
<string name="name">Name</string>
<string name="description">Description</string>
<string name="created">Created</string>
<string name="modified">Modified</string>
<string name="category">Category</string>
<string name="coords">Coordinates</string>
<string name="accuracy">Accuracy: {accuracy} m</string>
<string name="location_permission_required">Location permission is required to create a geofavorite.</string>
<string name="confirm">Save</string>
<string name="error_saving_geofavorite">Unable to save geofavorite</string>

View File

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

View File

@ -20,4 +20,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
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