Added statistics function

Fixes #1743
This commit is contained in:
ByteHamster 2016-03-21 13:00:12 +01:00
parent 8d2ec19cbe
commit aa56d6822a
10 changed files with 419 additions and 0 deletions

View File

@ -144,6 +144,13 @@
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/> android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
</activity> </activity>
<activity
android:name=".activity.StatisticsActivity"
android:label="@string/statistics_label">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="de.danoeh.antennapod.activity.PreferenceActivity"/>
</activity>
<activity <activity
android:name=".activity.OpmlImportFromPathActivity" android:name=".activity.OpmlImportFromPathActivity"
android:configChanges="keyboardHidden|orientation" android:configChanges="keyboardHidden|orientation"

View File

@ -0,0 +1,107 @@
package de.danoeh.antennapod.activity;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.adapter.StatisticsListAdapter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
import rx.Observable;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;
/**
* Displays the 'statistics' screen
*/
public class StatisticsActivity extends AppCompatActivity
implements AdapterView.OnItemClickListener {
private static final String TAG = StatisticsActivity.class.getSimpleName();
private Subscription subscription;
private TextView totalTimeTextView;
private ListView feedStatisticsList;
private ProgressBar progressBar;
private StatisticsListAdapter listAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
setTheme(UserPreferences.getTheme());
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayShowHomeEnabled(true);
setContentView(R.layout.statistics_activity);
totalTimeTextView = (TextView) findViewById(R.id.total_time);
feedStatisticsList = (ListView) findViewById(R.id.statistics_list);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
listAdapter = new StatisticsListAdapter(this);
feedStatisticsList.setAdapter(listAdapter);
feedStatisticsList.setOnItemClickListener(this);
}
@Override
public void onResume() {
super.onResume();
progressBar.setVisibility(View.VISIBLE);
totalTimeTextView.setVisibility(View.GONE);
feedStatisticsList.setVisibility(View.GONE);
loadStats();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
} else {
return super.onOptionsItemSelected(item);
}
}
private void loadStats() {
if(subscription != null) {
subscription.unsubscribe();
}
subscription = Observable.fromCallable(() -> DBReader.getStatistics())
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
if (result != null) {
totalTimeTextView.setText(Converter
.shortLocalizedDuration(this, result.totalTime));
listAdapter.update(result.feedTime);
progressBar.setVisibility(View.GONE);
totalTimeTextView.setVisibility(View.VISIBLE);
feedStatisticsList.setVisibility(View.VISIBLE);
}
}, error -> {
Log.e(TAG, Log.getStackTraceString(error));
});
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
DBReader.StatisticsItem stats = listAdapter.getItem(position);
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
dialog.setTitle(stats.feed.getTitle());
dialog.setMessage(getString(R.string.statistics_details_dialog,
stats.episodesStarted,
stats.episodes,
Converter.shortLocalizedDuration(this, stats.timePlayed),
Converter.shortLocalizedDuration(this, stats.time)));
dialog.setPositiveButton(android.R.string.ok, null);
dialog.show();
}
}

View File

@ -0,0 +1,97 @@
package de.danoeh.antennapod.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.joanzapata.iconify.widget.IconTextView;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.Converter;
/**
* Adapter for the statistics list
*/
public class StatisticsListAdapter extends BaseAdapter {
private Context context;
List<DBReader.StatisticsItem> feedTime = new ArrayList<>();
public StatisticsListAdapter(Context context) {
this.context = context;
}
@Override
public int getCount() {
return feedTime.size();
}
@Override
public DBReader.StatisticsItem getItem(int position) {
return feedTime.get(position);
}
@Override
public long getItemId(int position) {
return feedTime.get(position).feed.getId();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
StatisticsHolder holder;
Feed feed = feedTime.get(position).feed;
if (convertView == null) {
holder = new StatisticsHolder();
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.statistics_listitem, parent, false);
holder.image = (ImageView) convertView.findViewById(R.id.imgvCover);
holder.title = (TextView) convertView.findViewById(R.id.txtvTitle);
holder.time = (TextView) convertView.findViewById(R.id.txtvTime);
convertView.setTag(holder);
} else {
holder = (StatisticsHolder) convertView.getTag();
}
Glide.with(context)
.load(feed.getImageUri())
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
.fitCenter()
.dontAnimate()
.into(holder.image);
holder.title.setText(feed.getTitle());
holder.time.setText(Converter.shortLocalizedDuration(context,
feedTime.get(position).timePlayed));
return convertView;
}
public void update(List<DBReader.StatisticsItem> feedTime) {
this.feedTime = feedTime;
notifyDataSetChanged();
}
static class StatisticsHolder {
ImageView image;
TextView title;
TextView time;
}
}

