parent
36e91bfa6c
commit
702a2ecd05
10 changed files with 271 additions and 96 deletions
|
@ -26,6 +26,6 @@ dependencies {
|
||||||
compile 'com.android.support:appcompat-v7:25.1.0'
|
compile 'com.android.support:appcompat-v7:25.1.0'
|
||||||
compile 'com.android.support:design:25.1.0'
|
compile 'com.android.support:design:25.1.0'
|
||||||
compile 'com.android.support:cardview-v7:25.1.0'
|
compile 'com.android.support:cardview-v7:25.1.0'
|
||||||
compile 'org.osmdroid:osmdroid-android:5.6.1'
|
compile 'org.osmdroid:osmdroid-android:5.6.2'
|
||||||
compile 'com.google.code.gson:gson:2.8.0'
|
compile 'com.google.code.gson:gson:2.8.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,7 +70,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private NonSwipeableViewPager mViewPager;
|
private NonSwipeableViewPager mViewPager;
|
||||||
private boolean mTrackerServiceRunning;
|
private boolean mTrackerServiceRunning;
|
||||||
private boolean mCurrentTrackVisible;
|
// private boolean mCurrentTrackVisible;
|
||||||
private boolean mPermissionsGranted;
|
private boolean mPermissionsGranted;
|
||||||
private boolean mFloatingActionButtonSubMenuVisible;
|
private boolean mFloatingActionButtonSubMenuVisible;
|
||||||
private List<String> mMissingPermissions;
|
private List<String> mMissingPermissions;
|
||||||
|
@ -89,7 +89,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
// load saved state of app
|
// load saved state of app
|
||||||
loadTrackState(this);
|
loadFloatingActionButtonState(this);
|
||||||
|
|
||||||
// check permissions on Android 6 and higher
|
// check permissions on Android 6 and higher
|
||||||
mPermissionsGranted = false;
|
mPermissionsGranted = false;
|
||||||
|
@ -114,31 +114,33 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
// add listeners to button and submenu
|
// // add listeners to button and submenu
|
||||||
if (mFloatingActionButton != null) {
|
// if (mFloatingActionButton != null) {
|
||||||
mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
|
// mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
|
||||||
@Override
|
// @Override
|
||||||
public void onClick(View view) {
|
// public void onClick(View view) {
|
||||||
handleFloatingActionButtonClick(view);
|
// handleFloatingActionButtonClick(view);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// } else {
|
||||||
if (mFloatingActionButtonSubMenu1 != null) {
|
// LogHelper.e(LOG_TAG, "mFloatingActionButton is null!");
|
||||||
mFloatingActionButtonSubMenu1.setOnClickListener(new View.OnClickListener() {
|
// }
|
||||||
@Override
|
// if (mFloatingActionButtonSubMenu1 != null) {
|
||||||
public void onClick(View view) {
|
// mFloatingActionButtonSubMenu1.setOnClickListener(new View.OnClickListener() {
|
||||||
handleButtonSaveAndClearClick();
|
// @Override
|
||||||
}
|
// public void onClick(View view) {
|
||||||
});
|
// handleButtonSaveAndClearClick();
|
||||||
}
|
// }
|
||||||
if (mFloatingActionButtonSubMenu2 != null) {
|
// });
|
||||||
mFloatingActionButtonSubMenu2.setOnClickListener(new View.OnClickListener() {
|
// }
|
||||||
@Override
|
// if (mFloatingActionButtonSubMenu2 != null) {
|
||||||
public void onClick(View view) {
|
// mFloatingActionButtonSubMenu2.setOnClickListener(new View.OnClickListener() {
|
||||||
handleButtonClearClick();
|
// @Override
|
||||||
}
|
// public void onClick(View view) {
|
||||||
});
|
// handleButtonClearClick();
|
||||||
}
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
// register broadcast receiver for stopped tracking
|
// register broadcast receiver for stopped tracking
|
||||||
mTrackingStoppedReceiver = createTrackingStoppedReceiver();
|
mTrackingStoppedReceiver = createTrackingStoppedReceiver();
|
||||||
|
@ -185,8 +187,8 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
LogHelper.v(LOG_TAG, "onResume called.");
|
LogHelper.v(LOG_TAG, "onResume called.");
|
||||||
|
|
||||||
// load state of track visibility
|
// load state of Floating Action Button
|
||||||
loadTrackState(this);
|
loadFloatingActionButtonState(this);
|
||||||
|
|
||||||
// handle incoming intent (from notification)
|
// handle incoming intent (from notification)
|
||||||
handleIncomingIntent();
|
handleIncomingIntent();
|
||||||
|
@ -202,6 +204,9 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
|
||||||
|
// save state of Floating Action Button
|
||||||
|
saveFloatingActionButtonState(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -253,7 +258,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
LogHelper.v(LOG_TAG, "onSaveInstanceState called.");
|
LogHelper.v(LOG_TAG, "onSaveInstanceState called.");
|
||||||
outState.putBoolean(INSTANCE_TRACKING_STATE, mTrackerServiceRunning);
|
outState.putBoolean(INSTANCE_TRACKING_STATE, mTrackerServiceRunning);
|
||||||
outState.putInt(INSTANCE_SELECTED_TAB, mSelectedTab);
|
outState.putInt(INSTANCE_SELECTED_TAB, mSelectedTab);
|
||||||
outState.putInt(INSTANCE_FAB_STATE, mFloatingActionButtonState);
|
// outState.putInt(INSTANCE_FAB_STATE, mFloatingActionButtonState);
|
||||||
outState.putBoolean(INSTANCE_FAB_SUB_MENU_VISIBLE, mFloatingActionButtonSubMenuVisible);
|
outState.putBoolean(INSTANCE_FAB_SUB_MENU_VISIBLE, mFloatingActionButtonSubMenuVisible);
|
||||||
super.onSaveInstanceState(outState);
|
super.onSaveInstanceState(outState);
|
||||||
}
|
}
|
||||||
|
@ -266,17 +271,26 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
LogHelper.v(LOG_TAG, "onRestoreInstanceState called.");
|
LogHelper.v(LOG_TAG, "onRestoreInstanceState called.");
|
||||||
mTrackerServiceRunning = savedInstanceState.getBoolean(INSTANCE_TRACKING_STATE, false);
|
mTrackerServiceRunning = savedInstanceState.getBoolean(INSTANCE_TRACKING_STATE, false);
|
||||||
mSelectedTab = savedInstanceState.getInt(INSTANCE_SELECTED_TAB, 0);
|
mSelectedTab = savedInstanceState.getInt(INSTANCE_SELECTED_TAB, 0);
|
||||||
mFloatingActionButtonState = savedInstanceState.getInt(INSTANCE_FAB_STATE, FAB_STATE_DEFAULT);
|
// mFloatingActionButtonState = savedInstanceState.getInt(INSTANCE_FAB_STATE, FAB_STATE_DEFAULT);
|
||||||
mFloatingActionButtonSubMenuVisible = savedInstanceState.getBoolean(INSTANCE_FAB_SUB_MENU_VISIBLE, false);
|
mFloatingActionButtonSubMenuVisible = savedInstanceState.getBoolean(INSTANCE_FAB_SUB_MENU_VISIBLE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Loads state of track visibility from preferences */
|
/* Loads state of Floating Action Button from preferences */
|
||||||
private void loadTrackState(Context context) {
|
private void loadFloatingActionButtonState(Context context) {
|
||||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
mCurrentTrackVisible = settings.getBoolean(INSTANCE_TRACK_VISIBLE, false);
|
mFloatingActionButtonState = settings.getInt(PREFS_FAB_STATE, FAB_STATE_DEFAULT);
|
||||||
|
// mCurrentTrackVisible = settings.getBoolean(PREFS_TRACK_VISIBLE, false); // TODO remove mCurrentTrackVisible completely
|
||||||
// mCurrentTrackVisible is handled / saved by fragment
|
// mCurrentTrackVisible is handled / saved by fragment
|
||||||
LogHelper.v(LOG_TAG, "Loading state. Track visibility: " + mCurrentTrackVisible);
|
// LogHelper.v(LOG_TAG, "Loading state. Track visibility: " + mCurrentTrackVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Saves state of Floating Action Button */
|
||||||
|
private void saveFloatingActionButtonState(Context context) {
|
||||||
|
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
SharedPreferences.Editor editor = settings.edit();
|
||||||
|
editor.putInt(PREFS_FAB_STATE, mFloatingActionButtonState);
|
||||||
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -341,6 +355,33 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
showFloatingActionButtonMenu(false);
|
showFloatingActionButtonMenu(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add listeners to button and submenu
|
||||||
|
if (mFloatingActionButton != null) {
|
||||||
|
mFloatingActionButton.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
handleFloatingActionButtonClick(view);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (mFloatingActionButtonSubMenu1 != null) {
|
||||||
|
mFloatingActionButtonSubMenu1.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
handleButtonSaveAndClearClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (mFloatingActionButtonSubMenu2 != null) {
|
||||||
|
mFloatingActionButtonSubMenu2.setOnClickListener(new View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
handleButtonClearClick();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// point to the on main onboarding layout
|
// point to the on main onboarding layout
|
||||||
setContentView(R.layout.activity_main_onboarding);
|
setContentView(R.layout.activity_main_onboarding);
|
||||||
|
@ -374,7 +415,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
|
|
||||||
// change state
|
// change state
|
||||||
mTrackerServiceRunning = true;
|
mTrackerServiceRunning = true;
|
||||||
mCurrentTrackVisible = true;
|
// mCurrentTrackVisible = true;
|
||||||
mFloatingActionButtonState = FAB_STATE_RECORDING;
|
mFloatingActionButtonState = FAB_STATE_RECORDING;
|
||||||
setFloatingActionButtonState();
|
setFloatingActionButtonState();
|
||||||
|
|
||||||
|
@ -433,7 +474,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
|
|
||||||
// clear map and save track
|
// clear map and save track
|
||||||
mMainActivityMapFragment.clearMap(true);
|
mMainActivityMapFragment.clearMap(true);
|
||||||
mCurrentTrackVisible = false;
|
// mCurrentTrackVisible = false;
|
||||||
|
|
||||||
// display and update track tab
|
// display and update track tab
|
||||||
mSelectedTab = FRAGMENT_ID_TRACK;
|
mSelectedTab = FRAGMENT_ID_TRACK;
|
||||||
|
@ -460,7 +501,7 @@ public class MainActivity extends AppCompatActivity implements TrackbookKeys {
|
||||||
|
|
||||||
// clear map, do not save track
|
// clear map, do not save track
|
||||||
mMainActivityMapFragment.clearMap(false);
|
mMainActivityMapFragment.clearMap(false);
|
||||||
mCurrentTrackVisible = false;
|
// mCurrentTrackVisible = false;
|
||||||
|
|
||||||
// dismiss notification
|
// dismiss notification
|
||||||
NotificationHelper.stop();
|
NotificationHelper.stop();
|
||||||
|
|
|
@ -21,7 +21,6 @@ import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
|
@ -30,7 +29,6 @@ import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
import android.support.v4.app.Fragment;
|
import android.support.v4.app.Fragment;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
|
@ -96,7 +94,7 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
LogHelper.v(LOG_TAG, "!!! MainActivityMapFragment onCreate called.");
|
LogHelper.v(LOG_TAG, "MainActivityMapFragment onCreate called.");
|
||||||
|
|
||||||
// get activity
|
// get activity
|
||||||
mActivity = getActivity();
|
mActivity = getActivity();
|
||||||
|
@ -193,12 +191,18 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
// restore track
|
// restore track
|
||||||
if (savedInstanceState != null) {
|
StorageHelper storageHelper = new StorageHelper(mActivity, FILETYPE_TEMP);
|
||||||
|
if (storageHelper.tempFileExists()) {
|
||||||
|
// load track from temp file if it exists
|
||||||
|
LoadTempTrackAsyncHelper loadTempTrackAsyncHelper = new LoadTempTrackAsyncHelper();
|
||||||
|
loadTempTrackAsyncHelper.execute();
|
||||||
|
} else if (savedInstanceState != null) {
|
||||||
|
// load track from saved instance
|
||||||
mTrack = savedInstanceState.getParcelable(INSTANCE_TRACK_MAIN_MAP);
|
mTrack = savedInstanceState.getParcelable(INSTANCE_TRACK_MAIN_MAP);
|
||||||
}
|
|
||||||
if (mTrack != null) {
|
if (mTrack != null) {
|
||||||
drawTrackOverlay(mTrack);
|
drawTrackOverlay(mTrack);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// mark user's location on map
|
// mark user's location on map
|
||||||
if (mCurrentBestLocation != null && !mTrackerServiceRunning) {
|
if (mCurrentBestLocation != null && !mTrackerServiceRunning) {
|
||||||
|
@ -256,8 +260,8 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
// disable content observer for changes in System Settings
|
// disable content observer for changes in System Settings
|
||||||
mActivity.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
|
mActivity.getContentResolver().unregisterContentObserver(mSettingsContentObserver);
|
||||||
|
|
||||||
// save state of track visibility
|
// // save state of track visibility
|
||||||
saveTrackVisibilityState(mActivity);
|
// saveTrackVisibilityState(mActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -407,8 +411,8 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
mTrack = null;
|
mTrack = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// save track state
|
// // save track state
|
||||||
saveTrackVisibilityState(mActivity);
|
// saveTrackVisibilityState(mActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -477,7 +481,7 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
return new LocationListener() {
|
return new LocationListener() {
|
||||||
public void onLocationChanged(Location location) {
|
public void onLocationChanged(Location location) {
|
||||||
// check if the new location is better
|
// check if the new location is better
|
||||||
if (LocationHelper.isBetterLocation(location, mCurrentBestLocation)) {
|
if (mCurrentBestLocation == null || LocationHelper.isBetterLocation(location, mCurrentBestLocation)) {
|
||||||
// save location
|
// save location
|
||||||
mCurrentBestLocation = location;
|
mCurrentBestLocation = location;
|
||||||
// mark user's new location on map and remove last marker
|
// mark user's new location on map and remove last marker
|
||||||
|
@ -574,14 +578,14 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Saves state of track visibility to SharedPreferences */
|
// /* Saves state of track visibility to SharedPreferences */
|
||||||
private void saveTrackVisibilityState(Context context) {
|
// private void saveTrackVisibilityState(Context context) {
|
||||||
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
// SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
SharedPreferences.Editor editor = settings.edit();
|
// SharedPreferences.Editor editor = settings.edit();
|
||||||
editor.putBoolean(INSTANCE_TRACK_VISIBLE, (mTrackOverlay != null));
|
// editor.putBoolean(PREFS_TRACK_VISIBLE, (mTrackOverlay != null));
|
||||||
editor.apply();
|
// editor.apply();
|
||||||
LogHelper.v(LOG_TAG, "Saving state: track visibility = " + (mTrackOverlay != null));
|
// LogHelper.v(LOG_TAG, "Saving state: track visibility = " + (mTrackOverlay != null));
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
// /* Saves state of map */
|
// /* Saves state of map */
|
||||||
|
@ -647,7 +651,7 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
LogHelper.v(LOG_TAG, "Saving track object in background.");
|
LogHelper.v(LOG_TAG, "Saving track object in background.");
|
||||||
// save track object
|
// save track object
|
||||||
StorageHelper storageHelper = new StorageHelper(mActivity);
|
StorageHelper storageHelper = new StorageHelper(mActivity, FILETYPE_TRACK);
|
||||||
storageHelper.saveTrack(mTrack);
|
storageHelper.saveTrack(mTrack);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -668,4 +672,35 @@ public class MainActivityMapFragment extends Fragment implements TrackbookKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class: Loads track from external storage using AsyncTask
|
||||||
|
*/
|
||||||
|
private class LoadTempTrackAsyncHelper extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
StorageHelper storageHelper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
LogHelper.v(LOG_TAG, "Loading temporary track object in background.");
|
||||||
|
// load track object
|
||||||
|
storageHelper = new StorageHelper(mActivity, FILETYPE_TEMP);
|
||||||
|
mTrack = storageHelper.loadTrack();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
super.onPostExecute(aVoid);
|
||||||
|
LogHelper.v(LOG_TAG, "Loading finished.");
|
||||||
|
|
||||||
|
// draw track on map
|
||||||
|
if (mTrack != null) {
|
||||||
|
drawTrackOverlay(mTrack);
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete temp file
|
||||||
|
storageHelper.deleteTempFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -303,9 +303,9 @@ public class MainActivityTrackFragment extends Fragment implements TrackbookKeys
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... voids) {
|
protected Void doInBackground(Void... voids) {
|
||||||
LogHelper.v(LOG_TAG, "Loading track object in background.");
|
LogHelper.v(LOG_TAG, "Loading track object in background.");
|
||||||
// save track object
|
// load track object
|
||||||
StorageHelper storageHelper = new StorageHelper(mActivity);
|
StorageHelper storageHelper = new StorageHelper(mActivity, FILETYPE_TRACK);
|
||||||
mTrack = storageHelper.loadTrack(storageHelper.getMostCurrentTrack());
|
mTrack = storageHelper.loadTrack();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.y20k.trackbook;
|
||||||
import android.app.Service;
|
import android.app.Service;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.database.ContentObserver;
|
import android.database.ContentObserver;
|
||||||
import android.hardware.Sensor;
|
import android.hardware.Sensor;
|
||||||
import android.hardware.SensorEvent;
|
import android.hardware.SensorEvent;
|
||||||
|
@ -27,10 +28,12 @@ import android.hardware.SensorManager;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.CountDownTimer;
|
import android.os.CountDownTimer;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.content.LocalBroadcastManager;
|
import android.support.v4.content.LocalBroadcastManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
@ -40,9 +43,9 @@ import org.y20k.trackbook.core.WayPoint;
|
||||||
import org.y20k.trackbook.helpers.LocationHelper;
|
import org.y20k.trackbook.helpers.LocationHelper;
|
||||||
import org.y20k.trackbook.helpers.LogHelper;
|
import org.y20k.trackbook.helpers.LogHelper;
|
||||||
import org.y20k.trackbook.helpers.NotificationHelper;
|
import org.y20k.trackbook.helpers.NotificationHelper;
|
||||||
|
import org.y20k.trackbook.helpers.StorageHelper;
|
||||||
import org.y20k.trackbook.helpers.TrackbookKeys;
|
import org.y20k.trackbook.helpers.TrackbookKeys;
|
||||||
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,6 +108,12 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
|
||||||
// ACTION STOP
|
// ACTION STOP
|
||||||
else if (intent.getAction().equals(ACTION_STOP) || !mLocationSystemSetting) {
|
else if (intent.getAction().equals(ACTION_STOP) || !mLocationSystemSetting) {
|
||||||
stopTracking();
|
stopTracking();
|
||||||
|
|
||||||
|
// save changed state of Floating Action Button
|
||||||
|
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this.getApplicationContext());
|
||||||
|
SharedPreferences.Editor editor = settings.edit();
|
||||||
|
editor.putInt(PREFS_FAB_STATE, FAB_STATE_SAVE);
|
||||||
|
editor.apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
// START_STICKY is used for services that are explicitly started and stopped as needed
|
// START_STICKY is used for services that are explicitly started and stopped as needed
|
||||||
|
@ -218,11 +227,15 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
|
||||||
LogHelper.v(LOG_TAG, "Service received command: STOP");
|
LogHelper.v(LOG_TAG, "Service received command: STOP");
|
||||||
|
|
||||||
// store current date and time
|
// store current date and time
|
||||||
mTrack.setRecordingEnd(GregorianCalendar.getInstance().getTime());
|
mTrack.setRecordingEnd();
|
||||||
|
|
||||||
// stop timer
|
// stop timer
|
||||||
mTimer.cancel();
|
mTimer.cancel();
|
||||||
|
|
||||||
|
// save a temp file in case the activity has been killed
|
||||||
|
SaveTempTrackAsyncHelper saveTempTrackAsyncHelper = new SaveTempTrackAsyncHelper();
|
||||||
|
saveTempTrackAsyncHelper.execute();
|
||||||
|
|
||||||
// change notification
|
// change notification
|
||||||
NotificationHelper.update(mTrack, false);
|
NotificationHelper.update(mTrack, false);
|
||||||
|
|
||||||
|
@ -369,7 +382,31 @@ public class TrackerService extends Service implements TrackbookKeys, SensorEven
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* End of inner class
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class: Saves track to external storage using AsyncTask
|
||||||
|
*/
|
||||||
|
private class SaveTempTrackAsyncHelper extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... voids) {
|
||||||
|
LogHelper.v(LOG_TAG, "Saving temporary track object in background.");
|
||||||
|
// save track object
|
||||||
|
StorageHelper storageHelper = new StorageHelper(TrackerService.this, FILETYPE_TEMP);
|
||||||
|
storageHelper.saveTrack(mTrack);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void aVoid) {
|
||||||
|
super.onPostExecute(aVoid);
|
||||||
|
LogHelper.v(LOG_TAG, "Saving finished.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,8 +53,8 @@ public class Track implements TrackbookKeys, Parcelable {
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
public Track() {
|
public Track() {
|
||||||
mWayPoints = new ArrayList<WayPoint>();
|
mWayPoints = new ArrayList<WayPoint>();
|
||||||
mTrackLength = 0;
|
mTrackLength = 0f;
|
||||||
mStepCount = 0;
|
mStepCount = 0f;
|
||||||
mRecordingStart = GregorianCalendar.getInstance().getTime();
|
mRecordingStart = GregorianCalendar.getInstance().getTime();
|
||||||
mRecordingStop = mRecordingStart;
|
mRecordingStop = mRecordingStart;
|
||||||
}
|
}
|
||||||
|
@ -113,9 +113,9 @@ public class Track implements TrackbookKeys, Parcelable {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Setter for end time and date of recording */
|
/* Sets end time and date of recording */
|
||||||
public void setRecordingEnd (Date recordingEnd) {
|
public void setRecordingEnd () {
|
||||||
mRecordingStop = recordingEnd;
|
mRecordingStop = GregorianCalendar.getInstance().getTime();;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,6 @@ public class NotificationHelper implements TrackbookKeys {
|
||||||
String contentText = mService.getString(R.string.notification_content_distance) + ": " + track.getTrackDistance() + " | " +
|
String contentText = mService.getString(R.string.notification_content_distance) + ": " + track.getTrackDistance() + " | " +
|
||||||
mService.getString(R.string.notification_content_duration) + ": " + track.getTrackDuration();
|
mService.getString(R.string.notification_content_duration) + ": " + track.getTrackDuration();
|
||||||
|
|
||||||
|
|
||||||
// ACTION: NOTIFICATION TAP
|
// ACTION: NOTIFICATION TAP
|
||||||
Intent tapActionIntent = new Intent(mService, MainActivity.class);
|
Intent tapActionIntent = new Intent(mService, MainActivity.class);
|
||||||
tapActionIntent.setAction(ACTION_SHOW_MAP);
|
tapActionIntent.setAction(ACTION_SHOW_MAP);
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.y20k.trackbook.helpers;
|
package org.y20k.trackbook.helpers;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.content.Context;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.v4.os.EnvironmentCompat;
|
import android.support.v4.os.EnvironmentCompat;
|
||||||
|
@ -50,17 +50,19 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
private static final String LOG_TAG = StorageHelper.class.getSimpleName();
|
private static final String LOG_TAG = StorageHelper.class.getSimpleName();
|
||||||
|
|
||||||
/* Main class variables */
|
/* Main class variables */
|
||||||
private final int mMaxTrackFiles = 25;
|
private final int mFileType;
|
||||||
private final String mDirectoryName = "tracks";
|
private final String mDirectoryName = "tracks";
|
||||||
private final String mFileExtension = ".trackbook";
|
private final String mFileExtension = ".trackbook";
|
||||||
private final Activity mActivity;
|
private final Context mActivity;
|
||||||
private File mFolder;
|
private File mFolder;
|
||||||
|
private File mTempFile;
|
||||||
|
|
||||||
|
|
||||||
/* Constructor */
|
/* Constructor */
|
||||||
public StorageHelper(Activity activity) {
|
public StorageHelper(Context activity, int fileType) {
|
||||||
// store activity
|
// store activity
|
||||||
mActivity = activity;
|
mActivity = activity;
|
||||||
|
mFileType = fileType;
|
||||||
|
|
||||||
// get "tracks" folder
|
// get "tracks" folder
|
||||||
mFolder = mActivity.getExternalFilesDir(mDirectoryName);
|
mFolder = mActivity.getExternalFilesDir(mDirectoryName);
|
||||||
|
@ -71,6 +73,21 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
LogHelper.v(LOG_TAG, "Creating new folder: " + mFolder.toString());
|
LogHelper.v(LOG_TAG, "Creating new folder: " + mFolder.toString());
|
||||||
mFolder.mkdir();
|
mFolder.mkdir();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create temp file object
|
||||||
|
mTempFile = new File(mFolder.toString() + "/" + FILENAME_TEMP + mFileExtension);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Checks if a temp file exits */
|
||||||
|
public boolean tempFileExists() {
|
||||||
|
return mTempFile.exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Deletes temp file - if it exits */
|
||||||
|
public boolean deleteTempFile() {
|
||||||
|
return mTempFile.exists() && mTempFile.delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,8 +104,13 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
|
|
||||||
if (mFolder != null && mFolder.exists() && mFolder.isDirectory() && mFolder.canWrite() && recordingStart != null && track != null) {
|
if (mFolder != null && mFolder.exists() && mFolder.isDirectory() && mFolder.canWrite() && recordingStart != null && track != null) {
|
||||||
// construct filename from track recording date
|
// construct filename from track recording date
|
||||||
|
String fileName;
|
||||||
|
if (mFileType == FILETYPE_TEMP) {
|
||||||
|
fileName = FILENAME_TEMP + mFileExtension;
|
||||||
|
} else {
|
||||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
|
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US);
|
||||||
String fileName = dateFormat.format(recordingStart) + mFileExtension;
|
fileName = dateFormat.format(recordingStart) + mFileExtension;
|
||||||
|
}
|
||||||
File file = new File(mFolder.toString() + "/" + fileName);
|
File file = new File(mFolder.toString() + "/" + fileName);
|
||||||
|
|
||||||
// convert to JSON
|
// convert to JSON
|
||||||
|
@ -104,8 +126,10 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if write was successful delete old track files
|
// if write was successful delete old track files - only if not a temp file
|
||||||
|
if (mFileType != FILETYPE_TEMP) {
|
||||||
deleteOldTracks();
|
deleteOldTracks();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
@ -118,7 +142,15 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
|
|
||||||
|
|
||||||
/* Loads given file into memory */
|
/* Loads given file into memory */
|
||||||
public Track loadTrack (File file) {
|
public Track loadTrack () {
|
||||||
|
|
||||||
|
// get file reference
|
||||||
|
File file;
|
||||||
|
if (mFileType == FILETYPE_TEMP) {
|
||||||
|
file = getTempFile();
|
||||||
|
} else {
|
||||||
|
file = getMostCurrentTrack();
|
||||||
|
}
|
||||||
|
|
||||||
// check if given file was null
|
// check if given file was null
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
|
@ -127,7 +159,7 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
}
|
}
|
||||||
|
|
||||||
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
|
||||||
LogHelper.v(LOG_TAG, "Loading track to external storage: " + file.toString());
|
LogHelper.v(LOG_TAG, "Loading track from external storage: " + file.toString());
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
StringBuilder sb = new StringBuilder("");
|
StringBuilder sb = new StringBuilder("");
|
||||||
|
@ -151,7 +183,7 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
|
|
||||||
|
|
||||||
/* Gets most current track from directory */
|
/* Gets most current track from directory */
|
||||||
public File getMostCurrentTrack() {
|
private File getMostCurrentTrack() {
|
||||||
|
|
||||||
// get "tracks" folder
|
// get "tracks" folder
|
||||||
mFolder = mActivity.getExternalFilesDir(mDirectoryName);
|
mFolder = mActivity.getExternalFilesDir(mDirectoryName);
|
||||||
|
@ -160,16 +192,26 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
// get files and sort them
|
// get files and sort them
|
||||||
File[] files = mFolder.listFiles();
|
File[] files = mFolder.listFiles();
|
||||||
files = sortFiles(files);
|
files = sortFiles(files);
|
||||||
if (files.length > 0 && files[0].getName().endsWith(mFileExtension)){
|
if (files.length > 0 && files[0].getName().endsWith(mFileExtension) && !files[0].equals(mTempFile)){
|
||||||
// return latest track
|
// return latest track
|
||||||
return files[0];
|
return files[0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LogHelper.e(LOG_TAG, "Unable to get files from given folder.");
|
LogHelper.e(LOG_TAG, "Unable to get files from given folder. Folder is probably empty.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Gets temp file - if it exists */
|
||||||
|
private File getTempFile() {
|
||||||
|
if (mTempFile.exists()) {
|
||||||
|
return mTempFile;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Gets the last track from directory */
|
/* Gets the last track from directory */
|
||||||
private void deleteOldTracks() {
|
private void deleteOldTracks() {
|
||||||
|
|
||||||
|
@ -187,9 +229,9 @@ public class StorageHelper implements TrackbookKeys {
|
||||||
int numberOfFiles = files.length;
|
int numberOfFiles = files.length;
|
||||||
|
|
||||||
// keep the latest ten (mMaxTrackFiles) track files
|
// keep the latest ten (mMaxTrackFiles) track files
|
||||||
int index = mMaxTrackFiles;
|
int index = MAXIMUM_TRACK_FILES;
|
||||||
// iterate through array
|
// iterate through array
|
||||||
while (index < numberOfFiles && files[index].getName().endsWith(mFileExtension)) {
|
while (index < numberOfFiles && files[index].getName().endsWith(mFileExtension) && !files[index].equals(mTempFile)) {
|
||||||
files[index].delete();
|
files[index].delete();
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,17 +52,18 @@ public interface TrackbookKeys {
|
||||||
|
|
||||||
/* PREFS */
|
/* PREFS */
|
||||||
String PREFS_NAME = "org.y20k.trackbook.prefs";
|
String PREFS_NAME = "org.y20k.trackbook.prefs";
|
||||||
String PREFS_TILE_SOURCE = "tileSource";
|
String PREFS_TILE_SOURCE = "tileSourcePrefs";
|
||||||
String PREFS_LATITUDE = "latitude";
|
String PREFS_LATITUDE = "latitudePrefs";
|
||||||
String PREFS_LONGITUDE = "longitude";
|
String PREFS_LONGITUDE = "longitudePrefs";
|
||||||
String PREFS_ZOOM_LEVEL = "zoomLevel";
|
String PREFS_ZOOM_LEVEL = "zoomLevelPrefs";
|
||||||
String PREFS_SHOW_LOCATION = "showLocation";
|
String PREFS_SHOW_LOCATION = "showLocationPrefs";
|
||||||
String PREFS_SHOW_COMPASS = "showCompass";
|
String PREFS_SHOW_COMPASS = "showCompassPrefs";
|
||||||
|
String PREFS_FAB_STATE = "fabStatePrefs";
|
||||||
|
String PREFS_TRACK_VISIBLE = "trackVisiblePrefs";
|
||||||
|
|
||||||
/* INSTANCE STATE */
|
/* INSTANCE STATE */
|
||||||
String INSTANCE_FIRST_START = "firstStart";
|
String INSTANCE_FIRST_START = "firstStart";
|
||||||
String INSTANCE_TRACKING_STATE = "trackingState";
|
String INSTANCE_TRACKING_STATE = "trackingState";
|
||||||
String INSTANCE_TRACK_VISIBLE = "trackVisible";
|
|
||||||
String INSTANCE_SELECTED_TAB = "selectedTab";
|
String INSTANCE_SELECTED_TAB = "selectedTab";
|
||||||
String INSTANCE_FAB_STATE = "fabState";
|
String INSTANCE_FAB_STATE = "fabState";
|
||||||
String INSTANCE_FAB_SUB_MENU_VISIBLE = "fabSubMenuVisible";
|
String INSTANCE_FAB_SUB_MENU_VISIBLE = "fabSubMenuVisible";
|
||||||
|
@ -88,6 +89,7 @@ public interface TrackbookKeys {
|
||||||
long FIFTEEN_SECONDS_IN_MILLISECONDS = 15000; // timer interval for tracking
|
long FIFTEEN_SECONDS_IN_MILLISECONDS = 15000; // timer interval for tracking
|
||||||
long FIVE_MINUTES_IN_NANOSECONDS = 5L * 60000000000L; // determines a stop over
|
long FIVE_MINUTES_IN_NANOSECONDS = 5L * 60000000000L; // determines a stop over
|
||||||
long TWO_MINUTES_IN_NANOSECONDS = 2L * 60000000000L; // defines an old location
|
long TWO_MINUTES_IN_NANOSECONDS = 2L * 60000000000L; // defines an old location
|
||||||
|
int MAXIMUM_TRACK_FILES = 25;
|
||||||
|
|
||||||
/* MISC */
|
/* MISC */
|
||||||
int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
|
int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
|
||||||
|
@ -98,6 +100,10 @@ public interface TrackbookKeys {
|
||||||
int FAB_STATE_DEFAULT = 0;
|
int FAB_STATE_DEFAULT = 0;
|
||||||
int FAB_STATE_RECORDING = 1;
|
int FAB_STATE_RECORDING = 1;
|
||||||
int FAB_STATE_SAVE = 2;
|
int FAB_STATE_SAVE = 2;
|
||||||
|
int FILETYPE_TEMP = 0;
|
||||||
|
int FILETYPE_TRACK = 1;
|
||||||
|
|
||||||
|
String FILENAME_TEMP = "temp";
|
||||||
|
|
||||||
double DEFAULT_LATITUDE = 49.41667; // latitude Nordkapp, Norway
|
double DEFAULT_LATITUDE = 49.41667; // latitude Nordkapp, Norway
|
||||||
double DEFAULT_LONGITUDE = 8.67201; // longitude Nordkapp, Norway
|
double DEFAULT_LONGITUDE = 8.67201; // longitude Nordkapp, Norway
|
||||||
|
|
|
@ -28,18 +28,25 @@ import android.view.MotionEvent;
|
||||||
import android.view.animation.DecelerateInterpolator;
|
import android.view.animation.DecelerateInterpolator;
|
||||||
import android.widget.Scroller;
|
import android.widget.Scroller;
|
||||||
|
|
||||||
|
import org.y20k.trackbook.helpers.LogHelper;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
|
||||||
public class NonSwipeableViewPager extends ViewPager {
|
public class NonSwipeableViewPager extends ViewPager {
|
||||||
|
|
||||||
|
/* Define log tag */
|
||||||
|
private static final String LOG_TAG = NonSwipeableViewPager.class.getSimpleName();
|
||||||
|
|
||||||
|
|
||||||
|
/* Constructor */
|
||||||
public NonSwipeableViewPager(Context context) {
|
public NonSwipeableViewPager(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
setMyScroller();
|
setMyScroller();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Constructor */
|
||||||
public NonSwipeableViewPager(Context context, AttributeSet attrs) {
|
public NonSwipeableViewPager(Context context, AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
setMyScroller();
|
setMyScroller();
|
||||||
|
@ -59,9 +66,9 @@ public class NonSwipeableViewPager extends ViewPager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//down one is added for smooth scrolling
|
//down one is added for smooth scrolling
|
||||||
|
|
||||||
|
/* Attaches a custom scroller to a ViewPager */
|
||||||
private void setMyScroller() {
|
private void setMyScroller() {
|
||||||
try {
|
try {
|
||||||
Class<?> viewpager = ViewPager.class;
|
Class<?> viewpager = ViewPager.class;
|
||||||
|
@ -69,11 +76,15 @@ public class NonSwipeableViewPager extends ViewPager {
|
||||||
scroller.setAccessible(true);
|
scroller.setAccessible(true);
|
||||||
scroller.set(this, new MyScroller(getContext()));
|
scroller.set(this, new MyScroller(getContext()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
LogHelper.e(LOG_TAG, "Problem accessing or modifying the mScroller field.");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inner class: MyScroller is a custom Scroller
|
||||||
|
*/
|
||||||
public class MyScroller extends Scroller {
|
public class MyScroller extends Scroller {
|
||||||
public MyScroller(Context context) {
|
public MyScroller(Context context) {
|
||||||
super(context, new DecelerateInterpolator());
|
super(context, new DecelerateInterpolator());
|
||||||
|
@ -84,4 +95,8 @@ public class NonSwipeableViewPager extends ViewPager {
|
||||||
super.startScroll(startX, startY, dx, dy, 350 /*1 secs*/);
|
super.startScroll(startX, startY, dx, dy, 350 /*1 secs*/);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* End of inner class
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in a new issue