From 9a14241915305174425a78b47703630e5ca82c00 Mon Sep 17 00:00:00 2001 From: y20k Date: Wed, 17 May 2017 15:43:53 +0200 Subject: [PATCH] got gpx export working - yay (#14) --- app/build.gradle | 6 +- app/src/main/AndroidManifest.xml | 2 + .../java/org/y20k/trackbook/MainActivity.java | 171 ++++++++++------ .../trackbook/MainActivityMapFragment.java | 31 ++- .../trackbook/MainActivityTrackFragment.java | 114 ++++++++--- .../y20k/trackbook/helpers/DialogHelper.java | 80 ++++++++ .../y20k/trackbook/helpers/ExportHelper.java | 186 ++++++++++++++++++ .../org/y20k/trackbook/helpers/GpxHelper.java | 110 ----------- .../org/y20k/trackbook/helpers/LogHelper.java | 2 +- .../y20k/trackbook/helpers/StorageHelper.java | 63 +++--- .../y20k/trackbook/helpers/TrackbookKeys.java | 20 +- .../res/drawable/ic_export_white_24dp.xml | 9 + .../main/res/layout/fragment_main_track.xml | 12 ++ app/src/main/res/values-de/strings.xml | 13 +- app/src/main/res/values-ja/strings.xml | 9 + app/src/main/res/values-nl/strings.xml | 9 + app/src/main/res/values/strings.xml | 11 +- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- 19 files changed, 611 insertions(+), 243 deletions(-) create mode 100644 app/src/main/java/org/y20k/trackbook/helpers/DialogHelper.java create mode 100644 app/src/main/java/org/y20k/trackbook/helpers/ExportHelper.java delete mode 100644 app/src/main/java/org/y20k/trackbook/helpers/GpxHelper.java create mode 100644 app/src/main/res/drawable/ic_export_white_24dp.xml diff --git a/app/build.gradle b/app/build.gradle index 41ed3bf..5dff480 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,9 +22,9 @@ android { dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') - compile 'com.android.support:appcompat-v7:25.1.1' - compile 'com.android.support:design:25.1.1' - compile 'com.android.support:cardview-v7:25.1.1' + compile 'com.android.support:appcompat-v7:25.2.0' + compile 'com.android.support:design:25.2.0' + compile 'com.android.support:cardview-v7:25.2.0' compile 'org.osmdroid:osmdroid-android:5.6.4' compile 'com.google.code.gson:gson:2.8.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3518a30..8017a70 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/TrackbookAppTheme.NoActionBar" + android:resizeableActivity="true" android:launchMode="singleTop"> @@ -39,6 +40,7 @@ android:name=".InfosheetActivity" android:label="@string/title_activity_infosheet" android:parentActivityName=".MainActivity" + android:resizeableActivity="true" android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"> diff --git a/app/src/main/java/org/y20k/trackbook/MainActivity.java b/app/src/main/java/org/y20k/trackbook/MainActivity.java index 512044a..ea40512 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivity.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivity.java @@ -18,9 +18,9 @@ package org.y20k.trackbook; import android.Manifest; import android.annotation.TargetApi; +import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; @@ -34,12 +34,12 @@ import android.support.annotation.Nullable; import android.support.design.widget.FloatingActionButton; import android.support.design.widget.Snackbar; import android.support.design.widget.TabLayout; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.content.ContextCompat; import android.support.v4.content.LocalBroadcastManager; -import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.SparseArray; @@ -51,6 +51,7 @@ import android.widget.Button; import android.widget.LinearLayout; import android.widget.Toast; +import org.y20k.trackbook.helpers.DialogHelper; import org.y20k.trackbook.helpers.LogHelper; import org.y20k.trackbook.helpers.NotificationHelper; import org.y20k.trackbook.helpers.TrackbookKeys; @@ -256,6 +257,64 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { } + /* Handles FloatingActionButton dialog results */ + public void onFloatingActionButtonResult(int requestCode, int resultCode) { + switch(requestCode) { + case RESULT_SAVE_DIALOG: + if (resultCode == Activity.RESULT_OK) { + // user chose SAVE + handleStateAfterSave(); + } else if (resultCode == Activity.RESULT_CANCELED){ + LogHelper.v(LOG_TAG, "Save dialog result: CANCEL"); + } + break; + case RESULT_CLEAR_DIALOG: + if (resultCode == Activity.RESULT_OK) { + // user chose CLEAR + handleStateAfterClear(); + } else if (resultCode == Activity.RESULT_CANCELED){ + LogHelper.v(LOG_TAG, "Clear map: User chose CANCEL."); + } + break; + } + } + + + /* Handles the visual state after a save action */ + private void handleStateAfterSave() { + // display and update track tab + mSelectedTab = FRAGMENT_ID_TRACK; + mViewPager.setCurrentItem(mSelectedTab); + + // dismiss notification + NotificationHelper.stop(); + + // hide Floating Action Button sub menu + showFloatingActionButtonMenu(false); + + // update Floating Action Button icon + mFloatingActionButtonState = FAB_STATE_DEFAULT; + setFloatingActionButtonState(); + } + + + /* Handles the visual state after a save action */ + private void handleStateAfterClear() { + // notify user + Toast.makeText(this, getString(R.string.toast_message_track_clear), Toast.LENGTH_LONG).show(); + + // dismiss notification + NotificationHelper.stop(); + + // hide Floating Action Button sub menu + showFloatingActionButtonMenu(false); + + // update Floating Action Button icon + mFloatingActionButtonState = FAB_STATE_DEFAULT; + setFloatingActionButtonState(); + } + + /* Loads state of Floating Action Button from preferences */ private void loadFloatingActionButtonState(Context context) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); @@ -348,7 +407,9 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { mFloatingActionButtonSubMenu1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - handleButtonSaveAndClearClick(); + MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); + mainActivityMapFragment.onActivityResult(RESULT_SAVE_DIALOG, Activity.RESULT_OK, getIntent()); + handleStateAfterSave(); } }); } @@ -356,24 +417,16 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { mFloatingActionButtonSubMenu2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - // ask user to confirm the clear action - AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); - builder.setMessage(R.string.dialog_clear_content); - builder.setNegativeButton(R.string.dialog_default_action_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // do nothing - } - }); - builder.setPositiveButton(R.string.dialog_clear_action_clear, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // clear current track - handleButtonClearClick(); - } - }); - AlertDialog dialog = builder.create(); - dialog.show(); + int dialogTitle = -1; + String dialogMessage = getString(R.string.dialog_clear_content); + int dialogPositiveButton = R.string.dialog_clear_action_clear; + int dialogNegativeButton = R.string.dialog_default_action_cancel; + + // show delete dialog - results are handles by onActivityResult + MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); + DialogFragment dialogFragment = DialogHelper.newInstance(dialogTitle, dialogMessage, dialogPositiveButton, dialogNegativeButton); + dialogFragment.setTargetFragment(mainActivityMapFragment, RESULT_CLEAR_DIALOG); + dialogFragment.show(getSupportFragmentManager(), "ClearDialog"); } }); } @@ -465,46 +518,46 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys { } - /* Handles tap on the save and clear button */ - private void handleButtonSaveAndClearClick() { - // clear map and save track - MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); - mainActivityMapFragment.clearMap(true); - - // display and update track tab - mSelectedTab = FRAGMENT_ID_TRACK; - mViewPager.setCurrentItem(mSelectedTab); - - // dismiss notification - NotificationHelper.stop(); - - // hide Floating Action Button sub menu - showFloatingActionButtonMenu(false); - - // update Floating Action Button icon - mFloatingActionButtonState = FAB_STATE_DEFAULT; - setFloatingActionButtonState(); - } +// /* Handles tap on the save and clear button */ +// private void handleButtonSaveAndClearClick() { +// // clear map and save track +// MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); +// mainActivityMapFragment.clearMap(true); +// +// // display and update track tab +// mSelectedTab = FRAGMENT_ID_TRACK; +// mViewPager.setCurrentItem(mSelectedTab); +// +// // dismiss notification +// NotificationHelper.stop(); +// +// // hide Floating Action Button sub menu +// showFloatingActionButtonMenu(false); +// +// // update Floating Action Button icon +// mFloatingActionButtonState = FAB_STATE_DEFAULT; +// setFloatingActionButtonState(); +// } - /* Handles tap on the clear button */ - private void handleButtonClearClick() { - // clear map, do not save track - MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); - mainActivityMapFragment.clearMap(false); - - // dismiss notification - NotificationHelper.stop(); - - // hide Floating Action Button sub menu - showFloatingActionButtonMenu(false); - - // update Floating Action Button icon - mFloatingActionButtonState = FAB_STATE_DEFAULT; - setFloatingActionButtonState(); - - Toast.makeText(this, getString(R.string.toast_message_track_clear), Toast.LENGTH_LONG).show(); - } +// /* Handles tap on the clear button */ +// private void handleButtonClearClick() { +// // clear map, do not save track +// MainActivityMapFragment mainActivityMapFragment = (MainActivityMapFragment) mSectionsPagerAdapter.getFragment(FRAGMENT_ID_MAP); +// mainActivityMapFragment.clearMap(false); +// +// // dismiss notification +// NotificationHelper.stop(); +// +// // hide Floating Action Button sub menu +// showFloatingActionButtonMenu(false); +// +// // update Floating Action Button icon +// mFloatingActionButtonState = FAB_STATE_DEFAULT; +// setFloatingActionButtonState(); +// +// Toast.makeText(this, getString(R.string.toast_message_track_clear), Toast.LENGTH_LONG).show(); +// } /* Set state of FloatingActionButton */ diff --git a/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java b/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java index d24abe2..b21c218 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivityMapFragment.java @@ -348,6 +348,35 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys { } + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch(requestCode) { + case RESULT_SAVE_DIALOG: + if (resultCode == Activity.RESULT_OK) { + // user chose SAVE - clear map and save track + clearMap(true); + // FloatingActionButton state is already being handled in MainActivity + // ((MainActivity)mActivity).onFloatingActionButtonResult(requestCode, resultCode); + LogHelper.v(LOG_TAG, "Save dialog result: SAVE"); + } else if (resultCode == Activity.RESULT_CANCELED){ + LogHelper.v(LOG_TAG, "Save dialog result: CANCEL"); + } + break; + case RESULT_CLEAR_DIALOG: + if (resultCode == Activity.RESULT_OK) { + // User chose CLEAR - clear map, do not save track + clearMap(false); + // handle FloatingActionButton state in MainActivity + ((MainActivity)mActivity).onFloatingActionButtonResult(requestCode, resultCode); + } else if (resultCode == Activity.RESULT_CANCELED){ + LogHelper.v(LOG_TAG, "Clear dialog result: CANCEL"); + } + break; + } + } + + @Override public void onSaveInstanceState(Bundle outState) { outState.putBoolean(INSTANCE_FIRST_START, mFirstStart); @@ -389,7 +418,7 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys { /* Removes track crumbs from map */ - public void clearMap(boolean saveTrack) { + private void clearMap(boolean saveTrack) { // clear map if (mTrackOverlay != null) { diff --git a/app/src/main/java/org/y20k/trackbook/MainActivityTrackFragment.java b/app/src/main/java/org/y20k/trackbook/MainActivityTrackFragment.java index afbd897..336e86a 100644 --- a/app/src/main/java/org/y20k/trackbook/MainActivityTrackFragment.java +++ b/app/src/main/java/org/y20k/trackbook/MainActivityTrackFragment.java @@ -19,7 +19,6 @@ package org.y20k.trackbook; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.location.Location; @@ -28,9 +27,10 @@ import android.os.Bundle; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.design.widget.BottomSheetBehavior; +import android.support.v4.app.DialogFragment; import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; import android.support.v4.content.LocalBroadcastManager; -import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -48,7 +48,9 @@ import org.osmdroid.views.overlay.ItemizedIconOverlay; import org.osmdroid.views.overlay.compass.CompassOverlay; import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider; import org.y20k.trackbook.core.Track; +import org.y20k.trackbook.helpers.DialogHelper; import org.y20k.trackbook.helpers.DropdownAdapter; +import org.y20k.trackbook.helpers.ExportHelper; import org.y20k.trackbook.helpers.LogHelper; import org.y20k.trackbook.helpers.MapHelper; import org.y20k.trackbook.helpers.StorageHelper; @@ -69,7 +71,7 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O /* Main class variables */ - private Activity mActivity; + private FragmentActivity mActivity; private View mRootView; private MapView mMapView; private LinearLayout mOnboardingView; @@ -175,8 +177,10 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O mTrackManagementLayout = (LinearLayout) mRootView.findViewById(R.id.track_management_layout); mDropdown = (Spinner) mRootView.findViewById(R.id.track_selector); - // + // attach listeners to export and delete buttons + ImageButton exportButton = (ImageButton) mRootView.findViewById(R.id.export_button); ImageButton deleteButton = (ImageButton) mRootView.findViewById(R.id.delete_button); + exportButton.setOnClickListener(getExportButtonListener()); deleteButton.setOnClickListener(getDeleteButtonListener()); // get views for statistics sheet @@ -291,6 +295,34 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + switch(requestCode) { + case RESULT_DELETE_DIALOG: + if (resultCode == Activity.RESULT_OK) { + LogHelper.v(LOG_TAG, "Delete dialog result: DELETE"); + } else if (resultCode == Activity.RESULT_CANCELED){ + LogHelper.v(LOG_TAG, "Delete dialog result: CANCEL"); + } + break; + case RESULT_EXPORT_DIALOG: + if (resultCode == Activity.RESULT_OK) { + // User chose EXPORT + ExportHelper exportHelper = new ExportHelper(mActivity); + exportHelper.exportToGpx(mTrack); + } else if (resultCode == Activity.RESULT_CANCELED){ + // User chose CANCEL + LogHelper.v(LOG_TAG, "Export to GPX: User chose CANCEL."); + } + break; + } + } + + + + /* Displays map and statistics for track */ private void displayTrack() { GeoPoint position; @@ -360,9 +392,11 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O switch (newState) { case BottomSheetBehavior.STATE_EXPANDED: // statistics sheet expanded + mTrackManagementLayout.setVisibility(View.INVISIBLE); break; case BottomSheetBehavior.STATE_COLLAPSED: // statistics sheet collapsed + mTrackManagementLayout.setVisibility(View.VISIBLE); mStatisticsSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); break; case BottomSheetBehavior.STATE_HIDDEN: @@ -376,6 +410,12 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O @Override public void onSlide(@NonNull View bottomSheet, float slideOffset) { // react to dragging events + if (slideOffset < 0.5f) { + mTrackManagementLayout.setVisibility(View.VISIBLE); + } else { + mTrackManagementLayout.setVisibility(View.INVISIBLE); + } + } }; } @@ -386,26 +426,54 @@ public class MainActivityTrackFragment extends Fragment implements AdapterView.O return new View.OnClickListener() { @Override public void onClick(View view) { - // ask user to confirm the delete action + // get text elements for delete dialog + int dialogTitle = R.string.dialog_delete_title; String dialogMessage = getString(R.string.dialog_delete_content) + " " + mTrack.getTrackDuration() + " | " + mTrack.getTrackDistance(); - AlertDialog.Builder builder = new AlertDialog.Builder(mActivity); - builder.setTitle(R.string.dialog_delete_title); - builder.setMessage(dialogMessage); - builder.setNegativeButton(R.string.dialog_default_action_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // do nothing - } - }); - builder.setPositiveButton(R.string.dialog_delete_action_delete, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - // delete current track - // TODO implement - } - }); - AlertDialog dialog = builder.create(); - dialog.show(); + int dialogPositiveButton = R.string.dialog_delete_action_delete; + int dialogNegativeButton = R.string.dialog_default_action_cancel; + + // show delete dialog - results are handles by onActivityResult + DialogFragment dialogFragment = DialogHelper.newInstance(dialogTitle, dialogMessage, dialogPositiveButton, dialogNegativeButton); + dialogFragment.setTargetFragment(MainActivityTrackFragment.this, RESULT_DELETE_DIALOG); + dialogFragment.show(mActivity.getSupportFragmentManager(), "DeleteDialog"); + } + }; + } + + + /* Creates OnClickListener for the export button - needed in onCreateView */ + private View.OnClickListener getExportButtonListener() { + return new View.OnClickListener() { + @Override + public void onClick(View view) { + // dialog text components + int dialogTitle; + String dialogMessage; + int dialogPositiveButton; + int dialogNegativeButton; + + // create an ExportHelper + final ExportHelper exportHelper = new ExportHelper(mActivity); + + // get text elements for delete dialog + if (exportHelper.gpxFileExists(mTrack)) { + // CASE: OVERWRITE - GPX file exists + dialogTitle = R.string.dialog_export_title_overwrite; + dialogMessage = getString(R.string.dialog_export_content_overwrite) + " (" + mTrack.getTrackDuration() + " | " + mTrack.getTrackDistance() + ")"; + dialogPositiveButton = R.string.dialog_export_action_overwrite; + dialogNegativeButton = R.string.dialog_default_action_cancel; + } else { + // CASE: EXPORT - GPX file does NOT yet exits + dialogTitle = R.string.dialog_export_title_export; + dialogMessage = getString(R.string.dialog_export_content_export) + " (" + mTrack.getTrackDuration() + " | " + mTrack.getTrackDistance() + ")"; + dialogPositiveButton = R.string.dialog_export_action_export; + dialogNegativeButton = R.string.dialog_default_action_cancel; + } + + // show delete dialog - results are handles by onActivityResult + DialogFragment dialogFragment = DialogHelper.newInstance(dialogTitle, dialogMessage, dialogPositiveButton, dialogNegativeButton); + dialogFragment.setTargetFragment(MainActivityTrackFragment.this, RESULT_EXPORT_DIALOG); + dialogFragment.show(mActivity.getSupportFragmentManager(), "ExportDialog"); } }; } diff --git a/app/src/main/java/org/y20k/trackbook/helpers/DialogHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/DialogHelper.java new file mode 100644 index 0000000..c12a9c0 --- /dev/null +++ b/app/src/main/java/org/y20k/trackbook/helpers/DialogHelper.java @@ -0,0 +1,80 @@ +/** + * DialogHelper.java + * Implements the DialogHelper class + * A DialogHelper creates a customizable alert dialog + * + * This file is part of + * TRACKBOOK - Movement Recorder for Android + * + * Copyright (c) 2016-17 - Y20K.org + * Licensed under the MIT-License + * http://opensource.org/licenses/MIT + * + * Trackbook uses osmdroid - OpenStreetMap-Tools for Android + * https://github.com/osmdroid/osmdroid + */ + +package org.y20k.trackbook.helpers; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.DialogFragment; + + +/** + * DialogHelper class + */ +public class DialogHelper extends DialogFragment implements TrackbookKeys { + + /* Constructs a new instance */ + public static DialogHelper newInstance(int title, String message, int positiveButton, int negativeButton) { + DialogHelper fragment = new DialogHelper(); + Bundle args = new Bundle(); + args.putInt(ARG_DIALOG_TITLE, title); + args.putString(ARG_DIALOG_MESSAGE, message); + args.putInt(ARG_DIALOG_BUTTON_POSITIVE, positiveButton); + args.putInt(ARG_DIALOG_BUTTON_NEGATIVE, negativeButton); + fragment.setArguments(args); + return fragment; + } + + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Bundle args = getArguments(); + + // get text elements + int title = args.getInt(ARG_DIALOG_TITLE); + String message = args.getString(ARG_DIALOG_MESSAGE); + int positiveButton = args.getInt(ARG_DIALOG_BUTTON_POSITIVE); + int negativeButton = args.getInt(ARG_DIALOG_BUTTON_NEGATIVE); + + // build dialog + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(getActivity()); + if (title != -1) { + dialogBuilder.setTitle(title); + } + dialogBuilder.setTitle(message); + dialogBuilder.setPositiveButton(positiveButton, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent()); + } + } + ); + dialogBuilder.setNegativeButton(negativeButton, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent()); + } + } + ); + + return dialogBuilder.create(); + } +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/ExportHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/ExportHelper.java new file mode 100644 index 0000000..c03d35d --- /dev/null +++ b/app/src/main/java/org/y20k/trackbook/helpers/ExportHelper.java @@ -0,0 +1,186 @@ +/** + * ExportHelper.java + * Implements the ExportHelper class + * A ExportHelper can convert Track object into a GPX string + * + * This file is part of + * TRACKBOOK - Movement Recorder for Android + * + * Copyright (c) 2016-17 - Y20K.org + * Licensed under the MIT-License + * http://opensource.org/licenses/MIT + * + * Trackbook uses osmdroid - OpenStreetMap-Tools for Android + * https://github.com/osmdroid/osmdroid + */ + +package org.y20k.trackbook.helpers; + +import android.content.Context; +import android.location.Location; +import android.os.Environment; +import android.widget.Toast; + +import org.y20k.trackbook.R; +import org.y20k.trackbook.core.Track; +import org.y20k.trackbook.core.WayPoint; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +/** + * ExportHelper class + */ +public class ExportHelper implements TrackbookKeys { + + /* Define log tag */ + private static final String LOG_TAG = ExportHelper.class.getSimpleName(); + + + /* Main class variables */ +// private final Track mTrack; + private final Context mContext; + private File mFolder; + + + /* Constructor */ + public ExportHelper(Context context) { + mContext = context; + mFolder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + } + + + /* Checks if a GPX file for given track is already present */ + public boolean gpxFileExists(Track track) { + return createFile(track).exists(); + } + + + /* Exports given track to GPX */ + public boolean exportToGpx(Track track) { + + // create "Download" folder if necessary + if (mFolder != null && !mFolder.exists()) { + LogHelper.v(LOG_TAG, "Creating new folder: " + mFolder.toString()); + mFolder.mkdirs(); + } + + // get file for given track + File gpxFile = createFile(track); + + // get GPX string representation for given track + String gpxString = createGpxString(track); + + // write GPX file + if (writeGpxToFile(gpxString, gpxFile)) { + String toastMessage = mContext.getResources().getString(R.string.toast_message_export_success) + " " + gpxFile.toString(); + Toast.makeText(mContext, toastMessage, Toast.LENGTH_LONG).show(); + return true; + } else { + String toastMessage = mContext.getResources().getString(R.string.toast_message_export_fail) + " " + gpxFile.toString(); + Toast.makeText(mContext, toastMessage, Toast.LENGTH_LONG).show(); + return false; + } + } + + + /* Return a GPX filepath for a given track */ + private File createFile(Track track) { + Date recordingStart = track.getRecordingStart(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); + return new File(mFolder, dateFormat.format(recordingStart) + FILE_TYPE_GPX_EXTENSION); + } + + + /* Writes given GPX string to Download folder */ + private boolean writeGpxToFile (String gpxString, File gpxFile) { + // write track + try (BufferedWriter bw = new BufferedWriter(new FileWriter(gpxFile))) { + LogHelper.v(LOG_TAG, "Saving track to external storage: " + gpxFile.toString()); + bw.write(gpxString); + return true; + } catch (IOException e) { + LogHelper.e(LOG_TAG, "Unable to saving track to external storage (IOException): " + gpxFile.toString()); + return false; + } + } + + + /* Creates GPX formatted string */ + private String createGpxString(Track track) { + String gpxString; + + // add header + gpxString = "\n" + + "\n"; + + // add track + gpxString = gpxString + addTrack(track); + + // add closing tag + gpxString = gpxString + "\n"; + + return gpxString; + } + + + /* Creates Track */ + private String addTrack(Track track) { + StringBuilder gpxTrack = new StringBuilder(""); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + + // add opening track tag + gpxTrack.append("\t\n"); + + // add name to track + gpxTrack.append("\t\t"); + gpxTrack.append("test"); + gpxTrack.append("\n"); + + // add opening track segment tag + gpxTrack.append("\t\t\n"); + + // add route point + for (WayPoint wayPoint:track.getWayPoints()) { + // get location from waypoint + Location location = wayPoint.getLocation(); + + // add longitude and latitude + gpxTrack.append("\t\t\t\n"); + + // add time + gpxTrack.append("\t\t\t\t\n"); + + // add altitude + gpxTrack.append("\t\t\t\t"); + gpxTrack.append(location.getAltitude()); + gpxTrack.append("\n"); + + // add closing tag + gpxTrack.append("\t\t\t\n"); + } + + // add closing track segment tag + gpxTrack.append("\t\t\n"); + + // add closing track tag + gpxTrack.append("\t\n"); + + return gpxTrack.toString(); + } + +} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/GpxHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/GpxHelper.java deleted file mode 100644 index 8e6efc6..0000000 --- a/app/src/main/java/org/y20k/trackbook/helpers/GpxHelper.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * GpxHelper.java - * Implements the GpxHelper class - * A GpxHelper can convert Track object into a GPX string - * - * This file is part of - * TRACKBOOK - Movement Recorder for Android - * - * Copyright (c) 2016-17 - Y20K.org - * Licensed under the MIT-License - * http://opensource.org/licenses/MIT - * - * Trackbook uses osmdroid - OpenStreetMap-Tools for Android - * https://github.com/osmdroid/osmdroid - */ - -package org.y20k.trackbook.helpers; - -import android.location.Location; - -import org.y20k.trackbook.core.Track; -import org.y20k.trackbook.core.WayPoint; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - -/** - * GpxHelper class - */ -public class GpxHelper { - - /* Define log tag */ - private static final String LOG_TAG = GpxHelper.class.getSimpleName(); - - - /* Main class variables */ - private final Track mTrack; - - - /* Constructor */ - public GpxHelper(Track track) { - mTrack = track; - } - - - /* Creates GPX formatted string */ - public String createGpxString() { - String gpxString; - - // add header - gpxString = "\n" + - "\n"; - - // add track - gpxString = gpxString + addTrack(); - - // add closing tag - gpxString = gpxString + "\n"; - - // todo remove - LogHelper.v(LOG_TAG, "GPX output:\n" + gpxString); - - return gpxString; - } - - - /* Creates Track */ - private String addTrack() { - StringBuilder gpxTrack = new StringBuilder(""); - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); - - // add opening route tag - gpxTrack.append("\t\n"); - - // add route point - for (WayPoint wayPoint:mTrack.getWayPoints()) { - // get location from waypoint - Location location = wayPoint.getLocation(); - - // add longitude and latitude - gpxTrack.append("\t\t\n"); - - // add time - gpxTrack.append("\t\t\t\n"); - - // add altitude - gpxTrack.append("\t\t\t"); - gpxTrack.append(location.getAltitude()); - gpxTrack.append("\n"); - - // add closing tag - gpxTrack.append("\t\t\n"); - } - - // add closing route tag - gpxTrack.append("\t\n"); - - return gpxTrack.toString(); - } - -} diff --git a/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.java index 184d60b..cea1d5c 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/LogHelper.java @@ -26,7 +26,7 @@ import android.util.Log; */ public final class LogHelper { - private final static boolean mTesting = false; + private final static boolean mTesting = true; public static void d(final String tag, String message) { // include logging only in debug versions diff --git a/app/src/main/java/org/y20k/trackbook/helpers/StorageHelper.java b/app/src/main/java/org/y20k/trackbook/helpers/StorageHelper.java index 33405bc..d489fe2 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/StorageHelper.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/StorageHelper.java @@ -36,11 +36,9 @@ import java.io.FileWriter; import java.io.IOException; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Date; -import java.util.List; import java.util.Locale; @@ -64,17 +62,17 @@ public class StorageHelper implements TrackbookKeys { mActivity = activity; // get "tracks" folder - mFolder = mActivity.getExternalFilesDir(DIRECTORY_NAME); + mFolder = mActivity.getExternalFilesDir(TRACKS_DIRECTORY_NAME); // mFolder = getTracksDirectory(); - // create folder if necessary + // create "tracks" folder if necessary if (mFolder != null && !mFolder.exists()) { LogHelper.v(LOG_TAG, "Creating new folder: " + mFolder.toString()); - mFolder.mkdir(); + mFolder.mkdirs(); } // create temp file object - mTempFile = new File(mFolder.toString() + "/" + FILE_NAME_TEMP + FILE_TYPE_EXTENSION); + mTempFile = new File(mFolder.toString() + "/" + FILE_NAME_TEMP + FILE_TYPE_TRACKBOOK_EXTENSION); // delete old track - exclude temp file deleteOldTracks(false); @@ -96,9 +94,6 @@ public class StorageHelper implements TrackbookKeys { /* Saves track object to file */ public boolean saveTrack(@Nullable Track track, int fileType) { - // get "tracks" folder - mFolder = mActivity.getExternalFilesDir(DIRECTORY_NAME); - Date recordingStart = null; if (track != null) { recordingStart = track.getRecordingStart(); @@ -109,11 +104,11 @@ public class StorageHelper implements TrackbookKeys { String fileName; if (fileType == FILE_TEMP_TRACK) { // case: temp file - fileName = FILE_NAME_TEMP + FILE_TYPE_EXTENSION; + fileName = FILE_NAME_TEMP + FILE_TYPE_TRACKBOOK_EXTENSION; } else { // case: regular file DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US); - fileName = dateFormat.format(recordingStart) + FILE_TYPE_EXTENSION; + fileName = dateFormat.format(recordingStart) + FILE_TYPE_TRACKBOOK_EXTENSION; } File file = new File(mFolder.toString() + "/" + fileName); @@ -196,21 +191,21 @@ public class StorageHelper implements TrackbookKeys { } - /* Gets a list of tracks based on their file names */ - public List getListOfTracks() { - List listOfTracks = new ArrayList(); - - // get files and sort them - File[] files = mFolder.listFiles(); - files = sortFiles(files); - - for (File file : files) { - listOfTracks.add(file.getName()); - } - - // TODO HANDLE CASE: EMPTY FILE LIST - return listOfTracks; - } +// /* Gets a list of tracks based on their file names */ +// public List getListOfTracks() { +// List listOfTracks = new ArrayList(); +// +// // get files and sort them +// File[] files = mFolder.listFiles(); +// files = sortFiles(files); +// +// for (File file : files) { +// listOfTracks.add(file.getName()); +// } +// +// // TODO HANDLE CASE: EMPTY FILE LIST +// return listOfTracks; +// } // loads file and parses it into a track @@ -252,14 +247,11 @@ public class StorageHelper implements TrackbookKeys { /* Gets most current track from directory */ private File getMostCurrentTrack() { - // get "tracks" folder - mFolder = mActivity.getExternalFilesDir(DIRECTORY_NAME); - if (mFolder != null && mFolder.isDirectory()) { // get files and sort them File[] files = mFolder.listFiles(); files = sortFiles(files); - if (files.length > 0 && files[0].getName().endsWith(FILE_TYPE_EXTENSION) && !files[0].equals(mTempFile)){ + if (files.length > 0 && files[0].getName().endsWith(FILE_TYPE_TRACKBOOK_EXTENSION) && !files[0].equals(mTempFile)){ // return latest track return files[0]; } @@ -282,9 +274,6 @@ public class StorageHelper implements TrackbookKeys { /* Gets the last track from directory */ private void deleteOldTracks(boolean includeTempFile) { - // get "tracks" folder - mFolder = mActivity.getExternalFilesDir(DIRECTORY_NAME); - if (mFolder != null && mFolder.isDirectory()) { LogHelper.v(LOG_TAG, "Deleting older recordings."); @@ -298,7 +287,7 @@ public class StorageHelper implements TrackbookKeys { // keep the latest ten (mMaxTrackFiles) track files int index = MAXIMUM_TRACK_FILES; // iterate through array - while (index < numberOfFiles && files[index].getName().endsWith(FILE_TYPE_EXTENSION) && !files[index].equals(mTempFile)) { + while (index < numberOfFiles && files[index].getName().endsWith(FILE_TYPE_TRACKBOOK_EXTENSION) && !files[index].equals(mTempFile)) { files[index].delete(); index++; } @@ -321,8 +310,8 @@ public class StorageHelper implements TrackbookKeys { public int compare(File file1, File file2) { // discard temp file and files not ending with ".trackbook" - boolean file1IsTrack = file1.getName().endsWith(FILE_TYPE_EXTENSION) && !file1.equals(mTempFile); - boolean file2IsTrack = file2.getName().endsWith(FILE_TYPE_EXTENSION) && !file2.equals(mTempFile); + boolean file1IsTrack = file1.getName().endsWith(FILE_TYPE_TRACKBOOK_EXTENSION) && !file1.equals(mTempFile); + boolean file2IsTrack = file2.getName().endsWith(FILE_TYPE_TRACKBOOK_EXTENSION) && !file2.equals(mTempFile); // note: "greater" means higher index in array if (!file1IsTrack && file2IsTrack) { @@ -353,7 +342,7 @@ public class StorageHelper implements TrackbookKeys { /* Return a write-able sub-directory from external storage */ private File getTracksDirectory() { - File[] storage = mActivity.getExternalFilesDirs(DIRECTORY_NAME); + File[] storage = mActivity.getExternalFilesDirs(TRACKS_DIRECTORY_NAME); for (File file : storage) { if (file != null) { String state = EnvironmentCompat.getStorageState(file); diff --git a/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java b/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java index 1498335..c3f1cb5 100644 --- a/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java +++ b/app/src/main/java/org/y20k/trackbook/helpers/TrackbookKeys.java @@ -1,7 +1,7 @@ /** * TrackbookKeys.java * Implements the keys used throughout the app - * This class hosts all keys used to control Trackbook's state + * This interface hosts all keys used to control Trackbook's state * * This file is part of * TRACKBOOK - Movement Recorder for Android @@ -41,6 +41,11 @@ public interface TrackbookKeys { String EXTRA_SAVE_FINISHED = "SAVE_FINISHED"; /* ARGS */ + String ARG_DIALOG_TITLE = "ArgDialogTitle"; + String ARG_DIALOG_MESSAGE = "ArgDialogMessage"; + String ARG_DIALOG_BUTTON_POSITIVE = "ArgDialogButtonPositive"; + String ARG_DIALOG_BUTTON_NEGATIVE = "ArgDialogButtonNegative"; + // String ARG_PERMISSIONS_GRANTED = "ArgPermissionsGranted"; // String ARG_TRACKING_STATE = "ArgTrackingState"; // String ARG_TRACK = "ArgTrack"; @@ -94,8 +99,17 @@ public interface TrackbookKeys { int FILE_MOST_CURRENT_TRACK = 1; int NEW_DROPDOWN_ITEM = -1; - String DIRECTORY_NAME = "tracks"; - String FILE_TYPE_EXTENSION = ".trackbook"; + int RESULT_SAVE_DIALOG = 1; + int RESULT_CLEAR_DIALOG = 2; + int RESULT_DELETE_DIALOG = 3; + int RESULT_EXPORT_DIALOG = 4; + + int STORAGE_TRACKS = 1; + int STORAGE_DOWNLOADS = 2; + + String TRACKS_DIRECTORY_NAME = "tracks"; + String FILE_TYPE_GPX_EXTENSION = ".gpx"; + String FILE_TYPE_TRACKBOOK_EXTENSION = ".trackbook"; String FILE_NAME_TEMP = "temp"; diff --git a/app/src/main/res/drawable/ic_export_white_24dp.xml b/app/src/main/res/drawable/ic_export_white_24dp.xml new file mode 100644 index 0000000..f82a153 --- /dev/null +++ b/app/src/main/res/drawable/ic_export_white_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_main_track.xml b/app/src/main/res/layout/fragment_main_track.xml index 99ad699..0ff0170 100644 --- a/app/src/main/res/layout/fragment_main_track.xml +++ b/app/src/main/res/layout/fragment_main_track.xml @@ -38,12 +38,24 @@ android:gravity="start|center" android:theme="@style/TrackbookAppTheme.PopupOverlay" android:contentDescription="@string/descr_track_selector" /> + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index faaba80..dc273d5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -40,6 +40,12 @@ Aufzeichnung löschen? Diese Aufzeichnung löschen: Löschen + Export Recording as GPX? + Export this recording as GPX track. + Export + Export and Overwrite? + File already exists. Export and overwrite this recording as GPX track. + Export and Overwrite Berechtigungen erteilt. @@ -52,6 +58,8 @@ Aufzeichnung wird gespeichert. über eine Stunde Aufzeichnung zurückgesetzt. + GPX export successful: + GPX export failed: Quelle @@ -81,8 +89,8 @@ Alles klar! - Your recorded tracks - … will show up here. + Bewegungsaufzeichnungen + … werden hier erscheinen. Über Trackbook @@ -126,6 +134,7 @@ Wert: Start der Aufzeichnung Wert: Ende der Aufzeichnung Auswahl-Menü für weitere Aufzeichnungen + Button Track Exportierem Button Track Löschen \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 7fa9a38..185f6fe 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -32,6 +32,12 @@ 記録を削除しますか? 次の記録を削除: Delete + Export Recording as GPX? + Export this recording as GPX track. + Export + Export and Overwrite? + File already exists. Export and overwrite this recording as GPX track. + Export and Overwrite アクセス許可を付与しました。 Trackbook を起動できません。 @@ -43,6 +49,8 @@ 現在のトレースを保存しています。 1 時間以上 現在のトレース データを削除しました。 + GPX export successful: + GPX export failed: ソース 時間 @@ -111,6 +119,7 @@ 値: 記録の開始 値: 記録の終了 ドロップダウン メニューでさらにトレース + Track export button トレース削除ボタン diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index efa22ab..8c5f621 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -30,6 +30,12 @@ Delete Recording? Delete the following recording: Delete + Export Recording as GPX? + Export this recording as GPX track. + Export + Export and Overwrite? + File already exists. Export and overwrite this recording as GPX track. + Export and Overwrite Rechten verleend. Trackbook kan niet worden gestart. @@ -41,6 +47,8 @@ Bezig met opslaan van huidige baan. langer dan één uur Huidige baangegevens verwijderd. + GPX export successful: + GPX export failed: Bron Tijd @@ -109,6 +117,7 @@ Waarde: beginnen met bijhouden Waarde: stoppen met bijhouden Uitrolmenu voor verdere banen + Track export button Baan - verwijderknop diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index afae65e..e62b22f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -38,8 +38,14 @@ Clear Recording? Clear Delete Recording? - Delete the following recording: + Delete this recording: Delete + Export Recording as GPX? + Export this recording as GPX track. + Export + Export and Overwrite? + File already exists. Export and overwrite this recording as GPX track. + Export and Overwrite Permissions granted. @@ -52,6 +58,8 @@ Saving current track. over one hour Current track data removed. + GPX export successful: + GPX export failed: Source @@ -126,6 +134,7 @@ Value: start of recording Value: end of recording Dropdown menu for further tracks + Track export button Track delete button diff --git a/build.gradle b/build.gradle index 74b2ab0..d0aa704 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.2.3' + classpath 'com.android.tools.build:gradle:2.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 35fa127..73d5ef9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Aug 22 10:55:02 CEST 2016 +#Fri Mar 03 15:35:49 CET 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip