Merge pull request #3523 from wseemann/develop

Show storage size of downloaded episodes
This commit is contained in:
H. Lehmann 2020-01-12 09:52:04 +01:00 committed by GitHub
commit 2f0c627b15
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 542 additions and 216 deletions

View File

@ -0,0 +1,45 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
/**
* Adapter for the download statistics list
*/
public class DownloadStatisticsListAdapter extends StatisticsListAdapter {
public DownloadStatisticsListAdapter(Context context) {
super(context);
}
@Override
int getHeaderCaptionResourceId() {
return R.string.total_size_downloaded_podcasts;
}
@Override
void onBindHeaderViewHolder(HeaderHolder holder) {
long totalDownloadSize = 0;
for (DBReader.StatisticsItem item: statisticsData.feeds) {
totalDownloadSize = totalDownloadSize + item.totalDownloadSize;
}
holder.totalTime.setText(Converter.byteToString(totalDownloadSize));
float[] dataValues = new float[statisticsData.feeds.size()];
for (int i = 0; i < statisticsData.feeds.size(); i++) {
DBReader.StatisticsItem item = statisticsData.feeds.get(i);
dataValues[i] = item.totalDownloadSize;
}
holder.pieChart.setData(dataValues);
}
@Override
void onBindFeedViewHolder(StatisticsHolder holder, int position) {
DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1);
holder.value.setText(Converter.byteToString(statsItem.totalDownloadSize));
}
}

View File

@ -0,0 +1,61 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import androidx.appcompat.app.AlertDialog;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
/**
* Adapter for the playback statistics list
*/
public class PlaybackStatisticsListAdapter extends StatisticsListAdapter {
boolean countAll = true;
public PlaybackStatisticsListAdapter(Context context) {
super(context);
}
public void setCountAll(boolean countAll) {
this.countAll = countAll;
}
@Override
int getHeaderCaptionResourceId() {
return R.string.total_time_listened_to_podcasts;
}
@Override
void onBindHeaderViewHolder(HeaderHolder holder) {
long time = countAll ? statisticsData.totalTimeCountAll : statisticsData.totalTime;
holder.totalTime.setText(Converter.shortLocalizedDuration(context, time));
float[] dataValues = new float[statisticsData.feeds.size()];
for (int i = 0; i < statisticsData.feeds.size(); i++) {
DBReader.StatisticsItem item = statisticsData.feeds.get(i);
dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed;
}
holder.pieChart.setData(dataValues);
}
@Override
void onBindFeedViewHolder(StatisticsHolder holder, int position) {
DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1);
long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed;
holder.value.setText(Converter.shortLocalizedDuration(context, time));
holder.itemView.setOnClickListener(v -> {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(statsItem.feed.getTitle());
dialog.setMessage(context.getString(R.string.statistics_details_dialog,
countAll ? statsItem.episodesStartedIncludingMarked : statsItem.episodesStarted,
statsItem.episodes, Converter.shortLocalizedDuration(context,
countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed),
Converter.shortLocalizedDuration(context, statsItem.time)));
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
});
}
}

View File

