diff --git a/build.gradle b/build.gradle index b202f2756..2031ca38c 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.2.2' + classpath 'com.android.tools.build:gradle:8.3.1' } } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index b4361b738..e23d5beb6 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -75,6 +75,9 @@ android:exported="false" android:label="@string/title_activity_leaderboard" android:theme="@style/OpenTracksTheme.NoActionBar" /> + @@ -369,7 +372,8 @@ android:launchMode="singleInstance" android:process=":crash" /> + android:exported="true" + tools:ignore="DuplicateActivity"> diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java index 499e8661f..5d3eb4a74 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordedActivity.java @@ -49,8 +49,10 @@ import de.dennisguse.opentracks.settings.SettingsActivity; import de.dennisguse.opentracks.share.ShareUtils; import de.dennisguse.opentracks.ui.aggregatedStatistics.ConfirmDeleteDialogFragment; +import de.dennisguse.opentracks.ui.aggregatedStatistics.dailyStats.DailyStatsActivity; import de.dennisguse.opentracks.ui.intervals.IntervalsFragment; import de.dennisguse.opentracks.ui.markers.MarkerListActivity; +import de.dennisguse.opentracks.ui.menuStatistics.MenuStatisticsActivity; import de.dennisguse.opentracks.util.IntentDashboardUtils; import de.dennisguse.opentracks.util.IntentUtils; @@ -212,6 +214,11 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } + if (item.getItemId() == R.id.filter) { + startActivity(IntentUtils.newIntent(this, MenuStatisticsActivity.class)); + return true; + } + if (item.getItemId() == R.id.track_detail_menu_show_on_map) { IntentDashboardUtils.showTrackOnMap(this, false, trackId); return true; diff --git a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java index 3390484d1..1784a3643 100644 --- a/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java +++ b/src/main/java/de/dennisguse/opentracks/TrackRecordingActivity.java @@ -41,6 +41,7 @@ import de.dennisguse.opentracks.services.handlers.GpsStatusValue; import de.dennisguse.opentracks.settings.PreferencesUtils; import de.dennisguse.opentracks.settings.SettingsActivity; +import de.dennisguse.opentracks.ui.menuStatistics.MenuStatisticsActivity; import de.dennisguse.opentracks.ui.intervals.IntervalsFragment; import de.dennisguse.opentracks.ui.markers.MarkerEditActivity; import de.dennisguse.opentracks.ui.markers.MarkerListActivity; @@ -267,6 +268,11 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } + if (item.getItemId() == R.id.filter) { + startActivity(IntentUtils.newIntent(this, MenuStatisticsActivity.class)); + return true; + } + if (item.getItemId() == R.id.track_detail_insert_marker) { Intent intent = IntentUtils .newIntent(this, MarkerEditActivity.class) diff --git a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java index bc6875850..f91fcae88 100644 --- a/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java +++ b/src/main/java/de/dennisguse/opentracks/data/ContentProviderUtils.java @@ -214,6 +214,34 @@ public List getTracks() { return tracks; } + //Returns list of tracks done within a given period of time + public List getTracks(Instant startTime, Instant endTime) { + // Convert the Instant objects to milliseconds since epoch + long startTimeMillis = startTime.toEpochMilli(); + long endTimeMillis = endTime.toEpochMilli(); + + // Define the selection and selectionArgs for the query + String selection = TracksColumns.STARTTIME + " >= ? AND " + TracksColumns.STOPTIME + " <= ?"; + String[] selectionArgs = new String[] { String.valueOf(startTimeMillis), String.valueOf(endTimeMillis) }; + + // Query the database for tracks within the time period + Cursor cursor = contentResolver.query(TracksColumns.CONTENT_URI, null, selection, selectionArgs, null); + + // Create a list to hold the resulting tracks + List tracks = new ArrayList<>(); + + // Iterate over the results and create Track objects + if (cursor != null) { + while (cursor.moveToNext()) { + Track track = createTrack(cursor); + tracks.add(track); + } + cursor.close(); + } + + return tracks; + } + public List getTracks(ContentProviderSelectionInterface selection) { SelectionData selectionData = selection.buildSelection(); ArrayList tracks = new ArrayList<>(); diff --git a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/dailyStats/DailyStatsActivity.java b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/dailyStats/DailyStatsActivity.java index ea98f4918..c9c6e22dc 100644 --- a/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/dailyStats/DailyStatsActivity.java +++ b/src/main/java/de/dennisguse/opentracks/ui/aggregatedStatistics/dailyStats/DailyStatsActivity.java @@ -8,8 +8,6 @@ import android.widget.Spinner; import com.github.mikephil.charting.charts.LineChart; - - import de.dennisguse.opentracks.R; import de.dennisguse.opentracks.AbstractActivity; import de.dennisguse.opentracks.databinding.DailyStatsBinding; @@ -26,16 +24,16 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Create and populate the spin_metrics spinner with Metric enums. - Spinner spin_metrics = (Spinner)findViewById(R.id.daily_metric); + Spinner spin_metrics = findViewById(R.id.daily_metric); ArrayAdapter array_metrics = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Metric.values()); spin_metrics.setAdapter(array_metrics); // Create and populate the spin_freq spinner with Metric enums. - Spinner spin_freq = (Spinner)findViewById(R.id.daily_data_point); + Spinner spin_freq = findViewById(R.id.daily_data_point); ArrayAdapter array_freq = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, Frequency.values()); spin_freq.setAdapter(array_freq); - line_chart = (LineChart) findViewById(R.id.dailyChart); + line_chart = findViewById(R.id.dailyChart); // Attach one listener to both spinners spin_metrics.setOnItemSelectedListener(this); diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoice.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoice.java new file mode 100644 index 000000000..64604e9e8 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoice.java @@ -0,0 +1,40 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + +import de.dennisguse.opentracks.R; + +// This is an enum class named GraphChoice. +public enum GraphChoice { + // These are the enum constants and each has a display name and a drawable resource associated with it. + DAY("Day", R.drawable.baseline_today_24), + WEEK("Week", R.drawable.baseline_calendar_view_week_24), + MONTH("Month", R.drawable.baseline_calendar_month_24), + SEASON("Season", R.drawable.baseline_season_24); + + // This is a private final field for the display name of the enum constant. + private final String displayName; + // This is a private final field for the drawable resource of the enum constant. + @DrawableRes + private final int drawableRes; + + // This is the constructor for the enum constants. + GraphChoice(String displayName, @DrawableRes int drawableRes) { + this.displayName = displayName; + this.drawableRes = drawableRes; + } + + // This method overrides the toString method and returns the display name of the enum constant. + @NonNull + @Override + public String toString() { + return displayName; + } + + // This method returns the drawable resource of the enum constant. + @DrawableRes + public int getDrawableRes() { + return drawableRes; + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoiceAdapter.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoiceAdapter.java new file mode 100644 index 000000000..619a371b3 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/GraphChoiceAdapter.java @@ -0,0 +1,60 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +// This class is an ArrayAdapter for GraphChoice objects. +public class GraphChoiceAdapter extends ArrayAdapter { + + // Constructor for the GraphChoiceAdapter class. + public GraphChoiceAdapter(Context context, GraphChoice[] choices) { + super(context, 0, choices); + } + + // This method is used to provide a view for the dropdown menu. + @Override + public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { + return createViewFromResource(position, convertView, parent); + } + + // This method is used to provide a view for the adapter. + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { + return createViewFromResource(position, convertView, parent); + } + + // This method is used to create a view from the given resource. + private View createViewFromResource(int position, View convertView, ViewGroup parent) { + View view; + // If convertView is null, inflate a new view. + if (convertView == null) { + view = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_spinner_item, parent, false); + } else { + // Otherwise, reuse the convertView. + view = convertView; + } + + // Get the GraphChoice item for the current position. + GraphChoice choice = getItem(position); + // Find the TextView in the view. + TextView textView = view.findViewById(android.R.id.text1); + // Set the text of the TextView to the string representation of the GraphChoice item. + assert choice != null; + textView.setText(choice.toString()); + // Set the drawable for the TextView. + textView.setCompoundDrawablesWithIntrinsicBounds(choice.getDrawableRes(), 0, 0, 0); + // Set the padding for the drawable. + int padding = (int) (10 * getContext().getResources().getDisplayMetrics().density); + textView.setCompoundDrawablePadding(padding); + + // Return the view. + return view; + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/IntegerValueFormatter.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/IntegerValueFormatter.java new file mode 100644 index 000000000..866914cd5 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/IntegerValueFormatter.java @@ -0,0 +1,16 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import com.github.mikephil.charting.formatter.ValueFormatter; +import java.util.Locale; + +// This class extends the ValueFormatter class from the MPAndroidChart library. +public class IntegerValueFormatter extends ValueFormatter { + + // This method overrides the getFormattedValue method of the ValueFormatter class. + @Override + public String getFormattedValue(float value) { + // It formats the float value as an integer string using the default locale. + // The float value is first cast to an integer before formatting. + return String.format(Locale.getDefault(), "%d", (int) value); + } +} diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuPlottingModule.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuPlottingModule.java new file mode 100644 index 000000000..1d7fc9576 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuPlottingModule.java @@ -0,0 +1,169 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import android.content.Context; +import android.graphics.Color; + +import com.github.mikephil.charting.charts.BarChart; +import com.github.mikephil.charting.components.Legend; +import com.github.mikephil.charting.components.XAxis; +import com.github.mikephil.charting.components.YAxis; +import com.github.mikephil.charting.data.BarData; +import com.github.mikephil.charting.data.BarDataSet; +import com.github.mikephil.charting.data.BarEntry; +import com.github.mikephil.charting.formatter.IndexAxisValueFormatter; + +import java.util.ArrayList; +import java.util.List; + +// This class is responsible for plotting graphs based on the GraphChoice. +public class MenuPlottingModule { + + // These are the data entries for the bar chart. + List dataEntries; + // This is the data set for the bar chart. + BarDataSet dataSet = null; + // These are the labels for the x-axis. + String[] xAxisLabels = null; + + private final TotalRunsProvider totalRunsProvider; + + public MenuPlottingModule(Context context) { + this.totalRunsProvider = new TotalRunsProvider(context); + } + + // This method plots a graph on the given BarChart based on the GraphChoice. + public void plotGraph(BarChart barChart, GraphChoice choice) { + + // The choice determines the type of data entries and the color of the data set. + switch (choice) { + case DAY -> { + dataEntries = getRunsPerHourEntries(totalRunsProvider.getRunsPerHour()); + dataSet = new BarDataSet(dataEntries, "Number of Runs per Hour"); + dataSet.setColor(Color.parseColor("#2774AE")); + } + case WEEK -> { + dataEntries = getRunsPerWeekdayEntries(totalRunsProvider.getRunsPerWeekday()); + dataSet = new BarDataSet(dataEntries, "Number of Runs per Weekday"); + dataSet.setColor(Color.parseColor("#ED9121")); + } + case MONTH -> { + dataEntries = getRunsPerMonthEntries(totalRunsProvider.getRunsPerMonth()); + dataSet = new BarDataSet(dataEntries, "Number of Runs per Month"); + dataSet.setColor(Color.parseColor("#F8DE7E")); + } + case SEASON -> { + dataEntries = getRunsPerSeasonEntries(totalRunsProvider.getRunsPerSeason()); + dataSet = new BarDataSet(dataEntries, "Number of Runs per Season"); + dataSet.setColor(Color.parseColor("#8DB600")); + } + } + + // The data set is added to the BarData. + BarData data = new BarData(dataSet); + // The values in the BarData are formatted as integers. + data.setValueFormatter(new IntegerValueFormatter()); + // The BarData is set to the BarChart. + barChart.setData(data); + + // The x-axis is configured. + XAxis xAxis = barChart.getXAxis(); + xAxis.setValueFormatter(new IndexAxisValueFormatter(xAxisLabels)); + xAxis.setPosition(XAxis.XAxisPosition.BOTTOM); + xAxis.setGranularity(1f); + xAxis.setDrawGridLines(false); + + // The left y-axis is configured. + YAxis leftAxis = barChart.getAxisLeft(); + leftAxis.setDrawLabels(false); + leftAxis.setDrawGridLines(false); + + // The right y-axis is configured. + YAxis rightAxis = barChart.getAxisRight(); + rightAxis.setDrawLabels(false); + rightAxis.setDrawGridLines(false); + + // The legend is configured. + Legend legend = barChart.getLegend(); + legend.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); + legend.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); + + // The description of the BarChart is disabled. + barChart.getDescription().setEnabled(false); + // The BarChart is invalidated to cause a redraw. + barChart.invalidate(); + } + + // This method returns a list of BarEntry objects representing the number of runs per hour. + private List getRunsPerHourEntries(List runs) { + // An ArrayList of BarEntry objects is created. + ArrayList toReturn = new ArrayList<>(); + + // For each run in the list of runs, + for (int i = 0; i < runs.size(); i++) { + // the run is retrieved, + RunViewModel run = runs.get(i); + // the number of runs per hour is retrieved, + int dayRuns = run.getDayRuns(); + // a new BarEntry object is created with the number of runs and added to the list. + toReturn.add(new BarEntry(i + 1, dayRuns)); + } + + // The list of BarEntry objects is returned. + return toReturn; + } + + // This method returns a list of BarEntry objects representing the number of runs per weekday. + private List getRunsPerWeekdayEntries(List runs) { + // An ArrayList of BarEntry objects is created. + ArrayList toReturn = new ArrayList<>(); + + // For each run in the list of runs, + for (int i = 0; i < runs.size(); i++) { + // the run is retrieved, + RunViewModel run = runs.get(i); + // the number of runs per weekday is retrieved, + int weekRuns = run.getWeekRuns(); + // a new BarEntry object is created with the number of runs and added to the list. + toReturn.add(new BarEntry(i + 1, weekRuns)); + } + + // The list of BarEntry objects is returned. + return toReturn; + } + + // This method returns a list of BarEntry objects representing the number of runs per month. + private List getRunsPerMonthEntries(List runs) { + // An ArrayList of BarEntry objects is created. + ArrayList toReturn = new ArrayList<>(); + + // For each run in the list of runs, + for (int i = 0; i < runs.size(); i++) { + // the run is retrieved, + RunViewModel run = runs.get(i); + // the number of runs per month is retrieved, + int monthRuns = run.getMonthRuns(); + // a new BarEntry object is created with the number of runs and added to the list. + toReturn.add(new BarEntry(i + 1, monthRuns)); + } + // The list of BarEntry objects is returned. + return toReturn; + } + + // This method returns a list of BarEntry objects representing the number of runs per season. + private List getRunsPerSeasonEntries(List runs) { + // An ArrayList of BarEntry objects is created. + ArrayList toReturn = new ArrayList<>(); + + // For each run in the list of runs, + for (int i = 0; i < runs.size(); i++) { + // the run is retrieved, + RunViewModel run = runs.get(i); + // the number of runs per season is retrieved, + int seasonRuns = run.getSeasonRuns(); + // a new BarEntry object is created with the number of runs and added to the list. + toReturn.add(new BarEntry(i + 1, seasonRuns)); + } + // The list of BarEntry objects is returned. + return toReturn; + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuStatisticsActivity.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuStatisticsActivity.java new file mode 100644 index 000000000..b8cdb055c --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/MenuStatisticsActivity.java @@ -0,0 +1,70 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import android.os.Bundle; +import android.view.View; +import android.widget.AdapterView; +import android.widget.Spinner; + +import com.github.mikephil.charting.charts.BarChart; +import de.dennisguse.opentracks.AbstractActivity; +import de.dennisguse.opentracks.R; +import de.dennisguse.opentracks.databinding.MenuStatisticsBinding; + +// This class extends AbstractActivity and implements AdapterView.OnItemSelectedListener. +public class MenuStatisticsActivity extends AbstractActivity implements AdapterView.OnItemSelectedListener { + // These are the private fields for the activity. + private MenuStatisticsBinding viewBinding; + private GraphChoice selectedGraph = null; + private final MenuPlottingModule menuPlottingModule = new MenuPlottingModule(this); + private BarChart barChart; + + // This method is called when the activity is created. + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // The graph selector spinner is found and a new GraphChoiceAdapter is set to it. + Spinner graph_selector = findViewById(R.id.graph_selector); + GraphChoiceAdapter array_metrics = new GraphChoiceAdapter(this, GraphChoice.values()); + graph_selector.setAdapter(array_metrics); + // The bar chart is found. + barChart = findViewById(R.id.barChart); + // The activity is set as the item selected listener for the graph selector spinner. + graph_selector.setOnItemSelectedListener(this); + // The bottom app bar is set as the support action bar. + setSupportActionBar(viewBinding.bottomAppBarLayout.bottomAppBar); + } + + // This method is called when an item is selected in the AdapterView. + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + // If the graph selector spinner is the parent, the selected graph is set. + if (parent.getId() == R.id.graph_selector) { + selectedGraph = GraphChoice.values()[position]; + } + + // If a graph is selected, it is plotted on the bar chart. + if (selectedGraph != null) { + menuPlottingModule.plotGraph(barChart, selectedGraph); + } + } + + // This method is called when nothing is selected in the AdapterView. + @Override + public void onNothingSelected(AdapterView parent) { + // If the graph selector spinner is the parent, the selected graph is set to null. + if (parent.getId() == R.id.graph_selector) { + selectedGraph = null; + } + // The bar chart is cleared. + barChart.clear(); + } + + // This method returns the root view for the activity. + @Override + protected View getRootView() { + // The view binding is inflated. + viewBinding = MenuStatisticsBinding.inflate(getLayoutInflater()); + // The root view from the view binding is returned. + return viewBinding.getRoot(); + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/RunViewModel.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/RunViewModel.java new file mode 100644 index 000000000..8153d4123 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/RunViewModel.java @@ -0,0 +1,50 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +// This class is a ViewModel for a Run. +public class RunViewModel { + // These are the private fields for the RunViewModel. + private int id; + private final int dayRuns; + private final int weekRuns; + private final int monthRuns; + private final int seasonRuns; + + // This is the constructor for the RunViewModel. + public RunViewModel(int id, int dayRuns, int weekRuns, int monthRuns, int seasonRuns) { + this.id = id; + this.dayRuns = dayRuns; + this.weekRuns = weekRuns; + this.monthRuns = monthRuns; + this.seasonRuns = seasonRuns; + } + + // This method returns the id of the RunViewModel. + public int getId() { + return id; + } + + // This method sets the id of the RunViewModel. + public void setId(int id) { + this.id = id; + } + + // This method returns the number of runs per day. + public int getDayRuns() { + return dayRuns; + } + + // This method returns the number of runs per week. + public int getWeekRuns() { + return weekRuns; + } + + // This method returns the number of runs per month. + public int getMonthRuns() { + return monthRuns; + } + + // This method returns the number of runs per season. + public int getSeasonRuns() { + return seasonRuns; + } +} \ No newline at end of file diff --git a/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/TotalRunsProvider.java b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/TotalRunsProvider.java new file mode 100644 index 000000000..77c7ce4c4 --- /dev/null +++ b/src/main/java/de/dennisguse/opentracks/ui/menuStatistics/TotalRunsProvider.java @@ -0,0 +1,82 @@ +package de.dennisguse.opentracks.ui.menuStatistics; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; + +import android.content.Context; + +import de.dennisguse.opentracks.data.ContentProviderUtils; +import de.dennisguse.opentracks.data.models.Track; + + +public class TotalRunsProvider { + + private static int runIdCtr = 0; + private final Context context; + + public TotalRunsProvider(Context context) { + this.context = context; + } + + //Returns the number of tracks between the given start and end time. + private int getTrackCount(Instant startTime, Instant endTime) { + List tracks = new ContentProviderUtils(context).getTracks(startTime, endTime); + return tracks.size(); + } + + // This method returns a list of RunViewModel objects representing the number of runs per hour. + public List getRunsPerHour() { + ArrayList runs = new ArrayList<>(); + int trackCount; + for (int i = 0; i < 24; i++) { + Instant startTime = Instant.now().truncatedTo(ChronoUnit.DAYS).plus(i, ChronoUnit.HOURS); + Instant endTime = startTime.plus(1, ChronoUnit.HOURS); + trackCount = getTrackCount(startTime, endTime); + runs.add(new RunViewModel(runIdCtr++, trackCount, 0, 0, 0)); + } + return runs; + } + + // This method returns a list of RunViewModel objects representing the number of runs per weekday. + public List getRunsPerWeekday() { + ArrayList runs = new ArrayList<>(); + int trackCount; + for (int i = 0; i < 7; i++) { + Instant startTime = Instant.now().truncatedTo(ChronoUnit.DAYS).plus(i, ChronoUnit.DAYS); + Instant endTime = startTime.plus(1, ChronoUnit.DAYS); + trackCount = getTrackCount(startTime, endTime); + runs.add(new RunViewModel(runIdCtr++, 0, trackCount, 0, 0)); + } + return runs; + } + + // This method returns a list of RunViewModel objects representing the number of runs per month. + public List getRunsPerMonth() { + ArrayList runs = new ArrayList<>(); + int trackCount; + for (int i = 0; i < 12; i++) { + ZonedDateTime startTime = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).withMonth(i+1); + ZonedDateTime endTime = startTime.plus(1, ChronoUnit.MONTHS); + trackCount = getTrackCount(startTime.toInstant(), endTime.toInstant()); + runs.add(new RunViewModel(runIdCtr++, 0, 0, trackCount, 0)); + } + return runs; + } + + // This method returns a list of RunViewModel objects representing the number of runs per season. + public List getRunsPerSeason() { + ArrayList runs = new ArrayList<>(); + int trackCount; + for (int i = 2021; i <= 2024; i++) { + ZonedDateTime startTime = ZonedDateTime.of(i, 1, 1, 0, 0, 0, 0, ZoneId.systemDefault()); + ZonedDateTime endTime = startTime.plus(1, ChronoUnit.YEARS); + trackCount = getTrackCount(startTime.toInstant(), endTime.toInstant()); + runs.add(new RunViewModel(runIdCtr++, 0, 0, 0, trackCount)); + } + return runs; + } +} \ No newline at end of file diff --git a/src/main/res/drawable/baseline_calendar_month_24.xml b/src/main/res/drawable/baseline_calendar_month_24.xml new file mode 100644 index 000000000..2a703af64 --- /dev/null +++ b/src/main/res/drawable/baseline_calendar_month_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/drawable/baseline_calendar_view_week_24.xml b/src/main/res/drawable/baseline_calendar_view_week_24.xml new file mode 100644 index 000000000..283aa1502 --- /dev/null +++ b/src/main/res/drawable/baseline_calendar_view_week_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/drawable/baseline_season_24.xml b/src/main/res/drawable/baseline_season_24.xml new file mode 100644 index 000000000..913672fa7 --- /dev/null +++ b/src/main/res/drawable/baseline_season_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/drawable/baseline_today_24.xml b/src/main/res/drawable/baseline_today_24.xml new file mode 100644 index 000000000..b8bbb4952 --- /dev/null +++ b/src/main/res/drawable/baseline_today_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/src/main/res/layout/menu_statistics.xml b/src/main/res/layout/menu_statistics.xml new file mode 100644 index 000000000..4b8ecfbfb --- /dev/null +++ b/src/main/res/layout/menu_statistics.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/menu/track_detail.xml b/src/main/res/menu/track_detail.xml index 0f88e6fb2..5dabcceab 100644 --- a/src/main/res/menu/track_detail.xml +++ b/src/main/res/menu/track_detail.xml @@ -15,6 +15,11 @@ limitations under the License. --> + + @string/sharing_option_share_with @string/sharing_option_public + Day \ No newline at end of file diff --git a/src/main/res/values/ids.xml b/src/main/res/values/ids.xml new file mode 100644 index 000000000..dc96c75d3 --- /dev/null +++ b/src/main/res/values/ids.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 44f608ea0..83e261e49 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -847,4 +847,11 @@ limitations under the License. Tab 1 Tab 2 Moving Average + Week + Month + Season + Dropdown Menu Statistics + Filter Graph + Total Run Menu Statistics + See Menu Selection Image \ No newline at end of file