got gpx export working - yay (#14)

master
y20k 2017-05-17 15:43:53 +02:00
parent 8c06c8c3ae
commit 9a14241915
19 changed files with 611 additions and 243 deletions

View File

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

View File

@ -25,6 +25,7 @@
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/TrackbookAppTheme.NoActionBar"
android:resizeableActivity="true"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -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">

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
"<gpx version=\"1.1\" creator=\"Transistor App (Android)\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n";
// add track
gpxString = gpxString + addTrack(track);
// add closing tag
gpxString = gpxString + "</gpx>\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<trk>\n");
// add name to track
gpxTrack.append("\t\t<name>");
gpxTrack.append("test");
gpxTrack.append("</name>\n");
// add opening track segment tag
gpxTrack.append("\t\t<trkseg>\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<trkpt lat=\"");
gpxTrack.append(location.getLatitude());
gpxTrack.append("\" lon=\"");
gpxTrack.append(location.getLongitude());
gpxTrack.append("\">\n");
// add time
gpxTrack.append("\t\t\t\t<time>");
gpxTrack.append(dateFormat.format(new Date(location.getTime())));
gpxTrack.append("</time>\n");
// add altitude
gpxTrack.append("\t\t\t\t<ele>");
gpxTrack.append(location.getAltitude());
gpxTrack.append("</ele>\n");
// add closing tag
gpxTrack.append("\t\t\t</trkpt>\n");
}
// add closing track segment tag
gpxTrack.append("\t\t<trkseg>\n");
// add closing track tag
gpxTrack.append("\t</trk>\n");
return gpxTrack.toString();
}
}

View File

@ -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 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n" +
"<gpx version=\"1.1\" creator=\"Transistor App (Android)\"\n" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\n";
// add track
gpxString = gpxString + addTrack();
// add closing tag
gpxString = gpxString + "</gpx>\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<rte>\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<rtept lat=\"");
gpxTrack.append(location.getLatitude());
gpxTrack.append("\" lon=\"");
gpxTrack.append(location.getLongitude());
gpxTrack.append("\">\n");
// add time
gpxTrack.append("\t\t\t<time>");
gpxTrack.append(dateFormat.format(new Date(location.getTime())));
gpxTrack.append("</time>\n");
// add altitude
gpxTrack.append("\t\t\t<ele>");
gpxTrack.append(location.getAltitude());
gpxTrack.append("</ele>\n");
// add closing tag
gpxTrack.append("\t\t</rtept>\n");
}
// add closing route tag
gpxTrack.append("\t</rte>\n");
return gpxTrack.toString();
}
}

View File

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

View File