@ -2,49 +2,44 @@ package de.danoeh.antennapod.adapter;
import android.content.Context; import android.content.Context;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import com.bumptech.glide.Glide; import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions; import com.bumptech.glide.request.RequestOptions;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.view.PieChartView; import de.danoeh.antennapod.view.PieChartView;
/** /**
* Adapter for the statistics list * Parent Adapter for the playback and download statistics list
*/ */
public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public abstract class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final int TYPE_HEADER = 0; private static final int TYPE_HEADER = 0;
private static final int TYPE_FEED = 1; private static final int TYPE_FEED = 1;
private final Context context; final Context context;
private DBReader.StatisticsData statisticsData; DBReader.StatisticsData statisticsData;
private boolean countAll = true;
public StatisticsListAdapter(Context context) { StatisticsListAdapter(Context context) {
this.context = context; this.context = context;
} }
public void setCountAll(boolean countAll) {
this.countAll = countAll;
}
@Override @Override
public int getItemCount() { public int getItemCount() {
return statisticsData.feedTime.size() + 1; return statisticsData.feeds.size() + 1;
} }
public DBReader.StatisticsItem getItem(int position) { public DBReader.StatisticsItem getItem(int position) {
if (position == 0) { if (position == 0) {
return null; return null;
} }
return statisticsData.feedTime.get(position - 1); return statisticsData.feeds.get(position - 1);
} }
@Override @Override
@ -57,7 +52,10 @@ public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.Vie
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(context); LayoutInflater inflater = LayoutInflater.from(context);
if (viewType == TYPE_HEADER) { if (viewType == TYPE_HEADER) {
return new HeaderHolder(inflater.inflate(R.layout.statistics_listitem_total_time, parent, false)); View view = inflater.inflate(R.layout.statistics_listitem_total, parent, false);
TextView totalText = view.findViewById(R.id.total_description);
totalText.setText(getHeaderCaptionResourceId());
return new HeaderHolder(view);
} }
return new StatisticsHolder(inflater.inflate(R.layout.statistics_listitem, parent, false)); return new StatisticsHolder(inflater.inflate(R.layout.statistics_listitem, parent, false));
} }
@ -65,18 +63,10 @@ public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.Vie
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder h, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder h, int position) {
if (getItemViewType(position) == TYPE_HEADER) { if (getItemViewType(position) == TYPE_HEADER) {
HeaderHolder holder = (HeaderHolder) h; onBindHeaderViewHolder((HeaderHolder) h);
long time = countAll ? statisticsData.totalTimeCountAll : statisticsData.totalTime;
holder.totalTime.setText(Converter.shortLocalizedDuration(context, time));
float[] dataValues = new float[statisticsData.feedTime.size()];
for (int i = 0; i < statisticsData.feedTime.size(); i++) {
DBReader.StatisticsItem item = statisticsData.feedTime.get(i);
dataValues[i] = countAll ? item.timePlayedCountAll : item.timePlayed;
}
holder.pieChart.setData(dataValues);
} else { } else {
StatisticsHolder holder = (StatisticsHolder) h; StatisticsHolder holder = (StatisticsHolder) h;
DBReader.StatisticsItem statsItem = statisticsData.feedTime.get(position - 1); DBReader.StatisticsItem statsItem = statisticsData.feeds.get(position - 1);
Glide.with(context) Glide.with(context)
.load(statsItem.feed.getImageLocation()) .load(statsItem.feed.getImageLocation())
.apply(new RequestOptions() .apply(new RequestOptions()
@ -88,20 +78,7 @@ public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.Vie
.into(holder.image); .into(holder.image);
holder.title.setText(statsItem.feed.getTitle()); holder.title.setText(statsItem.feed.getTitle());
long time = countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed; onBindFeedViewHolder(holder, position);
holder.time.setText(Converter.shortLocalizedDuration(context, time));
holder.itemView.setOnClickListener(v -> {
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
dialog.setTitle(statsItem.feed.getTitle());
dialog.setMessage(context.getString(R.string.statistics_details_dialog,
countAll ? statsItem.episodesStartedIncludingMarked : statsItem.episodesStarted,
statsItem.episodes, Converter.shortLocalizedDuration(context,
countAll ? statsItem.timePlayedCountAll : statsItem.timePlayed),
Converter.shortLocalizedDuration(context, statsItem.time)));
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
});
} }
} }
@ -124,14 +101,19 @@ public class StatisticsListAdapter extends RecyclerView.Adapter<RecyclerView.Vie
static class StatisticsHolder extends RecyclerView.ViewHolder { static class StatisticsHolder extends RecyclerView.ViewHolder {
ImageView image; ImageView image;
TextView title; TextView title;
TextView time; TextView value;
StatisticsHolder(View itemView) { StatisticsHolder(View itemView) {
super(itemView); super(itemView);
image = itemView.findViewById(R.id.imgvCover); image = itemView.findViewById(R.id.imgvCover);
title = itemView.findViewById(R.id.txtvTitle); title = itemView.findViewById(R.id.txtvTitle);
time = itemView.findViewById(R.id.txtvTime); value = itemView.findViewById(R.id.txtvValue);
} }
} }
abstract int getHeaderCaptionResourceId();
abstract void onBindHeaderViewHolder(HeaderHolder holder);
abstract void onBindFeedViewHolder(StatisticsHolder holder, int position);
} }

View File