View File

@ -49,6 +49,7 @@ import de.danoeh.antennapod.activity.DirectoryChooserActivity;
import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.activity.MainActivity;
import de.danoeh.antennapod.activity.PreferenceActivity; import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.activity.PreferenceActivityGingerbread; import de.danoeh.antennapod.activity.PreferenceActivityGingerbread;
import de.danoeh.antennapod.activity.StatisticsActivity;
import de.danoeh.antennapod.asynctask.OpmlExportWorker; import de.danoeh.antennapod.asynctask.OpmlExportWorker;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences; import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences;
@ -75,6 +76,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess"; public static final String PREF_FLATTR_REVOKE = "prefRevokeAccess";
public static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs"; public static final String PREF_AUTO_FLATTR_PREFS = "prefAutoFlattrPrefs";
public static final String PREF_OPML_EXPORT = "prefOpmlExport"; public static final String PREF_OPML_EXPORT = "prefOpmlExport";
public static final String STATISTICS = "statistics";
public static final String PREF_ABOUT = "prefAbout"; public static final String PREF_ABOUT = "prefAbout";
public static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir"; public static final String PREF_CHOOSE_DATA_DIR = "prefChooseDataDir";
public static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings"; public static final String AUTO_DL_PREF_SCREEN = "prefAutoDownloadSettings";
@ -153,6 +155,12 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
return true; return true;
} }
); );
ui.findPreference(PreferenceController.STATISTICS).setOnPreferenceClickListener(
preference -> {
activity.startActivity(new Intent(activity, StatisticsActivity.class));
return true;
}
);
ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener( ui.findPreference(PreferenceController.PREF_OPML_EXPORT).setOnPreferenceClickListener(
preference -> { preference -> {
new OpmlExportWorker(activity).executeAsync(); new OpmlExportWorker(activity).executeAsync();

View File

@ -0,0 +1,41 @@
<?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="match_parent"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/total_time_listened_to_podcasts"
android:gravity="center_horizontal"/>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/progressBar"
android:layout_gravity="center_horizontal"
style="?android:attr/progressBarStyleLarge"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/total_time"
android:gravity="center_horizontal"
android:textSize="45sp"/>
<ListView
android:id="@+id/statistics_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="singleChoice"
android:clipToPadding="false"
android:divider="@android:color/transparent"
android:dividerHeight="0dp"
android:paddingBottom="@dimen/list_vertical_padding"
android:paddingTop="@dimen/list_vertical_padding"
android:scrollbarStyle="outsideOverlay" />
</LinearLayout>

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="@dimen/listitem_iconwithtext_height"
android:paddingRight="@dimen/listitem_threeline_verticalpadding"
tools:background="@android:color/darker_gray">
<ImageView
android:id="@+id/imgvCover"
android:contentDescription="@string/cover_label"
android:layout_width="@dimen/thumbnail_length_navlist"
android:layout_height="@dimen/thumbnail_length_navlist"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:adjustViewBounds="true"
android:cropToPadding="true"
android:scaleType="centerCrop"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:layout_marginLeft="@dimen/listitem_icon_leftpadding"
tools:src="@drawable/ic_stat_antenna_default"
tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/txtvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/list_vertical_padding"
android:lines="1"
android:textColor="?android:attr/textColorTertiary"
android:textSize="@dimen/text_size_navdrawer"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
tools:text="23"
tools:background="@android:color/holo_green_dark"/>
<TextView
android:id="@+id/txtvTitle"
android:lines="1"
android:ellipsize="end"
android:singleLine="true"
android:layout_centerVertical="true"
android:textColor="?android:attr/textColorPrimary"
android:textSize="@dimen/text_size_navdrawer"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/listitem_iconwithtext_textleftpadding"
android:layout_toRightOf="@id/imgvCover"
android:layout_toLeftOf="@id/txtvTime"
android:layout_alignWithParentIfMissing="true"
tools:text="Navigation feed item title"
tools:background="@android:color/holo_green_dark"/>
</RelativeLayout>

View File

@ -268,6 +268,9 @@
<Preference <Preference
android:key="prefOpmlExport" android:key="prefOpmlExport"
android:title="@string/opml_export_label"/> android:title="@string/opml_export_label"/>
<Preference
android:key="statistics"
android:title="@string/statistics_label"/>
<Preference <Preference
android:key="prefAbout" android:key="prefAbout"
android:title="@string/about_pref"/> android:title="@string/about_pref"/>

View File

@ -917,6 +917,88 @@ public final class DBReader {
return media; return media;
} }
/**
* Searches the DB for statistics
*
* @return The StatisticsInfo object
*/
public static StatisticsData getStatistics() {
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
long totalTime = 0;
List<StatisticsItem> feedTime = new ArrayList<>();
List<Feed> feeds = getFeedList();
for (Feed feed : feeds) {
long feedPlayedTime = 0;
long feedTotalTime = 0;
long episodes = 0;
long episodesStarted = 0;
List<FeedItem> items = getFeed(feed.getId()).getItems();
for(FeedItem item : items) {
FeedMedia media = item.getMedia();
if(media == null) {
continue;
}
if(item.isPlayed()) {
feedPlayedTime += media.getDuration() / 1000;
} else {
feedPlayedTime += media.getPosition() / 1000;
}
if(item.isPlayed() || media.getPosition() != 0) {
episodesStarted++;
}
feedTotalTime += media.getDuration() / 1000;
episodes++;
}
feedTime.add(new StatisticsItem(
feed, feedTotalTime, feedPlayedTime, episodes, episodesStarted));
totalTime += feedPlayedTime;
}
Collections.sort(feedTime, (item1, item2) -> {
if(item1.timePlayed > item2.timePlayed) {
return -1;
} else if(item1.timePlayed < item2.timePlayed) {
return 1;
} else {
return 0;
}
});
adapter.close();
return new StatisticsData(totalTime, feedTime);
}
public static class StatisticsData {
public long totalTime;
public List<StatisticsItem> feedTime;
public StatisticsData(long totalTime, List<StatisticsItem> feedTime) {
this.totalTime = totalTime;
this.feedTime = feedTime;
}
}
public static class StatisticsItem {
public Feed feed;
public long time;
public long timePlayed;
public long episodes;
public long episodesStarted;
public StatisticsItem(Feed feed, long time, long timePlayed,
long episodes, long episodesStarted) {
this.feed = feed;
this.time = time;
this.timePlayed = timePlayed;
this.episodes = episodes;
this.episodesStarted = episodesStarted;
}
}
/** /**
* Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems. * Returns the flattr queue as a List of FlattrThings. The list consists of Feeds and FeedItems.
* *

View File

@ -3,6 +3,8 @@ package de.danoeh.antennapod.core.util;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import java.util.Locale;
import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.R;
/** Provides methods for converting various units. */ /** Provides methods for converting various units. */
@ -119,6 +121,16 @@ public final class Converter {
return result; return result;
} }
/**
* Converts seconds to a localized representation
* @param time The time in seconds
* @return "HH:MM hours"
*/
public static String shortLocalizedDuration(Context context, long time) {
float hours = (float) time / 3600f;
return String.format(Locale.getDefault(), "%.1f ", hours) + context.getString(R.string.time_hours);
}
/** /**
* Converts the volume as read as the progress from a SeekBar scaled to 100 and as saved in * Converts the volume as read as the progress from a SeekBar scaled to 100 and as saved in
* UserPreferences to the format taken by setVolume methods. * UserPreferences to the format taken by setVolume methods.

View File

@ -6,6 +6,7 @@
<!-- Activitiy and fragment titles --> <!-- Activitiy and fragment titles -->
<string name="app_name">AntennaPod</string> <string name="app_name">AntennaPod</string>
<string name="feeds_label">Feeds</string> <string name="feeds_label">Feeds</string>
<string name="statistics_label">Statistics</string>
<string name="add_feed_label">Add Podcast</string> <string name="add_feed_label">Add Podcast</string>
<string name="podcasts_label">PODCASTS</string> <string name="podcasts_label">PODCASTS</string>
<string name="episodes_label">Episodes</string> <string name="episodes_label">Episodes</string>
@ -33,6 +34,10 @@
<string name="recently_published_episodes_label">Recently published</string> <string name="recently_published_episodes_label">Recently published</string>
<string name="episode_filter_label">Show only new Episodes</string> <string name="episode_filter_label">Show only new Episodes</string>
<!-- Statistics fragment -->
<string name="total_time_listened_to_podcasts">Total time of podcasts played:</string>
<string name="statistics_details_dialog">%1$d out of %2$d episodes started.\n\nPlayed %3$s out of %4$s.</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>