@ -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<String> getListOfTracks() {
List<String> listOfTracks = new ArrayList<String>();
// 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<String> getListOfTracks() {
// List<String> listOfTracks = new ArrayList<String>();
//
// // 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);

View File

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

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:pathData="M9,3L5,6.99h3L8,14h2L10,6.99h3L9,3zM16,17.01L16,10h-2v7.01h-3L15,21l4,-3.99h-3z"
android:fillColor="@color/trackbook_white" />
</vector>

View File

@ -38,12 +38,24 @@
android:gravity="start|center"
android:theme="@style/TrackbookAppTheme.PopupOverlay"
android:contentDescription="@string/descr_track_selector" />
<ImageButton
android:visibility="visible"
android:id="@+id/export_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:background="@drawable/ic_export_white_24dp"
android:contentDescription="@string/descr_export_button" />
<ImageButton
android:visibility="gone"
android:id="@+id/delete_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|center"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:background="@drawable/ic_delete_forever_white_24dp"
android:contentDescription="@string/descr_delete_button" />
</LinearLayout>

View File

@ -40,6 +40,12 @@
<string name="dialog_delete_title">Aufzeichnung löschen?</string>
<string name="dialog_delete_content">Diese Aufzeichnung löschen:</string>
<string name="dialog_delete_action_delete">Löschen</string>
<string name="dialog_export_title_export">Export Recording as GPX?</string>
<string name="dialog_export_content_export">Export this recording as GPX track.</string>
<string name="dialog_export_action_export">Export</string>
<string name="dialog_export_title_overwrite">Export and Overwrite?</string>
<string name="dialog_export_content_overwrite">File already exists. Export and overwrite this recording as GPX track.</string>
<string name="dialog_export_action_overwrite">Export and Overwrite</string>
<!-- toast messages -->
<string name="toast_message_permissions_granted">Berechtigungen erteilt.</string>
@ -52,6 +58,8 @@
<string name="toast_message_save_track">Aufzeichnung wird gespeichert.</string>
<string name="toast_message_last_location_age_one_hour">über eine Stunde</string>
<string name="toast_message_track_clear">Aufzeichnung zurückgesetzt.</string>
<string name="toast_message_export_success">GPX export successful:</string>
<string name="toast_message_export_fail">GPX export failed:</string>
<!-- map markers -->
<string name="marker_description_source">Quelle</string>
@ -81,8 +89,8 @@
<string name="layout_onboarding_button_okay">Alles klar!</string>
<!-- track tab onboarding -->
<string name="track_tab_onboarding_h1_part_1">Your recorded tracks</string>
<string name="track_tab_onboarding_h1_part_2">… will show up here.</string>
<string name="track_tab_onboarding_h1_part_1">Bewegungsaufzeichnungen</string>
<string name="track_tab_onboarding_h1_part_2">… werden hier erscheinen.</string>
<!-- infosheet about -->
<string name="infosheet_about_h1_about">Über Trackbook</string>
@ -126,6 +134,7 @@
<string name="descr_statistics_sheet_p_recording_start_value">Wert: Start der Aufzeichnung</string>
<string name="descr_statistics_sheet_p_recording_end_value">Wert: Ende der Aufzeichnung</string>
<string name="descr_track_selector">Auswahl-Menü für weitere Aufzeichnungen</string>
<string name="descr_export_button">Button Track Exportierem</string>
<string name="descr_delete_button">Button Track Löschen</string>
</resources>

View File

@ -32,6 +32,12 @@
<string name="dialog_delete_title">記録を削除しますか?</string>
<string name="dialog_delete_content">次の記録を削除:</string>
<string name="dialog_delete_action_delete">Delete</string>
<string name="dialog_export_title_export">Export Recording as GPX?</string>
<string name="dialog_export_content_export">Export this recording as GPX track.</string>
<string name="dialog_export_action_export">Export</string>
<string name="dialog_export_title_overwrite">Export and Overwrite?</string>
<string name="dialog_export_content_overwrite">File already exists. Export and overwrite this recording as GPX track.</string>
<string name="dialog_export_action_overwrite">Export and Overwrite</string>
<string name="toast_message_permissions_granted">アクセス許可を付与しました。</string>
<string name="toast_message_unable_to_start_app">Trackbook を起動できません。</string>
@ -43,6 +49,8 @@
<string name="toast_message_save_track">現在のトレースを保存しています。</string>
<string name="toast_message_last_location_age_one_hour">1 時間以上</string>
<string name="toast_message_track_clear">現在のトレース データを削除しました。</string>
<string name="toast_message_export_success">GPX export successful:</string>
<string name="toast_message_export_fail">GPX export failed:</string>
<string name="marker_description_source">ソース</string>
<string name="marker_description_time">時間</string>
@ -111,6 +119,7 @@
<string name="descr_statistics_sheet_p_recording_start_value">値: 記録の開始</string>
<string name="descr_statistics_sheet_p_recording_end_value">値: 記録の終了</string>
<string name="descr_track_selector">ドロップダウン メニューでさらにトレース</string>
<string name="descr_export_button">Track export button</string>
<string name="descr_delete_button">トレース削除ボタン</string>
</resources>

View File

@ -30,6 +30,12 @@
<string name="dialog_delete_title">Delete Recording?</string>
<string name="dialog_delete_content">Delete the following recording:</string>
<string name="dialog_delete_action_delete">Delete</string>
<string name="dialog_export_title_export">Export Recording as GPX?</string>
<string name="dialog_export_content_export">Export this recording as GPX track.</string>
<string name="dialog_export_action_export">Export</string>
<string name="dialog_export_title_overwrite">Export and Overwrite?</string>
<string name="dialog_export_content_overwrite">File already exists. Export and overwrite this recording as GPX track.</string>
<string name="dialog_export_action_overwrite">Export and Overwrite</string>
<string name="toast_message_permissions_granted">Rechten verleend.</string>
<string name="toast_message_unable_to_start_app">Trackbook kan niet worden gestart.</string>
@ -41,6 +47,8 @@
<string name="toast_message_save_track">Bezig met opslaan van huidige baan.</string>
<string name="toast_message_last_location_age_one_hour">langer dan één uur</string>
<string name="toast_message_track_clear">Huidige baangegevens verwijderd.</string>
<string name="toast_message_export_success">GPX export successful:</string>
<string name="toast_message_export_fail">GPX export failed:</string>
<string name="marker_description_source">Bron</string>
<string name="marker_description_time">Tijd</string>
@ -109,6 +117,7 @@
<string name="descr_statistics_sheet_p_recording_start_value">Waarde: beginnen met bijhouden</string>
<string name="descr_statistics_sheet_p_recording_end_value">Waarde: stoppen met bijhouden</string>
<string name="descr_track_selector">Uitrolmenu voor verdere banen</string>
<string name="descr_export_button">Track export button</string>
<string name="descr_delete_button">Baan - verwijderknop</string>
</resources>

View File

@ -38,8 +38,14 @@
<string name="dialog_clear_content">Clear Recording?</string>
<string name="dialog_clear_action_clear">Clear</string>
<string name="dialog_delete_title">Delete Recording?</string>
<string name="dialog_delete_content">Delete the following recording:</string>
<string name="dialog_delete_content">Delete this recording:</string>
<string name="dialog_delete_action_delete">Delete</string>
<string name="dialog_export_title_export">Export Recording as GPX?</string>
<string name="dialog_export_content_export">Export this recording as GPX track.</string>
<string name="dialog_export_action_export">Export</string>
<string name="dialog_export_title_overwrite">Export and Overwrite?</string>
<string name="dialog_export_content_overwrite">File already exists. Export and overwrite this recording as GPX track.</string>
<string name="dialog_export_action_overwrite">Export and Overwrite</string>
<!-- toast messages -->
<string name="toast_message_permissions_granted">Permissions granted.</string>
@ -52,6 +58,8 @@
<string name="toast_message_save_track">Saving current track.</string>
<string name="toast_message_last_location_age_one_hour">over one hour</string>
<string name="toast_message_track_clear">Current track data removed.</string>
<string name="toast_message_export_success">GPX export successful:</string>
<string name="toast_message_export_fail">GPX export failed:</string>
<!-- map markers -->
<string name="marker_description_source">Source</string>
@ -126,6 +134,7 @@
<string name="descr_statistics_sheet_p_recording_start_value">Value: start of recording</string>
<string name="descr_statistics_sheet_p_recording_end_value">Value: end of recording</string>
<string name="descr_track_selector">Dropdown menu for further tracks</string>
<string name="descr_export_button">Track export button</string>
<string name="descr_delete_button">Track delete button</string>
</resources>

View File

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

View File

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