@ -0,0 +1,87 @@
package de.danoeh.antennapod.fragment.preferences;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ProgressBar;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
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.util.comparator.CompareCompat;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import java.util.Collections;
/**
* Displays the 'download statistics' screen
*/
public class DownloadStatisticsFragment extends Fragment {
private static final String TAG = DownloadStatisticsFragment.class.getSimpleName();
private Disposable disposable;
private RecyclerView downloadStatisticsList;
private ProgressBar progressBar;
private DownloadStatisticsListAdapter listAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
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);
listAdapter = new DownloadStatisticsListAdapter(getContext());
downloadStatisticsList.setLayoutManager(new LinearLayoutManager(getContext()));
downloadStatisticsList.setAdapter(listAdapter);
return root;
}
@Override
public void onStart() {
super.onStart();
refreshDownloadStatistics();
}
private void refreshDownloadStatistics() {
progressBar.setVisibility(View.VISIBLE);
downloadStatisticsList.setVisibility(View.GONE);
loadStatistics();
}
private void loadStatistics() {
if (disposable != null) {
disposable.dispose();
}
disposable =
Observable.fromCallable(() -> {
DBReader.StatisticsData statisticsData = DBReader.getStatistics();
Collections.sort(statisticsData.feeds, (item1, item2) ->
CompareCompat.compareLong(item1.totalDownloadSize, item2.totalDownloadSize));
return statisticsData;
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
listAdapter.update(result);
progressBar.setVisibility(View.GONE);
downloadStatisticsList.setVisibility(View.VISIBLE);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
}

View File

@ -0,0 +1,194 @@
package de.danoeh.antennapod.fragment.preferences;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
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.ProgressBar;
import android.widget.RadioButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.adapter.PlaybackStatisticsListAdapter;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.util.comparator.CompareCompat;
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.util.Collections;
/**
* Displays the 'playback statistics' screen
*/
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 Disposable disposable;
private RecyclerView feedStatisticsList;
private ProgressBar progressBar;
private PlaybackStatisticsListAdapter listAdapter;
private boolean countAll = false;
private SharedPreferences prefs;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
prefs = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
countAll = prefs.getBoolean(PREF_COUNT_ALL, false);
setHasOptionsMenu(true);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.statistics_activity, container, false);
feedStatisticsList = root.findViewById(R.id.statistics_list);
progressBar = root.findViewById(R.id.progressBar);
listAdapter = new PlaybackStatisticsListAdapter(getContext());
listAdapter.setCountAll(countAll);
feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext()));
feedStatisticsList.setAdapter(listAdapter);
return root;
}
@Override
public void onStart() {
super.onStart();
((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.statistics_label);
refreshStatistics();
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (disposable != null) {
disposable.dispose();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.statistics, menu);
menu.findItem(R.id.statistics_reset).setEnabled(!countAll);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.statistics_mode) {
selectStatisticsMode();
return true;
}
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);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setView(contentView);
builder.setTitle(R.string.statistics_mode);
if (countAll) {
((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).setChecked(true);
} else {
((RadioButton) contentView.findViewById(R.id.statistics_mode_normal)).setChecked(true);
}
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();
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 void doResetStatistics() {
progressBar.setVisibility(View.VISIBLE);
feedStatisticsList.setVisibility(View.GONE);
if (disposable != null) {
disposable.dispose();
}
disposable = Completable.fromFuture(DBWriter.resetStatistics())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(this::refreshStatistics, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
private void refreshStatistics() {
progressBar.setVisibility(View.VISIBLE);
feedStatisticsList.setVisibility(View.GONE);
loadStatistics();
}
private void loadStatistics() {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(this::fetchStatistics)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
listAdapter.update(result);
progressBar.setVisibility(View.GONE);
feedStatisticsList.setVisibility(View.VISIBLE);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
}
private DBReader.StatisticsData fetchStatistics() {
DBReader.StatisticsData statisticsData = DBReader.getStatistics();
if (countAll) {
Collections.sort(statisticsData.feeds, (item1, item2) ->
CompareCompat.compareLong(item1.timePlayedCountAll, item2.timePlayedCountAll));
} else {
Collections.sort(statisticsData.feeds, (item1, item2) ->
CompareCompat.compareLong(item1.timePlayed, item2.timePlayed));
}
return statisticsData;
}
}

View File

@ -1,180 +1,96 @@
package de.danoeh.antennapod.fragment.preferences; package de.danoeh.antennapod.fragment.preferences;
import android.content.Context; import android.content.res.Resources;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ProgressBar;
import android.widget.RadioButton;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.fragment.app.FragmentPagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.tabs.TabLayout;
import de.danoeh.antennapod.R; import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.adapter.StatisticsListAdapter;
import de.danoeh.antennapod.core.dialog.ConfirmationDialog;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DBWriter;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
/** /**
* Displays the 'statistics' screen * Displays the 'statistics' screen
*/ */
public class StatisticsFragment extends Fragment { public class StatisticsFragment extends Fragment {
private static final String TAG = StatisticsFragment.class.getSimpleName();
private static final String PREF_NAME = "StatisticsActivityPrefs";
private static final String PREF_COUNT_ALL = "countAll";
private Disposable disposable; public static final String TAG = "StatisticsFragment";
private RecyclerView feedStatisticsList;
private ProgressBar progressBar; private static final int POS_LISTENED_HOURS = 0;
private StatisticsListAdapter listAdapter; private static final int POS_SPACE_TAKEN = 1;
private boolean countAll = false; private static final int TOTAL_COUNT = 2;
private SharedPreferences prefs;
private TabLayout tabLayout;
private ViewPager viewPager;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
prefs = getContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
countAll = prefs.getBoolean(PREF_COUNT_ALL, false);
setHasOptionsMenu(true);
} }
@Nullable public View onCreateView(LayoutInflater inflater, ViewGroup container,
@Override Bundle savedInstanceState) {
public View onCreateView( super.onCreateView(inflater, container, savedInstanceState);
@NonNull LayoutInflater inflater, setHasOptionsMenu(true);
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.statistics_activity, container, false); View rootView = inflater.inflate(R.layout.pager_fragment, container, false);
feedStatisticsList = root.findViewById(R.id.statistics_list); viewPager = rootView.findViewById(R.id.viewpager);
progressBar = root.findViewById(R.id.progressBar); viewPager.setAdapter(new StatisticsPagerAdapter(getChildFragmentManager(), getResources()));
listAdapter = new StatisticsListAdapter(getContext());
listAdapter.setCountAll(countAll); // Give the TabLayout the ViewPager
feedStatisticsList.setLayoutManager(new LinearLayoutManager(getContext())); tabLayout = rootView.findViewById(R.id.sliding_tabs);
feedStatisticsList.setAdapter(listAdapter); tabLayout.setupWithViewPager(viewPager);
return root;
return rootView;
} }
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.statistics_label); ((PreferenceActivity) getActivity()).getSupportActionBar().setTitle(R.string.statistics_label);
refreshStatistics(); }
public static class StatisticsPagerAdapter extends FragmentPagerAdapter {
private final Resources resources;
public StatisticsPagerAdapter(FragmentManager fm, Resources resources) {
super(fm);
this.resources = resources;
} }
@Override @Override
public void onDestroyView() { public Fragment getItem(int position) {
super.onDestroyView(); if (position == 0) {
if (disposable != null) { return new PlaybackStatisticsFragment();
disposable.dispose();
}
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.statistics, menu);
menu.findItem(R.id.statistics_reset).setEnabled(!countAll);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.statistics_mode) {
selectStatisticsMode();
return true;
}
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);
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setView(contentView);
builder.setTitle(R.string.statistics_mode);
if (countAll) {
((RadioButton) contentView.findViewById(R.id.statistics_mode_count_all)).setChecked(true);
} else { } else {
((RadioButton) contentView.findViewById(R.id.statistics_mode_normal)).setChecked(true); return new DownloadStatisticsFragment();
} }
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();
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 @Override
public void onConfirmButtonPressed(DialogInterface dialog) { public int getCount() {
dialog.dismiss(); return TOTAL_COUNT;
doResetStatistics();
}
};
conDialog.createNewDialog().show();
}
} }
private void doResetStatistics() { @Override
progressBar.setVisibility(View.VISIBLE); public CharSequence getPageTitle(int position) {
feedStatisticsList.setVisibility(View.GONE); switch (position) {
if (disposable != null) { case POS_LISTENED_HOURS:
disposable.dispose(); return resources.getString(R.string.playback_statistics_label);
} case POS_SPACE_TAKEN:
return resources.getString(R.string.download_statistics_label);
disposable = Completable.fromFuture(DBWriter.resetStatistics()) default:
.subscribeOn(Schedulers.io()) return super.getPageTitle(position);
.observeOn(AndroidSchedulers.mainThread()) }
.subscribe(this::refreshStatistics, error -> Log.e(TAG, Log.getStackTraceString(error))); }
}
private void refreshStatistics() {
progressBar.setVisibility(View.VISIBLE);
feedStatisticsList.setVisibility(View.GONE);
loadStatistics();
}
private void loadStatistics() {
if (disposable != null) {
disposable.dispose();
}
disposable = Observable.fromCallable(() -> DBReader.getStatistics(countAll))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
listAdapter.update(result);
progressBar.setVisibility(View.GONE);
feedStatisticsList.setVisibility(View.VISIBLE);
}, error -> Log.e(TAG, Log.getStackTraceString(error)));
} }
} }

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.tabs.TabLayout
android:id="@+id/sliding_tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:tabGravity="fill"
app:tabMode="fixed" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="1" />
</LinearLayout>

