diff options
author | ByteHamster <ByteHamster@users.noreply.github.com> | 2022-02-21 22:53:18 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-21 22:53:18 +0100 |
commit | b6d23168707bd55e5bb4060a9cd8e8ecf96a9716 (patch) | |
tree | 044c0195da0168f0c3561fefaae3a6e98f50c33c | |
parent | 4655fcfc80fbb2083abbb68b6b07283e462da940 (diff) | |
download | AntennaPod-b6d23168707bd55e5bb4060a9cd8e8ecf96a9716.zip |
Add time-based statistics filter (#5734)
19 files changed, 342 insertions, 241 deletions
diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java index 26674b2b2..e566836e4 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/PlaybackStatisticsListAdapter.java @@ -2,15 +2,15 @@ package de.danoeh.antennapod.adapter; import androidx.fragment.app.Fragment; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.StatisticsItem; import de.danoeh.antennapod.core.util.Converter; -import de.danoeh.antennapod.core.util.DateFormatter; import de.danoeh.antennapod.fragment.FeedStatisticsDialogFragment; import de.danoeh.antennapod.view.PieChartView; +import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; +import java.util.Locale; /** * Adapter for the playback statistics list. @@ -18,26 +18,30 @@ import java.util.List; public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { private final Fragment fragment; - boolean countAll = true; + private long timeFilterFrom = 0; + private long timeFilterTo = Long.MAX_VALUE; + private boolean includeMarkedAsPlayed = false; public PlaybackStatisticsListAdapter(Fragment fragment) { super(fragment.getContext()); this.fragment = fragment; } - public void setCountAll(boolean countAll) { - this.countAll = countAll; + public void setTimeFilter(boolean includeMarkedAsPlayed, long timeFilterFrom, long timeFilterTo) { + this.includeMarkedAsPlayed = includeMarkedAsPlayed; + this.timeFilterFrom = timeFilterFrom; + this.timeFilterTo = timeFilterTo; } @Override String getHeaderCaption() { - long usageCounting = UserPreferences.getUsageCountingDateMillis(); - if (usageCounting > 0) { - String date = DateFormatter.formatAbbrev(context, new Date(usageCounting)); - return context.getString(R.string.statistics_counting_since, date); - } else { - return context.getString(R.string.total_time_listened_to_podcasts); + if (includeMarkedAsPlayed) { + return context.getString(R.string.statistics_counting_total); } + SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy", Locale.getDefault()); + String dateFrom = dateFormat.format(new Date(timeFilterFrom)); + String dateTo = dateFormat.format(new Date(timeFilterTo)); + return context.getString(R.string.statistics_counting_range, dateFrom, dateTo); } @Override @@ -50,14 +54,14 @@ public class PlaybackStatisticsListAdapter extends StatisticsListAdapter { float[] dataValues = new float[statisticsData.size()]; for (int i = 0; i < statisticsData.size(); i++) { StatisticsItem item = statisticsData.get(i); - dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed; + dataValues[i] = item.timePlayed; } return new PieChartView.PieChartData(dataValues); } @Override void onBindFeedViewHolder(StatisticsHolder holder, StatisticsItem statsItem) { - long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed; + long time = statsItem.timePlayed; holder.value.setText(Converter.shortLocalizedDuration(context, time)); holder.itemView.setOnClickListener(v -> { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java index 23b5cfdce..f0451e974 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/StatisticsListAdapter.java @@ -48,10 +48,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(context); if (viewType == TYPE_HEADER) { - View view = inflater.inflate(R.layout.statistics_listitem_total, parent, false); - TextView totalText = view.findViewById(R.id.total_description); - totalText.setText(getHeaderCaption()); - return new HeaderHolder(view); + return new HeaderHolder(inflater.inflate(R.layout.statistics_listitem_total, parent, false)); } return new StatisticsHolder(inflater.inflate(R.layout.statistics_listitem, parent, false)); } @@ -62,6 +59,7 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle HeaderHolder holder = (HeaderHolder) h; holder.pieChart.setData(pieChartData); holder.totalTime.setText(getHeaderValue()); + holder.totalText.setText(getHeaderCaption()); } else { StatisticsHolder holder = (StatisticsHolder) h; StatisticsItem statsItem = statisticsData.get(position - 1); @@ -90,11 +88,13 @@ public abstract class StatisticsListAdapter extends RecyclerView.Adapter<Recycle static class HeaderHolder extends RecyclerView.ViewHolder { TextView totalTime; PieChartView pieChart; + TextView totalText; HeaderHolder(View itemView) { super(itemView); totalTime = itemView.findViewById(R.id.total_time); pieChart = itemView.findViewById(R.id.pie_chart); + totalText = itemView.findViewById(R.id.total_description); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java index e85c2a386..d6ab34855 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/FeedStatisticsFragment.java @@ -17,7 +17,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; -import java.util.List; +import java.util.Collections; import java.util.Locale; public class FeedStatisticsFragment extends Fragment { @@ -60,8 +60,11 @@ public class FeedStatisticsFragment extends Fragment { private void loadStatistics() { disposable = Observable.fromCallable(() -> { - List<StatisticsItem> statisticsData = DBReader.getStatistics(); - for (StatisticsItem statisticsItem : statisticsData) { + DBReader.StatisticsResult statisticsData = DBReader.getStatistics(true, 0, Long.MAX_VALUE); + Collections.sort(statisticsData.feedTime, (item1, item2) -> + Long.compare(item2.timePlayed, item1.timePlayed)); + + for (StatisticsItem statisticsItem : statisticsData.feedTime) { if (statisticsItem.feed.getId() == feedId) { return statisticsItem; } @@ -77,7 +80,6 @@ public class FeedStatisticsFragment extends Fragment { viewBinding.startedTotalLabel.setText(String.format(Locale.getDefault(), "%d / %d", s.episodesStarted, s.episodes)); viewBinding.timePlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayed)); - viewBinding.durationPlayedLabel.setText(Converter.shortLocalizedDuration(getContext(), s.timePlayedCountAll)); viewBinding.totalDurationLabel.setText(Converter.shortLocalizedDuration(getContext(), s.time)); viewBinding.onDeviceLabel.setText(String.format(Locale.getDefault(), "%d", s.episodesDownloadCount)); viewBinding.spaceUsedLabel.setText(Formatter.formatShortFileSize(getContext(), s.totalDownloadSize)); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java index ffaaedec8..f8f489fc4 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/DownloadStatisticsFragment.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.fragment.preferences; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.widget.ProgressBar; @@ -16,14 +17,12 @@ import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.DownloadStatisticsListAdapter; import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.StatisticsItem; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; import java.util.Collections; -import java.util.List; /** * Displays the 'download statistics' screen @@ -38,7 +37,8 @@ public class DownloadStatisticsFragment extends Fragment { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { View root = inflater.inflate(R.layout.statistics_activity, container, false); downloadStatisticsList = root.findViewById(R.id.statistics_list); progressBar = root.findViewById(R.id.progressBar); @@ -54,6 +54,13 @@ public class DownloadStatisticsFragment extends Fragment { refreshDownloadStatistics(); } + @Override + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.statistics_reset).setVisible(false); + menu.findItem(R.id.statistics_filter).setVisible(false); + } + private void refreshDownloadStatistics() { progressBar.setVisibility(View.VISIBLE); downloadStatisticsList.setVisibility(View.GONE); @@ -67,15 +74,16 @@ public class DownloadStatisticsFragment extends Fragment { disposable = Observable.fromCallable(() -> { - List<StatisticsItem> statisticsData = DBReader.getStatistics(); - Collections.sort(statisticsData, (item1, item2) -> + // Filters do not matter here + DBReader.StatisticsResult statisticsData = DBReader.getStatistics(false, 0, Long.MAX_VALUE); + Collections.sort(statisticsData.feedTime, (item1, item2) -> Long.compare(item2.totalDownloadSize, item1.totalDownloadSize)); return statisticsData; }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { - listAdapter.update(result); + listAdapter.update(result.feedTime); progressBar.setVisibility(View.GONE); downloadStatisticsList.setVisibility(View.VISIBLE); }, error -> Log.e(TAG, Log.getStackTraceString(error))); diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java index 5156de432..5bbc8f0a0 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/ImportExportPreferencesFragment.java @@ -31,7 +31,6 @@ import de.danoeh.antennapod.core.export.ExportWriter; import de.danoeh.antennapod.core.export.favorites.FavoritesWriter; import de.danoeh.antennapod.core.export.html.HtmlWriter; import de.danoeh.antennapod.core.export.opml.OpmlWriter; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DatabaseExporter; import io.reactivex.Completable; import io.reactivex.Observable; @@ -263,7 +262,6 @@ public class ImportExportPreferencesFragment extends PreferenceFragmentCompat { .observeOn(AndroidSchedulers.mainThread()) .subscribe(() -> { showDatabaseImportSuccessDialog(); - UserPreferences.unsetUsageCountingDate(); progressDialog.dismiss(); }, this::showExportErrorDialog); } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java index 891d3737b..e2c5036df 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/MainPreferencesFragment.java @@ -29,7 +29,6 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { private static final String PREF_VIEW_FORUM = "prefViewForum"; private static final String PREF_SEND_BUG_REPORT = "prefSendBugReport"; private static final String PREF_CATEGORY_PROJECT = "project"; - private static final String STATISTICS = "statistics"; private static final String PREF_ABOUT = "prefAbout"; private static final String PREF_NOTIFICATION = "notifications"; private static final String PREF_CONTRIBUTE = "prefContribute"; @@ -106,14 +105,6 @@ public class MainPreferencesFragment extends PreferenceFragmentCompat { return true; } ); - findPreference(STATISTICS).setOnPreferenceClickListener( - preference -> { - getParentFragmentManager().beginTransaction() - .replace(R.id.settingsContainer, new StatisticsFragment()) - .addToBackStack(getString(R.string.statistics_label)).commit(); - return true; - } - ); findPreference(PREF_DOCUMENTATION).setOnPreferenceClickListener(preference -> { IntentUtils.openInBrowser(getContext(), "https://antennapod.org/documentation/"); return true; diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java index ba6164212..31c96a2ff 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/PlaybackStatisticsFragment.java @@ -7,16 +7,16 @@ import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.ProgressBar; -import android.widget.RadioButton; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.core.util.Pair; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -24,18 +24,21 @@ import androidx.recyclerview.widget.RecyclerView; import de.danoeh.antennapod.R; import de.danoeh.antennapod.adapter.PlaybackStatisticsListAdapter; import de.danoeh.antennapod.core.dialog.ConfirmationDialog; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.StatisticsItem; +import de.danoeh.antennapod.databinding.StatisticsFilterDialogBinding; import io.reactivex.Completable; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; import java.util.Collections; -import java.util.List; +import java.util.Date; +import java.util.Locale; /** * Displays the 'playback statistics' screen @@ -43,20 +46,27 @@ import java.util.List; public class PlaybackStatisticsFragment extends Fragment { private static final String TAG = PlaybackStatisticsFragment.class.getSimpleName(); private static final String PREF_NAME = "StatisticsActivityPrefs"; - private static final String PREF_COUNT_ALL = "countAll"; + private static final String PREF_INCLUDE_MARKED_PLAYED = "countAll"; + private static final String PREF_FILTER_FROM = "filterFrom"; + private static final String PREF_FILTER_TO = "filterTo"; private Disposable disposable; private RecyclerView feedStatisticsList; private ProgressBar progressBar; private PlaybackStatisticsListAdapter listAdapter; - private boolean countAll = false; + private boolean includeMarkedAsPlayed = false; + private long timeFilterFrom = 0; + private long timeFilterTo = Long.MAX_VALUE; private SharedPreferences prefs; + private DBReader.StatisticsResult statisticsResult; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); prefs = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); - countAll = prefs.getBoolean(PREF_COUNT_ALL, false); + includeMarkedAsPlayed = prefs.getBoolean(PREF_INCLUDE_MARKED_PLAYED, false); + timeFilterFrom = prefs.getLong(PREF_FILTER_FROM, 0); + timeFilterTo = prefs.getLong(PREF_FILTER_TO, Long.MAX_VALUE); setHasOptionsMenu(true); } @@ -68,7 +78,6 @@ public class PlaybackStatisticsFragment extends Fragment { feedStatisticsList = root.findViewById(R.id.statistics_list); progressBar = root.findViewById(R.id.progressBar); listAdapter = new PlaybackStatisticsListAdapter(this); - listAdapter.setCountAll(countAll); feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext())); feedStatisticsList.setAdapter(listAdapter); return root; @@ -89,62 +98,129 @@ public class PlaybackStatisticsFragment extends Fragment { } @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - inflater.inflate(R.menu.statistics, menu); - menu.findItem(R.id.statistics_reset).setEnabled(!countAll); + public void onPrepareOptionsMenu(@NonNull Menu menu) { + super.onPrepareOptionsMenu(menu); + menu.findItem(R.id.statistics_reset).setVisible(true); + menu.findItem(R.id.statistics_filter).setVisible(true); } @Override public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.statistics_mode) { - selectStatisticsMode(); + if (item.getItemId() == R.id.statistics_filter) { + selectStatisticsFilter(); return true; - } - if (item.getItemId() == R.id.statistics_reset) { + } else if (item.getItemId() == R.id.statistics_reset) { confirmResetStatistics(); return true; } return super.onOptionsItemSelected(item); } - private void selectStatisticsMode() { - View contentView = View.inflate(getContext(), R.layout.statistics_mode_select_dialog, null); + private void selectStatisticsFilter() { + if (statisticsResult == null) { + return; + } + StatisticsFilterDialogBinding dialogBinding = StatisticsFilterDialogBinding.inflate(getLayoutInflater()); AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); - builder.setView(contentView); - builder.setTitle(R.string.statistics_mode); + builder.setView(dialogBinding.getRoot()); + builder.setTitle(R.string.filter); + dialogBinding.includeMarkedCheckbox.setOnCheckedChangeListener((compoundButton, checked) -> { + dialogBinding.timeToSpinner.setEnabled(!checked); + dialogBinding.timeFromSpinner.setEnabled(!checked); + dialogBinding.lastYearButton.setEnabled(!checked); + dialogBinding.allTimeButton.setEnabled(!checked); + dialogBinding.dateSelectionContainer.setAlpha(checked ? 0.5f : 1f); + }); + dialogBinding.includeMarkedCheckbox.setChecked(includeMarkedAsPlayed); + + Pair<String[], Long[]> filterDates = makeMonthlyList(statisticsResult.oldestDate); + + ArrayAdapter<String> adapterFrom = new ArrayAdapter<>(getContext(), + android.R.layout.simple_spinner_item, filterDates.first); + adapterFrom.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + dialogBinding.timeFromSpinner.setAdapter(adapterFrom); + for (int i = 0; i < filterDates.second.length; i++) { + if (filterDates.second[i] >= timeFilterFrom) { + dialogBinding.timeFromSpinner.setSelection(i); + break; + } + } - if (countAll) { - ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).setChecked(true); - } else { - ((RadioButton) contentView.findViewById(R.id.statistics_mode_normal)).setChecked(true); + ArrayAdapter<String> adapterTo = new ArrayAdapter<>(getContext(), + android.R.layout.simple_spinner_item, filterDates.first); + adapterTo.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + dialogBinding.timeToSpinner.setAdapter(adapterTo); + for (int i = 0; i < filterDates.second.length; i++) { + if (filterDates.second[i] >= timeFilterTo) { + dialogBinding.timeToSpinner.setSelection(i); + break; + } } + dialogBinding.allTimeButton.setOnClickListener(v -> { + dialogBinding.timeFromSpinner.setSelection(0); + dialogBinding.timeToSpinner.setSelection(filterDates.first.length - 1); + }); + dialogBinding.lastYearButton.setOnClickListener(v -> { + dialogBinding.timeFromSpinner.setSelection(Math.max(0, filterDates.first.length - 14)); + dialogBinding.timeToSpinner.setSelection(filterDates.first.length - 2); + }); + builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { - countAll = ((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).isChecked(); - listAdapter.setCountAll(countAll); - prefs.edit().putBoolean(PREF_COUNT_ALL, countAll).apply(); + includeMarkedAsPlayed = dialogBinding.includeMarkedCheckbox.isChecked(); + if (includeMarkedAsPlayed) { + // We do not know the date at which something was marked as played, so filtering does not make sense + timeFilterFrom = 0; + timeFilterTo = Long.MAX_VALUE; + } else { + timeFilterFrom = filterDates.second[dialogBinding.timeFromSpinner.getSelectedItemPosition()]; + timeFilterTo = filterDates.second[dialogBinding.timeToSpinner.getSelectedItemPosition()]; + } + prefs.edit() + .putBoolean(PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed) + .putLong(PREF_FILTER_FROM, timeFilterFrom) + .putLong(PREF_FILTER_TO, timeFilterTo) + .apply(); refreshStatistics(); - getActivity().invalidateOptionsMenu(); }); - builder.show(); } - private void confirmResetStatistics() { - if (!countAll) { - ConfirmationDialog conDialog = new ConfirmationDialog( - getActivity(), - R.string.statistics_reset_data, - R.string.statistics_reset_data_msg) { - - @Override - public void onConfirmButtonPressed(DialogInterface dialog) { - dialog.dismiss(); - doResetStatistics(); - } - }; - conDialog.createNewDialog().show(); + private Pair<String[], Long[]> makeMonthlyList(long oldestDate) { + Calendar date = Calendar.getInstance(); + date.setTimeInMillis(oldestDate); + date.set(Calendar.DAY_OF_MONTH, 1); + ArrayList<String> names = new ArrayList<>(); + ArrayList<Long> timestamps = new ArrayList<>(); + SimpleDateFormat dateFormat = new SimpleDateFormat("MMM yyyy", Locale.getDefault()); + while (date.getTimeInMillis() < System.currentTimeMillis()) { + names.add(dateFormat.format(new Date(date.getTimeInMillis()))); + timestamps.add(date.getTimeInMillis()); + if (date.get(Calendar.MONTH) == Calendar.DECEMBER) { + date.set(Calendar.MONTH, Calendar.JANUARY); + date.set(Calendar.YEAR, date.get(Calendar.YEAR) + 1); + } else { + date.set(Calendar.MONTH, date.get(Calendar.MONTH) + 1); + } } + names.add(getString(R.string.statistics_today)); + timestamps.add(Long.MAX_VALUE); + return new Pair<>(names.toArray(new String[0]), timestamps.toArray(new Long[0])); + } + + private void confirmResetStatistics() { + ConfirmationDialog conDialog = new ConfirmationDialog( + getActivity(), + R.string.statistics_reset_data, + R.string.statistics_reset_data_msg) { + + @Override + public void onConfirmButtonPressed(DialogInterface dialog) { + dialog.dismiss(); + doResetStatistics(); + } + }; + conDialog.createNewDialog().show(); } private void doResetStatistics() { @@ -154,13 +230,19 @@ public class PlaybackStatisticsFragment extends Fragment { disposable.dispose(); } + includeMarkedAsPlayed = false; + timeFilterFrom = 0; + timeFilterTo = Long.MAX_VALUE; + prefs.edit() + .putBoolean(PREF_INCLUDE_MARKED_PLAYED, includeMarkedAsPlayed) + .putLong(PREF_FILTER_FROM, timeFilterFrom) + .putLong(PREF_FILTER_TO, timeFilterTo) + .apply(); + disposable = Completable.fromFuture(DBWriter.resetStatistics()) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { - refreshStatistics(); - UserPreferences.resetUsageCountingDate(); - }, error -> Log.e(TAG, Log.getStackTraceString(error))); + .subscribe(this::refreshStatistics, error -> Log.e(TAG, Log.getStackTraceString(error))); } private void refreshStatistics() { @@ -173,25 +255,25 @@ public class PlaybackStatisticsFragment extends Fragment { if (disposable != null) { disposable.dispose(); } - disposable = Observable.fromCallable(this::fetchStatistics) + disposable = Observable.fromCallable( + () -> { + DBReader.StatisticsResult statisticsData = DBReader.getStatistics( + includeMarkedAsPlayed, timeFilterFrom, timeFilterTo); + Collections.sort(statisticsData.feedTime, (item1, item2) -> + Long.compare(item2.timePlayed, item1.timePlayed)); + return statisticsData; + }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { - listAdapter.update(result); + statisticsResult = result; + // When "from" is "today", set it to today + listAdapter.setTimeFilter(includeMarkedAsPlayed, Math.max( + Math.min(timeFilterFrom, System.currentTimeMillis()), result.oldestDate), + Math.min(timeFilterTo, System.currentTimeMillis())); + listAdapter.update(result.feedTime); progressBar.setVisibility(View.GONE); feedStatisticsList.setVisibility(View.VISIBLE); }, error -> Log.e(TAG, Log.getStackTraceString(error))); } - - private List<StatisticsItem> fetchStatistics() { - List<StatisticsItem> statisticsData = DBReader.getStatistics(); - if (countAll) { - Collections.sort(statisticsData, (item1, item2) -> - Long.compare(item2.timePlayedCountAll, item1.timePlayedCountAll)); - } else { - Collections.sort(statisticsData, (item1, item2) -> - Long.compare(item2.timePlayed, item1.timePlayed)); - } - return statisticsData; - } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java index 4110662a1..5a280ac81 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/StatisticsFragment.java @@ -16,11 +16,12 @@ import com.google.android.material.tabs.TabLayoutMediator; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.PreferenceActivity; +import de.danoeh.antennapod.fragment.PagedToolbarFragment; /** * Displays the 'statistics' screen */ -public class StatisticsFragment extends Fragment { +public class StatisticsFragment extends PagedToolbarFragment { public static final String TAG = "StatisticsFragment"; @@ -28,7 +29,6 @@ public class StatisticsFragment extends Fragment { private static final int POS_SPACE_TAKEN = 1; private static final int TOTAL_COUNT = 2; - private TabLayout tabLayout; private ViewPager2 viewPager; private Toolbar toolbar; @@ -42,9 +42,13 @@ public class StatisticsFragment extends Fragment { View rootView = inflater.inflate(R.layout.pager_fragment, container, false); viewPager = rootView.findViewById(R.id.viewpager); toolbar = rootView.findViewById(R.id.toolbar); + toolbar.setTitle(getString(R.string.statistics_label)); + toolbar.inflateMenu(R.menu.statistics); + toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); viewPager.setAdapter(new StatisticsPagerAdapter(this)); // Give the TabLayout the ViewPager tabLayout = rootView.findViewById(R.id.sliding_tabs); + super.setupPagedToolbar(toolbar, viewPager); new TabLayoutMediator(tabLayout, viewPager, (tab, position) -> { switch (position) { case POS_LISTENED_HOURS: @@ -57,14 +61,6 @@ public class StatisticsFragment extends Fragment { break; } }).attach(); - - if (getActivity().getClass() == PreferenceActivity.class) { - rootView.findViewById(R.id.toolbar).setVisibility(View.GONE); - } else { - toolbar.setTitle(getString(R.string.statistics_label)); - toolbar.setNavigationOnClickListener(v -> getParentFragmentManager().popBackStack()); - } - return rootView; } diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index af35bbac9..3a0e4e91f 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -39,9 +39,6 @@ public class PreferenceUpgrader { private static void upgrade(int oldVersion, Context context) { if (oldVersion == -1) { //New installation - if (UserPreferences.getUsageCountingDateMillis() < 0) { - UserPreferences.resetUsageCountingDate(); - } return; } if (oldVersion < 1070196) { diff --git a/app/src/main/res/layout/feed_statistics.xml b/app/src/main/res/layout/feed_statistics.xml index f8f5ac555..7897a7d5f 100644 --- a/app/src/main/res/layout/feed_statistics.xml +++ b/app/src/main/res/layout/feed_statistics.xml @@ -46,24 +46,6 @@ <TextView android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/statistics_duration_played_episodes" /> - - <TextView - android:id="@+id/durationPlayedLabel" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginLeft="8dp" - android:layout_marginStart="8dp" - tools:text="0 min" /> - - </TableRow> - - <TableRow - android:tag="detailed"> - - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" android:text="@string/statistics_total_duration" /> <TextView diff --git a/app/src/main/res/layout/statistics_filter_dialog.xml b/app/src/main/res/layout/statistics_filter_dialog.xml new file mode 100644 index 000000000..d37226c07 --- /dev/null +++ b/app/src/main/res/layout/statistics_filter_dialog.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <CheckBox + android:id="@+id/includeMarkedCheckbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_include_marked" + android:layout_marginBottom="8dp" /> + + <LinearLayout + android:id="@+id/dateSelectionContainer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/statistics_from" + android:padding="4dp" + android:layout_weight="1" /> + + <TextView + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/statistics_to" + android:padding="4dp" + android:layout_weight="1" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Spinner + android:id="@+id/timeFromSpinner" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <Spinner + android:id="@+id/timeToSpinner" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/lastYearButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/statistics_filter_last_year" + android:layout_weight="1" + android:layout_marginEnd="4dp" + style="@style/Widget.MaterialComponents.Button.OutlinedButton" /> + + <Button + android:id="@+id/allTimeButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:text="@string/statistics_filter_all_time" + android:layout_weight="1" + android:layout_marginStart="4dp" + style="@style/Widget.MaterialComponents.Button.OutlinedButton" /> + + </LinearLayout> + + </LinearLayout> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/statistics_speed_not_counted" + android:layout_marginTop="16dp" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/statistics_listitem_total.xml b/app/src/main/res/layout/statistics_listitem_total.xml index 628e26c1f..11ff24977 100644 --- a/app/src/main/res/layout/statistics_listitem_total.xml +++ b/app/src/main/res/layout/statistics_listitem_total.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> -<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" @@ -16,33 +17,32 @@ android:layout_marginLeft="8dp" /> <TextView - android:id="@+id/total_description" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_centerHorizontal="true" - android:textAlignment="center" - android:layout_marginLeft="56dp" - android:layout_marginRight="56dp" - android:maxLines="3" - android:layout_above="@id/total_time" - android:textSize="14sp" /> - - <TextView + android:id="@+id/total_time" android:layout_width="match_parent" android:layout_height="wrap_content" - android:id="@+id/total_time" android:textColor="?android:attr/textColorPrimary" android:gravity="center_horizontal" android:textSize="28sp" + android:layout_marginBottom="4dp" + android:layout_above="@id/total_description" + tools:text="10.0 hours" /> + + <TextView + android:id="@+id/total_description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:textAlignment="center" + android:maxLines="3" + android:textSize="14sp" android:layout_marginBottom="16dp" - android:layout_alignBottom="@id/pie_chart" - tools:text="10.0 hours"/> + android:layout_alignBottom="@id/pie_chart" /> <View android:layout_width="match_parent" android:layout_height="1dp" android:layout_marginTop="16dp" android:background="?android:attr/dividerVertical" - android:layout_below="@+id/pie_chart"/> + android:layout_below="@+id/pie_chart" /> -</RelativeLayout>
\ No newline at end of file +</RelativeLayout> diff --git a/app/src/main/res/layout/statistics_mode_select_dialog.xml b/app/src/main/res/layout/statistics_mode_select_dialog.xml deleted file mode 100644 index 8f8e1e657..000000000 --- a/app/src/main/res/layout/statistics_mode_select_dialog.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<RadioGroup xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - android:padding="16dp"> - - <TextView - android:text="@string/statistics_speed_not_counted" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginBottom="16dp"/> - - <RadioButton - android:id="@+id/statistics_mode_normal" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/statistics_mode_normal"/> - - <RadioButton - android:id="@+id/statistics_mode_count_all" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:text="@string/statistics_mode_count_all"/> -</RadioGroup> diff --git a/app/src/main/res/menu/statistics.xml b/app/src/main/res/menu/statistics.xml index 9e4b7fab1..71bd12b63 100644 --- a/app/src/main/res/menu/statistics.xml +++ b/app/src/main/res/menu/statistics.xml @@ -5,14 +5,13 @@ <item android:id="@+id/statistics_reset" android:title="@string/statistics_reset_data" - custom:showAsAction="never" - /> + custom:showAsAction="never" /> <item - android:id="@+id/statistics_mode" + android:id="@+id/statistics_filter" android:icon="@drawable/ic_filter" - android:title="@string/statistics_mode" - custom:showAsAction="never"> + android:title="@string/filter" + custom:showAsAction="always"> </item> </menu> diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 91ba649d2..9967d7fd1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -44,11 +44,6 @@ android:title="@string/notification_pref_fragment" android:icon="@drawable/ic_notifications"/> - <Preference - android:icon="@drawable/chart_box_outline" - android:key="statistics" - android:title="@string/statistics_label" /> - <PreferenceCategory android:key="project" android:title="@string/project_pref"> diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index 74f89a039..357ea0b61 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -23,7 +23,6 @@ import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.ArrayList; import java.util.Arrays; -import java.util.Calendar; import java.util.HashSet; import java.util.List; import java.util.Locale; @@ -1071,22 +1070,6 @@ public class UserPreferences { .apply(); } - public static long getUsageCountingDateMillis() { - return prefs.getLong(PREF_USAGE_COUNTING_DATE, -1); - } - - private static void setUsageCountingDateMillis(long value) { - prefs.edit().putLong(PREF_USAGE_COUNTING_DATE, value).apply(); - } - - public static void resetUsageCountingDate() { - setUsageCountingDateMillis(Calendar.getInstance().getTimeInMillis()); - } - - public static void unsetUsageCountingDate() { - setUsageCountingDateMillis(-1); - } - public static boolean shouldShowSubscriptionTitle() { return prefs.getBoolean(PREF_SUBSCRIPTION_TITLE, false); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java index da6987910..5ea3f1e14 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java @@ -771,26 +771,29 @@ public final class DBReader { } } + public static class StatisticsResult { + public List<StatisticsItem> feedTime = new ArrayList<>(); + public long oldestDate = System.currentTimeMillis(); + } + /** * Searches the DB for statistics. * * @return The list of statistics objects */ @NonNull - public static List<StatisticsItem> getStatistics() { + public static StatisticsResult getStatistics(boolean includeMarkedAsPlayed, + long timeFilterFrom, long timeFilterTo) { PodDBAdapter adapter = PodDBAdapter.getInstance(); adapter.open(); - List<StatisticsItem> feedTime = new ArrayList<>(); - + StatisticsResult result = new StatisticsResult(); List<Feed> feeds = getFeedList(); for (Feed feed : feeds) { - long feedPlayedTimeCountAll = 0; long feedPlayedTime = 0; long feedTotalTime = 0; long episodes = 0; long episodesStarted = 0; - long episodesStartedIncludingMarked = 0; long totalDownloadSize = 0; long episodesDownloadCount = 0; List<FeedItem> items = getFeed(feed.getId()).getItems(); @@ -800,20 +803,22 @@ public final class DBReader { continue; } - feedPlayedTime += media.getPlayedDuration() / 1000; - - if (item.isPlayed()) { - feedPlayedTimeCountAll += media.getDuration() / 1000; - } else { - feedPlayedTimeCountAll += media.getPosition() / 1000; + if (media.getLastPlayedTime() > 0 && media.getPlayedDuration() != 0) { + result.oldestDate = Math.min(result.oldestDate, media.getLastPlayedTime()); } - - if (media.getPlaybackCompletionDate() != null || media.getPlayedDuration() > 0) { - episodesStarted++; + if (media.getLastPlayedTime() >= timeFilterFrom + && media.getLastPlayedTime() <= timeFilterTo) { + if (media.getPlayedDuration() != 0) { + feedPlayedTime += media.getPlayedDuration() / 1000; + } else if (includeMarkedAsPlayed && item.isPlayed()) { + feedPlayedTime += media.getDuration() / 1000; + } } - if (item.isPlayed() || media.getPosition() != 0) { - episodesStartedIncludingMarked++; + boolean markedAsStarted = item.isPlayed() || media.getPosition() != 0; + boolean hasStatistics = media.getPlaybackCompletionDate() != null || media.getPlayedDuration() > 0; + if (hasStatistics || (includeMarkedAsPlayed && markedAsStarted)) { + episodesStarted++; } feedTotalTime += media.getDuration() / 1000; @@ -825,13 +830,12 @@ public final class DBReader { episodes++; } - feedTime.add(new StatisticsItem( - feed, feedTotalTime, feedPlayedTime, feedPlayedTimeCountAll, episodes, - episodesStarted, episodesStartedIncludingMarked, totalDownloadSize, episodesDownloadCount)); + result.feedTime.add(new StatisticsItem(feed, feedTotalTime, feedPlayedTime, episodes, + episodesStarted, totalDownloadSize, episodesDownloadCount)); } adapter.close(); - return feedTime; + return result; } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java index 90978d6b8..1bc4997dd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java @@ -12,11 +12,6 @@ public class StatisticsItem { public final long timePlayed; /** - * Simply sums up time of podcasts that are marked as played. - */ - public final long timePlayedCountAll; - - /** * Number of episodes. */ public final long episodes; @@ -27,11 +22,6 @@ public class StatisticsItem { public final long episodesStarted; /** - * All episodes that are marked as played (or have position != 0). - */ - public final long episodesStartedIncludingMarked; - - /** * Simply sums up the size of download podcasts. */ public final long totalDownloadSize; @@ -41,16 +31,14 @@ public class StatisticsItem { */ public final long episodesDownloadCount; - public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll, - long episodes, long episodesStarted, long episodesStartedIncludingMarked, + public StatisticsItem(Feed feed, long time, long timePlayed, + long episodes, long episodesStarted, long totalDownloadSize, long episodesDownloadCount) { this.feed = feed; this.time = time; this.timePlayed = timePlayed; - this.timePlayedCountAll = timePlayedCountAll; this.episodes = episodes; this.episodesStarted = episodesStarted; - this.episodesStartedIncludingMarked = episodesStartedIncludingMarked; this.totalDownloadSize = totalDownloadSize; this.episodesDownloadCount = episodesDownloadCount; } diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 1196c0eee..67dd3b3e4 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -44,17 +44,20 @@ <string name="change_setting">Change</string> <!-- Statistics fragment --> - <string name="total_time_listened_to_podcasts">Total time of episodes played:</string> - <string name="statistics_mode">Statistics mode</string> - <string name="statistics_mode_normal">Calculate duration that was actually played. Playing twice is counted twice, while marking as played is not counted</string> - <string name="statistics_mode_count_all">Sum up all episodes marked as played</string> + <string name="statistics_include_marked">Include duration of episodes that are just marked as played</string> <string name="statistics_speed_not_counted">Notice: Playback speed is never taken into account.</string> + <string name="statistics_from">From</string> + <string name="statistics_to">To</string> + <string name="statistics_today">Today</string> + <string name="statistics_filter_all_time">All time</string> + <string name="statistics_filter_last_year">Last year</string> <string name="statistics_reset_data">Reset statistics data</string> <string name="statistics_reset_data_msg">This will erase the history of duration played for all episodes. Are you sure you want to proceed?</string> - <string name="statistics_counting_since">Since %s,\nyou played</string> + <string name="statistics_counting_range">Played between %1$s and %2$s</string> + <string name="statistics_counting_total">Played in total</string> <!-- Download Statistics fragment --> - <string name="total_size_downloaded_podcasts">Total size of episodes on the device:</string> + <string name="total_size_downloaded_podcasts">Total size of episodes on the device</string> <!-- Main activity --> <string name="drawer_open">Open menu</string> @@ -707,7 +710,6 @@ <string name="auto_download_disabled_globally">Auto download is disabled in the main AntennaPod settings</string> <string name="statistics_time_played">Time played:</string> <string name="statistics_total_duration">Total duration (estimate):</string> - <string name="statistics_duration_played_episodes">Duration of played episodes:</string> <string name="statistics_episodes_on_device">Episodes on the device:</string> <string name="statistics_space_used">Space used:</string> <string name="statistics_episodes_started_total">Episodes started/total:</string> |