forked from upstream-mirrors/OpenTracks
Interval stats: added a new tab in TrackRecordingActivity that shows the intervals on live.
This commit is contained in:
@@ -25,6 +25,7 @@ import de.dennisguse.opentracks.content.data.Track;
|
|||||||
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
||||||
import de.dennisguse.opentracks.fragments.ChartFragment;
|
import de.dennisguse.opentracks.fragments.ChartFragment;
|
||||||
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
import de.dennisguse.opentracks.fragments.ChooseActivityTypeDialogFragment;
|
||||||
|
import de.dennisguse.opentracks.fragments.IntervalsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
import de.dennisguse.opentracks.fragments.StatisticsRecordingFragment;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceConnection;
|
||||||
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
import de.dennisguse.opentracks.services.TrackRecordingServiceInterface;
|
||||||
@@ -372,7 +373,7 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return 3;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -382,8 +383,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
case 0:
|
case 0:
|
||||||
return new StatisticsRecordingFragment();
|
return new StatisticsRecordingFragment();
|
||||||
case 1:
|
case 1:
|
||||||
return ChartFragment.newInstance(false);
|
return new IntervalsRecordingFragment();
|
||||||
case 2:
|
case 2:
|
||||||
|
return ChartFragment.newInstance(false);
|
||||||
|
case 3:
|
||||||
return ChartFragment.newInstance(true);
|
return ChartFragment.newInstance(true);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
@@ -396,8 +399,10 @@ public class TrackRecordingActivity extends AbstractActivity implements ChooseAc
|
|||||||
case 0:
|
case 0:
|
||||||
return getString(R.string.track_detail_stats_tab);
|
return getString(R.string.track_detail_stats_tab);
|
||||||
case 1:
|
case 1:
|
||||||
return getString(R.string.settings_chart_by_time);
|
return getString(R.string.track_detail_intervals_tab);
|
||||||
case 2:
|
case 2:
|
||||||
|
return getString(R.string.settings_chart_by_time);
|
||||||
|
case 3:
|
||||||
return getString(R.string.settings_chart_by_distance);
|
return getString(R.string.settings_chart_by_distance);
|
||||||
default:
|
default:
|
||||||
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
throw new RuntimeException("There isn't Fragment associated with the position: " + position);
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
package de.dennisguse.opentracks.fragments;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.R;
|
||||||
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||||
|
import de.dennisguse.opentracks.views.IntervalListView;
|
||||||
|
import de.dennisguse.opentracks.views.IntervalReverseListView;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A fragment to display the intervals from recording track.
|
||||||
|
*/
|
||||||
|
public class IntervalsRecordingFragment extends Fragment implements IntervalListView.IntervalListListener {
|
||||||
|
|
||||||
|
private static final String TAG = IntervalsRecordingFragment.class.getSimpleName();
|
||||||
|
|
||||||
|
private IntervalStatisticsModel viewModel;
|
||||||
|
private IntervalReverseListView intervalListView;
|
||||||
|
|
||||||
|
private class IntervalRunner implements Runnable {
|
||||||
|
private boolean stopped = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (!stopped) {
|
||||||
|
updateIntervals();
|
||||||
|
intervalHandler.postDelayed(intervalRunner, 5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
stopped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Handler intervalHandler = null;
|
||||||
|
private IntervalRunner intervalRunner = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.intervals_recording, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
intervalHandler = new Handler();
|
||||||
|
|
||||||
|
intervalListView = new IntervalReverseListView(getActivity(), this);
|
||||||
|
intervalListView.setId(View.generateViewId());
|
||||||
|
LinearLayout linearLayout = view.findViewById(R.id.root_view);
|
||||||
|
linearLayout.removeAllViews();
|
||||||
|
linearLayout.addView(intervalListView);
|
||||||
|
|
||||||
|
viewModel = new ViewModelProvider(this).get(IntervalStatisticsModel.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (intervalRunner == null) {
|
||||||
|
intervalRunner = new IntervalRunner();
|
||||||
|
intervalRunner.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (intervalRunner != null) {
|
||||||
|
intervalRunner.stop();
|
||||||
|
intervalRunner = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
|
||||||
|
intervalListView.destroy();
|
||||||
|
intervalListView = null;
|
||||||
|
viewModel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update intervals through {@link IntervalStatisticsModel} view model.
|
||||||
|
*
|
||||||
|
* @param interval intervals will split in this interval if not null. If it's null then view model will use the default one.
|
||||||
|
*/
|
||||||
|
private void updateIntervals(@Nullable IntervalStatisticsModel.IntervalOption interval) {
|
||||||
|
if (viewModel == null | intervalListView == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long trackId = PreferencesUtils.getRecordingTrackId(getContext());
|
||||||
|
viewModel.invalidate();
|
||||||
|
LiveData<IntervalStatistics> liveData;
|
||||||
|
if (interval == null) {
|
||||||
|
liveData = viewModel.getIntervalStats(trackId);
|
||||||
|
} else {
|
||||||
|
liveData = viewModel.getIntervalStats(trackId, interval);
|
||||||
|
}
|
||||||
|
liveData.observe(getActivity(), intervalStatistics -> {
|
||||||
|
if (intervalStatistics != null) {
|
||||||
|
intervalListView.display(intervalStatistics.getIntervalList());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateIntervals() {
|
||||||
|
updateIntervals(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void intervalChanged(IntervalStatisticsModel.IntervalOption interval) {
|
||||||
|
updateIntervals(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,10 @@ import de.dennisguse.opentracks.content.provider.ContentProviderUtils;
|
|||||||
import de.dennisguse.opentracks.util.PreferencesUtils;
|
import de.dennisguse.opentracks.util.PreferencesUtils;
|
||||||
import de.dennisguse.opentracks.util.UnitConversions;
|
import de.dennisguse.opentracks.util.UnitConversions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This view model is used to load intervals for a track.
|
||||||
|
* It uses a default interval but it can be set from outside to manage the interval length.
|
||||||
|
*/
|
||||||
public class IntervalStatisticsModel extends AndroidViewModel {
|
public class IntervalStatisticsModel extends AndroidViewModel {
|
||||||
|
|
||||||
private MutableLiveData<IntervalStatistics> intervalStats;
|
private MutableLiveData<IntervalStatistics> intervalStats;
|
||||||
@@ -38,6 +42,9 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
|||||||
return getIntervalStats(trackId, interval);
|
return getIntervalStats(trackId, interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this method when you want to force the view model to re-load intervals.
|
||||||
|
*/
|
||||||
public void invalidate() {
|
public void invalidate() {
|
||||||
intervalStats = null;
|
intervalStats = null;
|
||||||
}
|
}
|
||||||
@@ -56,6 +63,9 @@ public class IntervalStatisticsModel extends AndroidViewModel {
|
|||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intervals length this view model support.
|
||||||
|
*/
|
||||||
public enum IntervalOption {
|
public enum IntervalOption {
|
||||||
OPTION_1(1),
|
OPTION_1(1),
|
||||||
OPTION_2(2),
|
OPTION_2(2),
|
||||||
|
|||||||
@@ -17,17 +17,21 @@ import de.dennisguse.opentracks.util.PreferencesUtils;
|
|||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
import de.dennisguse.opentracks.viewmodels.IntervalStatisticsModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LinearLayout view used to build a list of intervals.
|
||||||
|
* See {@link IntervalStatisticsAdapter}.
|
||||||
|
*/
|
||||||
public class IntervalListView extends LinearLayout {
|
public class IntervalListView extends LinearLayout {
|
||||||
|
|
||||||
private IntervalStatisticsAdapter adapter;
|
protected IntervalStatisticsAdapter adapter;
|
||||||
private LinearLayout linearLayoutIntervals;
|
protected LinearLayout linearLayoutIntervals;
|
||||||
private Spinner spinnerIntervals;
|
protected Spinner spinnerIntervals;
|
||||||
private TextView spinnerIntervalsUnit;
|
protected TextView spinnerIntervalsUnit;
|
||||||
|
|
||||||
private Context context;
|
protected Context context;
|
||||||
private IntervalListListener listener;
|
protected IntervalListListener listener;
|
||||||
|
|
||||||
private final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
protected final SharedPreferences.OnSharedPreferenceChangeListener sharedPreferenceChangeListener = (preferences, key) -> {
|
||||||
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
if (PreferencesUtils.isKey(getContext(), R.string.stats_units_key, key) || PreferencesUtils.isKey(getContext(), R.string.stats_rate_key, key)) {
|
||||||
if (spinnerIntervalsUnit != null) {
|
if (spinnerIntervalsUnit != null) {
|
||||||
spinnerIntervalsUnit.setText(PreferencesUtils.isMetricUnits(context) ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile));
|
spinnerIntervalsUnit.setText(PreferencesUtils.isMetricUnits(context) ? context.getString(R.string.unit_kilometer) : context.getString(R.string.unit_mile));
|
||||||
@@ -43,7 +47,7 @@ public class IntervalListView extends LinearLayout {
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void init() {
|
protected void init() {
|
||||||
inflate(getContext(), R.layout.interval_list_view, this);
|
inflate(getContext(), R.layout.interval_list_view, this);
|
||||||
linearLayoutIntervals = findViewById(R.id.interval_list);
|
linearLayoutIntervals = findViewById(R.id.interval_list);
|
||||||
spinnerIntervals = findViewById(R.id.spinner_intervals);
|
spinnerIntervals = findViewById(R.id.spinner_intervals);
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package de.dennisguse.opentracks.views;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import de.dennisguse.opentracks.adapters.IntervalStatisticsAdapter;
|
||||||
|
import de.dennisguse.opentracks.viewmodels.IntervalStatistics;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LinearLayout view used to build a list of intervals in a reverse mode, the last one will appear in the first position on the LinearLayout.
|
||||||
|
* This class is an specialization of {@link IntervalListView} that display the views contained in the LinearLayout in a reverse mode.
|
||||||
|
*/
|
||||||
|
public class IntervalReverseListView extends IntervalListView {
|
||||||
|
|
||||||
|
public IntervalReverseListView(Context context, IntervalListListener listener) {
|
||||||
|
super(context, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void display(List<IntervalStatistics.Interval> intervalList) {
|
||||||
|
if (intervalList != null) {
|
||||||
|
adapter = new IntervalStatisticsAdapter(getContext(), intervalList);
|
||||||
|
linearLayoutIntervals.removeAllViews();
|
||||||
|
for (int i = 0; i < adapter.getCount(); i++) {
|
||||||
|
View intervalView = adapter.getView(i, null, linearLayoutIntervals);
|
||||||
|
linearLayoutIntervals.addView(intervalView, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
style="@style/StatsScrollView">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/root_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:layout_marginRight="8dp" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -432,6 +432,7 @@ limitations under the License.
|
|||||||
<!-- Track Detail -->
|
<!-- Track Detail -->
|
||||||
<string name="track_detail_chart_tab">Chart</string>
|
<string name="track_detail_chart_tab">Chart</string>
|
||||||
<string name="track_detail_stats_tab">Stats</string>
|
<string name="track_detail_stats_tab">Stats</string>
|
||||||
|
<string name="track_detail_intervals_tab">Intervals</string>
|
||||||
<string name="track_detail_record_success">Now recording a new track</string>
|
<string name="track_detail_record_success">Now recording a new track</string>
|
||||||
<string name="track_detail_resume_success">Now resuming the track</string>
|
<string name="track_detail_resume_success">Now resuming the track</string>
|
||||||
<!-- Track Edit -->
|
<!-- Track Edit -->
|
||||||
|
|||||||
Reference in New Issue
Block a user