View File

@ -41,7 +41,7 @@
tools:text="Feed title"/> tools:text="Feed title"/>
<TextView <TextView
android:id="@+id/txtvTime" android:id="@+id/txtvValue"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:lines="1" android:lines="1"

View File

@ -15,9 +15,8 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:id="@+id/total_time_description" android:id="@+id/total_description"
android:textSize="14sp" android:textSize="14sp"
android:text="@string/total_time_listened_to_podcasts"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_above="@+id/total_time"/> android:layout_above="@+id/total_time"/>

View File

@ -868,12 +868,10 @@ public final class DBReader {
/** /**
* Searches the DB for statistics * Searches the DB for statistics
* *
* @param sortByCountAll If true, the statistic items will be sorted according to the
* countAll calculation time
* @return The StatisticsInfo object * @return The StatisticsInfo object
*/ */
@NonNull @NonNull
public static StatisticsData getStatistics(boolean sortByCountAll) { public static StatisticsData getStatistics() {
PodDBAdapter adapter = PodDBAdapter.getInstance(); PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open(); adapter.open();
@ -889,6 +887,7 @@ public final class DBReader {
long episodes = 0; long episodes = 0;
long episodesStarted = 0; long episodesStarted = 0;
long episodesStartedIncludingMarked = 0; long episodesStartedIncludingMarked = 0;
long totalDownloadSize = 0;
List<FeedItem> items = getFeed(feed.getId()).getItems(); List<FeedItem> items = getFeed(feed.getId()).getItems();
for (FeedItem item : items) { for (FeedItem item : items) {
FeedMedia media = item.getMedia(); FeedMedia media = item.getMedia();
@ -913,43 +912,24 @@ public final class DBReader {
} }
feedTotalTime += media.getDuration() / 1000; feedTotalTime += media.getDuration() / 1000;
if (media.isDownloaded()) {
totalDownloadSize = totalDownloadSize + media.getSize();
}
episodes++; episodes++;
} }
feedTime.add(new StatisticsItem( feedTime.add(new StatisticsItem(
feed, feedTotalTime, feedPlayedTime, feedPlayedTimeCountAll, episodes, feed, feedTotalTime, feedPlayedTime, feedPlayedTimeCountAll, episodes,
episodesStarted, episodesStartedIncludingMarked)); episodesStarted, episodesStartedIncludingMarked, totalDownloadSize));
totalTime += feedPlayedTime; totalTime += feedPlayedTime;
totalTimeCountAll += feedPlayedTimeCountAll; totalTimeCountAll += feedPlayedTimeCountAll;
} }
if (sortByCountAll) {
Collections.sort(feedTime, (item1, item2) ->
compareLong(item1.timePlayedCountAll, item2.timePlayedCountAll));
} else {
Collections.sort(feedTime, (item1, item2) ->
compareLong(item1.timePlayed, item2.timePlayed));
}
adapter.close(); adapter.close();
return new StatisticsData(totalTime, totalTimeCountAll, feedTime); return new StatisticsData(totalTime, totalTimeCountAll, feedTime);
} }
/**
* Compares two {@code long} values. Long.compare() is not available before API 19
*
* @return 0 if long1 = long2, less than 0 if long1 &lt; long2,
* and greater than 0 if long1 &gt; long2.
*/
private static int compareLong(long long1, long long2) {
if (long1 > long2) {
return -1;
} else if (long1 < long2) {
return 1;
} else {
return 0;
}
}
public static class StatisticsData { public static class StatisticsData {
/** /**
* Simply sums up time of podcasts that are marked as played * Simply sums up time of podcasts that are marked as played
@ -961,12 +941,12 @@ public final class DBReader {
*/ */
public final long totalTime; public final long totalTime;
public final List<StatisticsItem> feedTime; public final List<StatisticsItem> feeds;
public StatisticsData(long totalTime, long totalTimeCountAll, List<StatisticsItem> feedTime) { public StatisticsData(long totalTime, long totalTimeCountAll, List<StatisticsItem> feeds) {
this.totalTime = totalTime; this.totalTime = totalTime;
this.totalTimeCountAll = totalTimeCountAll; this.totalTimeCountAll = totalTimeCountAll;
this.feedTime = feedTime; this.feeds = feeds;
} }
} }
@ -991,9 +971,14 @@ public final class DBReader {
* All episodes that are marked as played (or have position != 0) * All episodes that are marked as played (or have position != 0)
*/ */
public final long episodesStartedIncludingMarked; public final long episodesStartedIncludingMarked;
/**
* Simply sums up the size of download podcasts
*/
public final long totalDownloadSize;
public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll, public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll,
long episodes, long episodesStarted, long episodesStartedIncludingMarked) { long episodes, long episodesStarted, long episodesStartedIncludingMarked,
long totalDownloadSize) {
this.feed = feed; this.feed = feed;
this.time = time; this.time = time;
this.timePlayed = timePlayed; this.timePlayed = timePlayed;
@ -1001,6 +986,7 @@ public final class DBReader {
this.episodes = episodes; this.episodes = episodes;
this.episodesStarted = episodesStarted; this.episodesStarted = episodesStarted;
this.episodesStartedIncludingMarked = episodesStartedIncludingMarked; this.episodesStartedIncludingMarked = episodesStartedIncludingMarked;
this.totalDownloadSize = totalDownloadSize;
} }
} }

