diff --git a/app/src/main/java/net/osmtracker/activity/TrackLogger.java b/app/src/main/java/net/osmtracker/activity/TrackLogger.java index 6ab9153e7..0cbb5f405 100644 --- a/app/src/main/java/net/osmtracker/activity/TrackLogger.java +++ b/app/src/main/java/net/osmtracker/activity/TrackLogger.java @@ -57,6 +57,7 @@ import java.io.File; import java.util.Date; import java.util.HashSet; +import java.util.UUID; /** @@ -609,6 +610,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { if (currentPhotoFile != null && currentPhotoFile.exists()) { Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP); + intent.putExtra(OSMTracker.INTENT_KEY_UUID, UUID.randomUUID().toString()); intent.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); intent.putExtra(OSMTracker.INTENT_KEY_NAME, getResources().getString(R.string.wpt_stillimage)); intent.putExtra(OSMTracker.INTENT_KEY_LINK, currentPhotoFile.getName()); @@ -632,6 +634,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { // Send an intent to inform service to track the waypoint. Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP); + intent.putExtra(OSMTracker.INTENT_KEY_UUID, UUID.randomUUID().toString()); intent.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); intent.putExtra(OSMTracker.INTENT_KEY_NAME, getResources().getString(R.string.wpt_stillimage)); intent.putExtra(OSMTracker.INTENT_KEY_LINK, destFile.getName()); @@ -778,7 +781,7 @@ private void startCamera() { */ private void startGallery() { Intent galleryIntent = new Intent(); - galleryIntent.setType("image/*"); + galleryIntent.setType(DataHelper.MIME_TYPE_IMAGE); galleryIntent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(galleryIntent, REQCODE_GALLERY_CHOSEN); } diff --git a/app/src/main/java/net/osmtracker/activity/WaypointList.java b/app/src/main/java/net/osmtracker/activity/WaypointList.java index bb249e107..0c046c7ee 100644 --- a/app/src/main/java/net/osmtracker/activity/WaypointList.java +++ b/app/src/main/java/net/osmtracker/activity/WaypointList.java @@ -1,13 +1,28 @@ package net.osmtracker.activity; -import net.osmtracker.db.TrackContentProvider; -import net.osmtracker.db.WaypointListAdapter; - +import android.app.AlertDialog; import android.app.ListActivity; +import android.content.DialogInterface; +import android.content.Intent; import android.database.Cursor; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; import android.widget.CursorAdapter; +import android.widget.EditText; import android.widget.ListView; +import androidx.core.content.FileProvider; +import net.osmtracker.R; +import net.osmtracker.db.DataHelper; +import net.osmtracker.db.TrackContentProvider; +import net.osmtracker.db.WaypointListAdapter; +import net.osmtracker.listener.EditWaypointDialogOnClickListener; + +import java.io.File; /** * Activity that lists the previous waypoints tracked by the user. @@ -17,6 +32,8 @@ */ public class WaypointList extends ListActivity { + private static final String TAG = WaypointList.class.getSimpleName(); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -29,12 +46,12 @@ protected void onCreate(Bundle savedInstanceState) { @Override protected void onResume() { Long trackId = getIntent().getExtras().getLong(TrackContentProvider.Schema.COL_TRACK_ID); - + Cursor cursor = getContentResolver().query(TrackContentProvider.waypointsUri(trackId), null, null, null, TrackContentProvider.Schema.COL_TIMESTAMP + " desc"); startManagingCursor(cursor); setListAdapter(new WaypointListAdapter(WaypointList.this, cursor)); - + super.onResume(); } @@ -52,4 +69,152 @@ protected void onPause() { super.onPause(); } + /** + * Handles the selection of a waypoint from the list and opens an edit dialog. + * This dialog allows the user to update the waypoint's name and preview attached files (images or audio). + * + * @param l The ListView where the item was clicked. + * @param v The view that was clicked. + * @param position The position of the clicked item. + * @param id The ID of the clicked waypoint. + */ + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + final Cursor cursor = ((CursorAdapter) getListAdapter()).getCursor(); + final DataHelper dataHelper = new DataHelper(l.getContext()); + LayoutInflater inflater = this.getLayoutInflater(); + + // Inflate the waypoint edit dialog layout + final View editWaypointDialog = inflater.inflate(R.layout.edit_waypoint_dialog, null); + final EditText editWaypointName = editWaypointDialog.findViewById(R.id.edit_waypoint_et_name); + + Button buttonPreview = editWaypointDialog.findViewById(R.id.edit_waypoint_button_preview); + Button buttonUpdate = editWaypointDialog.findViewById(R.id.edit_waypoint_button_update); + Button buttonDelete = editWaypointDialog.findViewById(R.id.edit_waypoint_button_delete); + Button buttonCancel = editWaypointDialog.findViewById(R.id.edit_waypoint_button_cancel); + + // Retrieve existing waypoint name + String oldName = cursor.getString(cursor.getColumnIndex(TrackContentProvider.Schema.COL_NAME)); + editWaypointName.setText(oldName); + editWaypointName.setSelection(oldName.length()); + + // Retrieve waypoint details + final long trackId = cursor.getLong(cursor.getColumnIndex(TrackContentProvider.Schema.COL_TRACK_ID)); + final String uuid = cursor.getString(cursor.getColumnIndex(TrackContentProvider.Schema.COL_UUID)); + final String link = cursor.getString(cursor.getColumnIndex(TrackContentProvider.Schema.COL_LINK)); + + final String filePath = (link != null) ? DataHelper.getTrackDirectory(trackId, l.getContext()) + "/" + link : null; + File file = (filePath != null) ? new File(filePath) : null; + + if (file != null && file.exists()) { + try { + if (isImageFile(filePath) || isAudioFile(filePath)) { + buttonPreview.setVisibility(View.VISIBLE); + } + } catch (Exception e) { + Log.e(TAG, "Error handling file: " + filePath, e); + } + } + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setCancelable(true); + AlertDialog alert = builder.create(); + + // Preview button + buttonPreview.setOnClickListener(new EditWaypointDialogOnClickListener(alert, null) { + @Override + public void onClick(View view) { + if (filePath != null) { + File file = new File(filePath); + Uri fileUri = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) ? + FileProvider.getUriForFile(getApplicationContext(), DataHelper.FILE_PROVIDER_AUTHORITY, file) : + Uri.fromFile(file); + + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + + if (isImageFile(filePath)) { + intent.setDataAndType(fileUri, DataHelper.MIME_TYPE_IMAGE); + } else if (isAudioFile(filePath)) { + intent.setDataAndType(fileUri, DataHelper.MIME_TYPE_AUDIO); + } + + if (intent.resolveActivity(getPackageManager()) != null) { + startActivity(intent); + } + } + alert.dismiss(); + } + }); + + // Update waypoint name + buttonUpdate.setOnClickListener(new EditWaypointDialogOnClickListener(alert, null) { + @Override + public void onClick(View view) { + String newName = editWaypointName.getText().toString(); + dataHelper.updateWayPoint(trackId, uuid, newName, link); + alert.dismiss(); + } + }); + + // Delete waypoint + buttonDelete.setOnClickListener(new EditWaypointDialogOnClickListener(alert, cursor) { + @Override + public void onClick(View view) { + new AlertDialog.Builder(WaypointList.this) + .setTitle(getString(R.string.delete_waypoint_confirm_dialog_title)) + .setMessage(getString(R.string.delete_waypoint_confirm_dialog_msg)) + .setPositiveButton(getString(R.string.delete_waypoint_confirm_bt_ok), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dataHelper.deleteWayPoint(uuid, filePath); + cursor.requery(); + alert.dismiss(); + dialog.dismiss(); + } + }) + .setNegativeButton(getString(R.string.delete_waypoint_confirm_bt_cancel), new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + } + }); + + // Cancel button + buttonCancel.setOnClickListener(new EditWaypointDialogOnClickListener(alert, null) { + @Override + public void onClick(View view) { + alert.dismiss(); + } + }); + + alert.setView(editWaypointDialog); + alert.show(); + + super.onListItemClick(l, v, position, id); + } + + /** + * Checks if a given file path corresponds to an image. + * + * @param path The file path. + * @return True if the file is an image, false otherwise. + */ + private boolean isImageFile(String path) { + return path.endsWith(DataHelper.EXTENSION_JPG); + } + + /** + * Checks if a given file path corresponds to an audio file. + * + * @param path The file path. + * @return True if the file is an audio file, false otherwise. + */ + private boolean isAudioFile(String path) { + return path.endsWith(DataHelper.EXTENSION_3GPP); + } + } diff --git a/app/src/main/java/net/osmtracker/db/DataHelper.java b/app/src/main/java/net/osmtracker/db/DataHelper.java index 47b9ea680..aafdbb751 100644 --- a/app/src/main/java/net/osmtracker/db/DataHelper.java +++ b/app/src/main/java/net/osmtracker/db/DataHelper.java @@ -58,6 +58,16 @@ public class DataHelper { */ public static final String MIME_TYPE_GPX = "application/gpx+xml"; + /** + * Audio Files MIME + */ + public static final String MIME_TYPE_AUDIO = "audio/*"; + + /** + * Image Files MIME + */ + public static final String MIME_TYPE_IMAGE = "image/*"; + /** * APP sign plus FileProvider = authority */ @@ -258,16 +268,25 @@ public void updateWayPoint(long trackId, String uuid, String name, String link) } /** - * Deletes a waypoint + * Deletes a waypoint and its file associated (if exists) * * @param uuid * Unique ID of the target waypoint + * + * @param filepath + * file attached to the waypoint */ - public void deleteWayPoint(String uuid) { + public void deleteWayPoint(String uuid, String filepath) { Log.v(TAG, "Deleting waypoint with uuid '" + uuid); if (uuid != null) { contentResolver.delete(Uri.withAppendedPath(TrackContentProvider.CONTENT_URI_WAYPOINT_UUID, uuid), null, null); } + + // delete file if exists + File file = (filepath != null) ? new File(filepath) : null; + if (file != null && file.exists() && file.delete()) { + Log.v(TAG, "File deleted: " + filepath); + } } @@ -407,27 +426,6 @@ public static File getTrackDirectory(long trackId, Context context) { return _return; } - /* method not in use. TODO: delete code. - public static File getGPXTrackFile(long trackId, ContentResolver contentResolver, Context context) { - - String trackName = getTrackNameInDB(trackId, contentResolver); - - File sdRoot = Environment.getExternalStorageDirectory(); - - // The location where the user has specified gpx files and associated content to be written - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - String userGPXExportDirectoryName = prefs.getString( - OSMTracker.Preferences.KEY_STORAGE_DIR, OSMTracker.Preferences.VAL_STORAGE_DIR); - - // Build storage track path for file creation - String completeGPXTrackPath = sdRoot + userGPXExportDirectoryName.trim() + - File.separator + trackName.trim() + File.separator + - trackName.trim() + DataHelper.EXTENSION_GPX; - - return new File(completeGPXTrackPath); - } - */ - public static String getTrackNameInDB(long trackId, ContentResolver contentResolver) { String trackName = ""; Uri trackUri = ContentUris.withAppendedId(TrackContentProvider.CONTENT_URI_TRACK, trackId); diff --git a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java index ffb551ed0..38f344221 100644 --- a/app/src/main/java/net/osmtracker/db/TrackContentProvider.java +++ b/app/src/main/java/net/osmtracker/db/TrackContentProvider.java @@ -471,7 +471,6 @@ public static final class Schema { public static final String TBL_TRACKPOINT = "trackpoint"; public static final String TBL_WAYPOINT = "waypoint"; public static final String TBL_TRACK = "track"; - public static final String COL_ID = "_id"; public static final String COL_TRACK_ID = "track_id"; public static final String COL_UUID = "uuid"; diff --git a/app/src/main/java/net/osmtracker/db/WaypointListAdapter.java b/app/src/main/java/net/osmtracker/db/WaypointListAdapter.java index b8f847a45..2f8ebc75c 100644 --- a/app/src/main/java/net/osmtracker/db/WaypointListAdapter.java +++ b/app/src/main/java/net/osmtracker/db/WaypointListAdapter.java @@ -13,6 +13,7 @@ import android.view.ViewGroup; import android.widget.CursorAdapter; import android.widget.RelativeLayout; +import android.widget.TableLayout; import android.widget.TextView; /** @@ -45,15 +46,15 @@ public WaypointListAdapter(Context context, Cursor c) { @Override public void bindView(View view, Context context, Cursor cursor) { - RelativeLayout rl = (RelativeLayout) view; - bind(cursor, rl, context); + TableLayout tl = (TableLayout) view; + bind(cursor, tl, context); } @Override public View newView(Context context, Cursor cursor, ViewGroup vg) { - RelativeLayout rl = (RelativeLayout) LayoutInflater.from(vg.getContext()).inflate(R.layout.waypointlist_item, + TableLayout tl = (TableLayout) LayoutInflater.from(vg.getContext()).inflate(R.layout.waypointlist_item, vg, false); - return bind(cursor, rl, context); + return bind(cursor, tl, context); } /** @@ -61,16 +62,16 @@ public View newView(Context context, Cursor cursor, ViewGroup vg) { * * @param cursor * Cursor to pull data - * @param rl + * @param tl * RelativeView representing one item * @param context * Context, to get resources * @return The relative view with data bound. */ - private View bind(Cursor cursor, RelativeLayout rl, Context context) { - TextView vName = (TextView) rl.findViewById(R.id.wplist_item_name); - TextView vLocation = (TextView) rl.findViewById(R.id.wplist_item_location); - TextView vTimestamp = (TextView) rl.findViewById(R.id.wplist_item_timestamp); + private View bind(Cursor cursor, TableLayout tl, Context context) { + TextView vName = (TextView) tl.findViewById(R.id.wplist_item_name); + TextView vLocation = (TextView) tl.findViewById(R.id.wplist_item_location); + TextView vTimestamp = (TextView) tl.findViewById(R.id.wplist_item_timestamp); // Bind name String name = cursor.getString(cursor.getColumnIndex(TrackContentProvider.Schema.COL_NAME)); @@ -103,8 +104,7 @@ private View bind(Cursor cursor, RelativeLayout rl, Context context) { // Bind timestamp Date ts = new Date(cursor.getLong(cursor.getColumnIndex(TrackContentProvider.Schema.COL_TIMESTAMP))); vTimestamp.setText(DATE_FORMATTER.format(ts)); - - return rl; + return tl; } } diff --git a/app/src/main/java/net/osmtracker/listener/EditWaypointDialogOnClickListener.java b/app/src/main/java/net/osmtracker/listener/EditWaypointDialogOnClickListener.java new file mode 100644 index 000000000..181a11ba2 --- /dev/null +++ b/app/src/main/java/net/osmtracker/listener/EditWaypointDialogOnClickListener.java @@ -0,0 +1,24 @@ +package net.osmtracker.listener; + +import android.app.AlertDialog; +import android.database.Cursor; +import android.view.View; + +/** + * Class that implements an OnClickListener to display an edit waypoint dialog. + */ +public class EditWaypointDialogOnClickListener implements View.OnClickListener { + + private Cursor cursor; + + protected AlertDialog alert; + + protected EditWaypointDialogOnClickListener(AlertDialog alert, Cursor cu) { + this.cursor = cu; // Assigns the received cursor to the class attribute + this.alert = alert; // Assigns the received alert to the class attribute + } + + @Override + public void onClick(View view) { + } +} diff --git a/app/src/main/java/net/osmtracker/listener/TagButtonOnClickListener.java b/app/src/main/java/net/osmtracker/listener/TagButtonOnClickListener.java index 149f9af5b..4b5f35b4c 100644 --- a/app/src/main/java/net/osmtracker/listener/TagButtonOnClickListener.java +++ b/app/src/main/java/net/osmtracker/listener/TagButtonOnClickListener.java @@ -1,16 +1,18 @@ package net.osmtracker.listener; -import net.osmtracker.OSMTracker; -import net.osmtracker.R; - import android.content.Intent; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.Toast; +import java.util.UUID; + +import net.osmtracker.OSMTracker; +import net.osmtracker.R; import net.osmtracker.db.TrackContentProvider; + /** * Listener for standard waypoint tag button. * Sends an Intent to track waypoint. Waypoint name is the @@ -36,6 +38,7 @@ public void onClick(View view) { Intent intent = new Intent(OSMTracker.INTENT_TRACK_WP); intent.putExtra(TrackContentProvider.Schema.COL_TRACK_ID, currentTrackId); intent.putExtra(OSMTracker.INTENT_KEY_NAME, label); + intent.putExtra(OSMTracker.INTENT_KEY_UUID, UUID.randomUUID().toString()); String packageName = view.getContext().getPackageName(); intent.setPackage(packageName); diff --git a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java index 77ae1987c..fb785d279 100644 --- a/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java +++ b/app/src/main/java/net/osmtracker/service/gps/GPSLogger.java @@ -148,8 +148,15 @@ public void onReceive(Context context, Intent intent) { // Delete an existing waypoint Bundle extras = intent.getExtras(); if (extras != null) { + Long trackId = extras.getLong(TrackContentProvider.Schema.COL_TRACK_ID); String uuid = extras.getString(OSMTracker.INTENT_KEY_UUID); - dataHelper.deleteWayPoint(uuid); + String link = extras.getString(OSMTracker.INTENT_KEY_LINK); + String filePath = null; + try { + filePath = link.equals("null") ? null : DataHelper.getTrackDirectory(trackId, context) + "/" + link; + } + catch(NullPointerException ne){} + dataHelper.deleteWayPoint(uuid, filePath); } } else if (OSMTracker.INTENT_START_TRACKING.equals(intent.getAction())) { Bundle extras = intent.getExtras(); diff --git a/app/src/main/res/layout/edit_waypoint_dialog.xml b/app/src/main/res/layout/edit_waypoint_dialog.xml new file mode 100644 index 000000000..dfffa9478 --- /dev/null +++ b/app/src/main/res/layout/edit_waypoint_dialog.xml @@ -0,0 +1,78 @@ + + + + +