View File

@ -0,0 +1,29 @@
package de.danoeh.antennapod.core.util.comparator;
/**
* Some compare() methods are not available before API 19.
* This class provides fallbacks
*/
public class CompareCompat {
private CompareCompat() {
// Must not be instantiated
}
/**
* Compares two {@code long} values. Long.compare() is not available before API 19
*
* @return 0 if long1 = long2, less than 0 if long1 &lt; long2,
* and greater than 0 if long1 &gt; long2.
*/
public static int compareLong(long long1, long long2) {
//noinspection UseCompareMethod
if (long1 > long2) {
return -1;
} else if (long1 < long2) {
return 1;
} else {
return 0;
}
}
}

View File

@ -29,6 +29,8 @@
<string name="gpodnet_auth_label">gpodder.net Login</string> <string name="gpodnet_auth_label">gpodder.net Login</string>
<string name="episode_cache_full_title">Episode cache full</string> <string name="episode_cache_full_title">Episode cache full</string>
<string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string> <string name="episode_cache_full_message">The episode cache limit has been reached. You can increase the cache size in the Settings.</string>
<string name="playback_statistics_label">Playback</string>
<string name="download_statistics_label">Downloads</string>
<!-- Statistics fragment --> <!-- Statistics fragment -->
<string name="total_time_listened_to_podcasts">Total time of podcasts played:</string> <string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
@ -40,6 +42,9 @@
<string name="statistics_reset_data">Reset statistics data</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_reset_data_msg">This will erase the history of duration played for all episodes. Are you sure you want to proceed?</string>
<!-- Download Statistics fragment -->
<string name="total_size_downloaded_podcasts">Total size of downloaded podcasts:</string>
<!-- Main activity --> <!-- Main activity -->
<string name="drawer_open">Open menu</string> <string name="drawer_open">Open menu</string>
<string name="drawer_close">Close menu</string> <string name="drawer_close">Close menu</string>