mirror of
https://github.com/ankidroid/Anki-Android.git
synced 2024-09-20 12:02:16 +02:00
more overview statistics
This commit is contained in:
parent
219a140bd5
commit
b89ddbc4e0
@ -144,7 +144,7 @@ public class DeckPicker extends NavigationDrawerActivity implements
|
||||
private DeckAdapter mDeckListAdapter;
|
||||
private FloatingActionsMenu mActionsMenu; // Note this will be null below SDK 14
|
||||
|
||||
private TextView mTodayTextView;
|
||||
private TextView mReviewSummaryTextView;
|
||||
|
||||
private BroadcastReceiver mUnmountReceiver = null;
|
||||
|
||||
@ -404,7 +404,7 @@ public class DeckPicker extends NavigationDrawerActivity implements
|
||||
});
|
||||
}
|
||||
|
||||
mTodayTextView = (TextView) findViewById(R.id.today_stats_text_view);
|
||||
mReviewSummaryTextView = (TextView) findViewById(R.id.today_stats_text_view);
|
||||
|
||||
// Hide the fragment until the counts have been loaded so that the Toolbar fills the whole screen on tablets
|
||||
if (mFragmented) {
|
||||
@ -1849,7 +1849,7 @@ public class DeckPicker extends NavigationDrawerActivity implements
|
||||
}
|
||||
|
||||
// Update the mini statistics bar as well
|
||||
AnkiStatsTaskHandler.createSmallTodayOverview(getCol(), mTodayTextView);
|
||||
AnkiStatsTaskHandler.createReviewSummaryStatistics(getCol(), mReviewSummaryTextView);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -324,7 +324,7 @@ public class Statistics extends NavigationDrawerActivity implements
|
||||
|
||||
switch (position) {
|
||||
case TODAYS_STATS_TAB_POSITION:
|
||||
return getString(R.string.stats_today).toUpperCase(l);
|
||||
return getString(R.string.stats_overview).toUpperCase(l);
|
||||
case FORECAST_TAB_POSITION:
|
||||
return getString(R.string.stats_forecast).toUpperCase(l);
|
||||
case REVIEW_COUNT_TAB_POSITION:
|
||||
|
@ -72,10 +72,10 @@ public class AnkiStatsTaskHandler {
|
||||
createChartTask.execute(views);
|
||||
return createChartTask;
|
||||
}
|
||||
public static CreateSmallTodayOverview createSmallTodayOverview(Collection col, TextView view){
|
||||
CreateSmallTodayOverview createSmallTodayOverview = new CreateSmallTodayOverview();
|
||||
createSmallTodayOverview.execute(col, view);
|
||||
return createSmallTodayOverview;
|
||||
public static DeckPreviewStatistics createReviewSummaryStatistics(Collection col, TextView view){
|
||||
DeckPreviewStatistics deckPreviewStatistics = new DeckPreviewStatistics();
|
||||
deckPreviewStatistics.execute(col, view);
|
||||
return deckPreviewStatistics;
|
||||
}
|
||||
|
||||
public static CreateFirstStatisticChooserTask createFirstStatisticChooserTask(Collection col, ViewPager viewPager){
|
||||
@ -162,8 +162,8 @@ public class AnkiStatsTaskHandler {
|
||||
mWebView = (WebView) params[0];
|
||||
mProgressBar = (ProgressBar) params[1];
|
||||
String html = "";
|
||||
InfoStatsBuilder infoStatsBuilder = new InfoStatsBuilder(mWebView, mCollectionData, mIsWholeCollection);
|
||||
html = infoStatsBuilder.createInfoHtmlString();
|
||||
OverviewStatsBuilder overviewStatsBuilder = new OverviewStatsBuilder(mWebView, mCollectionData, mIsWholeCollection, mStatType);
|
||||
html = overviewStatsBuilder.createInfoHtmlString();
|
||||
return html;
|
||||
}finally {
|
||||
sLock.unlock();
|
||||
@ -195,12 +195,12 @@ public class AnkiStatsTaskHandler {
|
||||
|
||||
}
|
||||
|
||||
private static class CreateSmallTodayOverview extends AsyncTask<Object, Void, String>{
|
||||
private static class DeckPreviewStatistics extends AsyncTask<Object, Void, String>{
|
||||
private TextView mTextView;
|
||||
|
||||
private boolean mIsRunning = false;
|
||||
|
||||
public CreateSmallTodayOverview(){
|
||||
public DeckPreviewStatistics(){
|
||||
super();
|
||||
mIsRunning = true;
|
||||
}
|
||||
@ -213,10 +213,10 @@ public class AnkiStatsTaskHandler {
|
||||
try {
|
||||
Collection collection = (Collection) params[0];
|
||||
if (!mIsRunning || collection == null || collection.getDb() == null) {
|
||||
Timber.d("quiting CreateSmallTodayOverview before execution");
|
||||
Timber.d("quiting DeckPreviewStatistics before execution");
|
||||
return null;
|
||||
} else
|
||||
Timber.d("starting CreateSmallTodayOverview" );
|
||||
Timber.d("starting DeckPreviewStatistics" );
|
||||
mTextView = (TextView) params[1];
|
||||
|
||||
//eventually put this in Stats (in desktop it is not though)
|
||||
@ -224,7 +224,7 @@ public class AnkiStatsTaskHandler {
|
||||
int minutes;
|
||||
Cursor cur = null;
|
||||
String query = "select count(), sum(time)/1000 from revlog where id > " + ((collection.getSched().getDayCutoff()-86400)*1000);
|
||||
Timber.d("CreateSmallTodayOverview query: " + query);
|
||||
Timber.d("DeckPreviewStatistics query: " + query);
|
||||
|
||||
try {
|
||||
cur = collection.getDb()
|
||||
@ -291,7 +291,7 @@ public class AnkiStatsTaskHandler {
|
||||
int cards;
|
||||
Cursor cur = null;
|
||||
String query = "select count() from revlog where id > " + ((collection.getSched().getDayCutoff()-86400)*1000);
|
||||
Timber.d("CreateSmallTodayOverview query: " + query);
|
||||
Timber.d("DeckPreviewStatistics query: " + query);
|
||||
|
||||
try {
|
||||
cur = collection.getDb()
|
||||
|
@ -17,14 +17,20 @@ package com.ichi2.anki.stats;
|
||||
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import com.ichi2.anki.R;
|
||||
import com.ichi2.libanki.Collection;
|
||||
import com.ichi2.libanki.Stats;
|
||||
import com.ichi2.libanki.Utils;
|
||||
import com.ichi2.themes.Themes;
|
||||
|
||||
public class InfoStatsBuilder {
|
||||
import java.util.ArrayList;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class OverviewStatsBuilder {
|
||||
private final int CARDS_INDEX = 0;
|
||||
private final int THETIME_INDEX = 1;
|
||||
private final int FAILED_INDEX = 2;
|
||||
@ -37,12 +43,30 @@ public class InfoStatsBuilder {
|
||||
;
|
||||
private final WebView mWebView; //for resources access
|
||||
private final Collection mCollectionData;
|
||||
private final boolean mIsWholeCollection;
|
||||
private final boolean mWholeCollection;
|
||||
private final Stats.AxisType mType;
|
||||
|
||||
public InfoStatsBuilder(WebView chartView, Collection collectionData, boolean isWholeCollection){
|
||||
|
||||
public class OverviewStats {
|
||||
public double reviewsPerDayOnAll;
|
||||
public double reviewsPerDayOnStudyDays;
|
||||
public int allDays;
|
||||
public int daysStudied;
|
||||
public double timePerDayOnAll;
|
||||
public double timePerDayOnStudyDays;
|
||||
public double totalTime;
|
||||
public int totalReviews;
|
||||
public double newCardsPerDay;
|
||||
public int totalNewCards;
|
||||
public double averageInterval;
|
||||
public double longestInterval;
|
||||
}
|
||||
|
||||
public OverviewStatsBuilder(WebView chartView, Collection collectionData, boolean isWholeCollection, Stats.AxisType mStatType){
|
||||
mWebView = chartView;
|
||||
mCollectionData = collectionData;
|
||||
mIsWholeCollection = isWholeCollection;
|
||||
mWholeCollection = isWholeCollection;
|
||||
mType = mStatType;
|
||||
}
|
||||
|
||||
public String createInfoHtmlString(){
|
||||
@ -61,12 +85,61 @@ public class InfoStatsBuilder {
|
||||
stringBuilder.append(css);
|
||||
appendTodaysStats(stringBuilder);
|
||||
|
||||
appendOverViewStats(stringBuilder);
|
||||
|
||||
stringBuilder.append("</center>");
|
||||
return stringBuilder.toString();
|
||||
}
|
||||
|
||||
private void appendOverViewStats(StringBuilder stringBuilder) {
|
||||
Stats stats = new Stats(mCollectionData, mWholeCollection);
|
||||
|
||||
OverviewStats oStats = new OverviewStats();
|
||||
stats.calculateOverviewStatistics(mType, oStats, mWebView.getContext());
|
||||
Resources res = mWebView.getResources();
|
||||
|
||||
stringBuilder.append(_title(res.getString(mType.descriptionId)));
|
||||
|
||||
|
||||
boolean allDaysStudied = oStats.daysStudied == oStats.allDays;
|
||||
|
||||
stringBuilder.append(_subtitle(res.getString(R.string.stats_review_count).toUpperCase()));
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_days_studied,(int)((float)oStats.daysStudied/(float)oStats.allDays*100), oStats.daysStudied, oStats.allDays));
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_total_reviews,oStats.totalReviews));
|
||||
stringBuilder.append("<br>");
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_reviews_per_day_studydays,oStats.reviewsPerDayOnStudyDays));
|
||||
if (!allDaysStudied){
|
||||
stringBuilder.append("<br>");
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_reviews_per_day_all,oStats.reviewsPerDayOnAll));
|
||||
}
|
||||
|
||||
stringBuilder.append("<br><br>");
|
||||
stringBuilder.append(_subtitle(res.getString(R.string.stats_review_time).toUpperCase()));
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_time_per_day_studydays,oStats.timePerDayOnStudyDays));
|
||||
if (!allDaysStudied){
|
||||
stringBuilder.append("<br>");
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_time_per_day_all,oStats.timePerDayOnAll));
|
||||
}
|
||||
|
||||
stringBuilder.append("<br><br>");
|
||||
stringBuilder.append(_subtitle(res.getString(R.string.stats_progress).toUpperCase()));
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_total_new_cards,oStats.totalNewCards));
|
||||
stringBuilder.append("<br>");
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_new_cards_per_day,oStats.newCardsPerDay));
|
||||
|
||||
stringBuilder.append("<br><br>");
|
||||
stringBuilder.append(_subtitle(res.getString(R.string.stats_review_intervals).toUpperCase()));
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_average_interval));
|
||||
|
||||
stringBuilder.append(Utils.roundedTimeSpan(mWebView.getContext(), (int)Math.round(oStats.averageInterval*Stats.SECONDS_PER_DAY)));
|
||||
stringBuilder.append("<br>");
|
||||
stringBuilder.append(res.getString(R.string.stats_overview_longest_interval));
|
||||
stringBuilder.append(Utils.roundedTimeSpan(mWebView.getContext(), (int)Math.round(oStats.longestInterval*Stats.SECONDS_PER_DAY)));
|
||||
|
||||
}
|
||||
|
||||
private void appendTodaysStats(StringBuilder stringBuilder){
|
||||
Stats stats = new Stats(mCollectionData, mIsWholeCollection);
|
||||
Stats stats = new Stats(mCollectionData, mWholeCollection);
|
||||
int[] todayStats = stats.calculateTodayStats();
|
||||
stringBuilder.append(_title(mWebView.getResources().getString(R.string.stats_today)));
|
||||
Resources res = mWebView.getResources();
|
||||
@ -88,15 +161,17 @@ public class InfoStatsBuilder {
|
||||
} else {
|
||||
stringBuilder.append(res.getString(R.string.stats_today_no_mature_cards));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
private String _title(String title){
|
||||
return _title(title, "");
|
||||
return "<h1>" + title + "</h1>";
|
||||
}
|
||||
|
||||
private String _title(String title, String subtitle){
|
||||
return "<h1>" + title + "</h1>" + subtitle;
|
||||
private String _subtitle(String title){
|
||||
return "<h3>" + title + "</h3>";
|
||||
}
|
||||
|
||||
private String bold(String s) {
|
@ -19,9 +19,11 @@ package com.ichi2.libanki;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.ichi2.anki.AnkiDroidApp;
|
||||
import com.ichi2.anki.R;
|
||||
import com.ichi2.anki.stats.OverviewStatsBuilder;
|
||||
import com.ichi2.anki.stats.StatsMetaInfo;
|
||||
import com.ichi2.libanki.hooks.Hooks;
|
||||
|
||||
@ -37,10 +39,19 @@ import timber.log.Timber;
|
||||
*/
|
||||
public class Stats {
|
||||
|
||||
|
||||
|
||||
public static enum AxisType{
|
||||
TYPE_MONTH,
|
||||
TYPE_YEAR,
|
||||
TYPE_LIFE
|
||||
TYPE_MONTH(30, R.string.stats_period_month),
|
||||
TYPE_YEAR(365, R.string.stats_period_year),
|
||||
TYPE_LIFE(-1, R.string.stats_period_lifetime);
|
||||
|
||||
public int days;
|
||||
public int descriptionId;
|
||||
AxisType(int dayss, int descriptionId) {
|
||||
this.days = dayss;
|
||||
this.descriptionId = descriptionId;
|
||||
}
|
||||
}
|
||||
|
||||
public static enum ChartType {FORECAST, REVIEW_COUNT, REVIEW_TIME,
|
||||
@ -75,6 +86,9 @@ public class Stats {
|
||||
private double mPeak;
|
||||
private double mMcount;
|
||||
|
||||
|
||||
public static double SECONDS_PER_DAY = 86400.0;
|
||||
|
||||
public Stats(Collection col, boolean wholeCollection) {
|
||||
mCol = col;
|
||||
mWholeCollection = wholeCollection;
|
||||
@ -116,7 +130,7 @@ public class Stats {
|
||||
* Todays statistics
|
||||
*/
|
||||
public int[] calculateTodayStats(){
|
||||
String lim = _revlogLimit();
|
||||
String lim = _getDeckFilter();
|
||||
if (lim.length() > 0)
|
||||
lim = " and " + lim;
|
||||
|
||||
@ -127,7 +141,7 @@ public class Stats {
|
||||
"sum(case when type = 1 then 1 else 0 end), "+ /* review */
|
||||
"sum(case when type = 2 then 1 else 0 end), "+ /* relearn */
|
||||
"sum(case when type = 3 then 1 else 0 end) "+ /* filter */
|
||||
"from revlog where id > " + ((mCol.getSched().getDayCutoff()-86400)*1000) + " " + lim;
|
||||
"from revlog where id > " + ((mCol.getSched().getDayCutoff()-SECONDS_PER_DAY)*1000) + " " + lim;
|
||||
Timber.d("todays statistics query: %s", query);
|
||||
|
||||
int cards, thetime, failed, lrn, rev, relrn, filt;
|
||||
@ -153,7 +167,7 @@ public class Stats {
|
||||
}
|
||||
}
|
||||
query = "select count(), sum(case when ease = 1 then 0 else 1 end) from revlog " +
|
||||
"where lastIvl >= 21 and id > " + ((mCol.getSched().getDayCutoff()-86400)*1000) + " " + lim;
|
||||
"where lastIvl >= 21 and id > " + ((mCol.getSched().getDayCutoff()-SECONDS_PER_DAY)*1000) + " " + lim;
|
||||
Timber.d("todays statistics query 2: %s", query);
|
||||
|
||||
int mcnt, msum;
|
||||
@ -174,6 +188,128 @@ public class Stats {
|
||||
return new int[]{cards, thetime, failed, lrn, rev, relrn, filt, mcnt, msum};
|
||||
}
|
||||
|
||||
private String getRevlogTimeFilter(AxisType timespan, boolean inverse){
|
||||
if (timespan == AxisType.TYPE_LIFE){
|
||||
return "";
|
||||
}
|
||||
else {
|
||||
String operator = null;
|
||||
if (inverse){
|
||||
operator = "<= ";
|
||||
}else{
|
||||
operator = "> ";
|
||||
}
|
||||
return "id "+ operator + ((mCol.getSched().getDayCutoff() - (timespan.days * SECONDS_PER_DAY)) * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
public int getNewCards(AxisType timespan){
|
||||
|
||||
String filter = getRevlogFilter(timespan,false);
|
||||
|
||||
String queryNeg = "";
|
||||
if (timespan != AxisType.TYPE_LIFE){
|
||||
String invfilter = getRevlogFilter(timespan,true);
|
||||
queryNeg = " EXCEPT SELECT distinct cid FROM revlog " + invfilter;
|
||||
}
|
||||
|
||||
String query = "SELECT COUNT(*) FROM(\n" +
|
||||
" SELECT distinct cid\n" +
|
||||
" FROM revlog \n" +
|
||||
filter+
|
||||
queryNeg+
|
||||
" )";
|
||||
Timber.d("New cards query: %s", query);
|
||||
Cursor cur = null;
|
||||
int res = 0;
|
||||
try {
|
||||
cur = mCol.getDb().getDatabase().rawQuery(query, null);
|
||||
while (cur.moveToNext()) {
|
||||
res = cur.getInt(0);
|
||||
}
|
||||
} finally {
|
||||
if (cur != null && !cur.isClosed()) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
String getRevlogFilter(AxisType timespan,boolean inverseTimeSpan){
|
||||
ArrayList<String> lims = new ArrayList<>();
|
||||
String dayFilter = getRevlogTimeFilter(timespan, inverseTimeSpan);
|
||||
if (dayFilter != "") lims.add(dayFilter);
|
||||
String lim = _getDeckFilter().replaceAll("[\\[\\]]", "");
|
||||
if (lim.length() > 0){
|
||||
lims.add(lim);
|
||||
}
|
||||
|
||||
if (!lims.isEmpty()) {
|
||||
lim = "WHERE ";
|
||||
lim += TextUtils.join(" AND ",lims.toArray());
|
||||
}
|
||||
|
||||
return lim;
|
||||
}
|
||||
|
||||
public void calculateOverviewStatistics(AxisType timespan, OverviewStatsBuilder.OverviewStats oStats, Context context) {
|
||||
oStats.allDays = timespan.days;
|
||||
|
||||
String lim = getRevlogFilter(timespan,false);
|
||||
Cursor cur = null;
|
||||
|
||||
try {
|
||||
cur = mCol.getDb().getDatabase().rawQuery( "SELECT COUNT(*) as num_reviews, sum(case when type = 0 then 1 else 0 end) as new_cards FROM revlog "+ lim, null);
|
||||
while (cur.moveToNext()) {
|
||||
oStats.totalReviews = cur.getInt(0);
|
||||
}
|
||||
} finally {
|
||||
if (cur != null && !cur.isClosed())
|
||||
cur.close();
|
||||
}
|
||||
|
||||
String cntquery = "SELECT COUNT(*) numDays, MIN(day) firstDay, SUM(time_per_day) sum_time from (" +
|
||||
" SELECT (cast((id/1000 - " + mCol.getSched().getDayCutoff() + ") / "+SECONDS_PER_DAY+" AS INT)) AS day, sum(time/1000.0/60.0) AS time_per_day"
|
||||
+ " FROM revlog " + lim + " GROUP BY day ORDER BY day)";
|
||||
Timber.d("Count cntquery: %s", cntquery);
|
||||
try {
|
||||
cur = mCol.getDb().getDatabase().rawQuery(cntquery, null);
|
||||
while (cur.moveToNext()) {
|
||||
oStats.daysStudied = cur.getInt(0);
|
||||
oStats.totalTime = cur.getDouble(2);
|
||||
if (timespan == AxisType.TYPE_LIFE)
|
||||
oStats.allDays = Math.abs(cur.getInt(1))+1; // +1 for today
|
||||
}
|
||||
} finally {
|
||||
if (cur != null && !cur.isClosed()) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
cur = mCol.getDb().getDatabase().rawQuery("select avg(ivl), max(ivl) from cards where did in " +_limit() +
|
||||
" and queue = 2", null);
|
||||
|
||||
cur.moveToFirst();
|
||||
oStats.averageInterval = cur.getDouble(0);
|
||||
oStats.longestInterval = cur.getDouble(1);
|
||||
} finally {
|
||||
if (cur != null && !cur.isClosed()) {
|
||||
cur.close();
|
||||
}
|
||||
}
|
||||
oStats.reviewsPerDayOnAll = (double)oStats.totalReviews / oStats.allDays;
|
||||
oStats.reviewsPerDayOnStudyDays = (double)oStats.totalReviews / oStats.daysStudied;
|
||||
|
||||
oStats.timePerDayOnAll = oStats.totalTime / oStats.allDays;
|
||||
oStats.timePerDayOnStudyDays = oStats.totalTime / oStats.daysStudied;
|
||||
|
||||
oStats.totalNewCards = getNewCards(timespan);
|
||||
oStats.newCardsPerDay = (double)oStats.totalNewCards/(double) oStats.allDays;
|
||||
|
||||
}
|
||||
|
||||
public boolean calculateDue(Context context, AxisType type) {
|
||||
|
||||
// Not in libanki
|
||||
@ -414,9 +550,9 @@ public class Stats {
|
||||
}
|
||||
ArrayList<String> lims = new ArrayList<>();
|
||||
if (num != -1) {
|
||||
lims.add("id > " + ((mCol.getSched().getDayCutoff() - ((num + 1) * chunk * 86400)) * 1000));
|
||||
lims.add("id > " + ((mCol.getSched().getDayCutoff() - ((num + 1) * chunk * SECONDS_PER_DAY)) * 1000));
|
||||
}
|
||||
String lim = _revlogLimit().replaceAll("[\\[\\]]", "");
|
||||
String lim = _getDeckFilter().replaceAll("[\\[\\]]", "");
|
||||
if (lim.length() > 0) {
|
||||
lims.add(lim);
|
||||
}
|
||||
@ -432,7 +568,7 @@ public class Stats {
|
||||
String ti;
|
||||
String tf;
|
||||
if (charType == ChartType.REVIEW_TIME) {
|
||||
ti = "time/1000";
|
||||
ti = "time/1000.0";
|
||||
if (mType == AxisType.TYPE_MONTH) {
|
||||
tf = "/60.0"; // minutes
|
||||
mAxisTitles = new int[] { type.ordinal(), R.string.stats_minutes, R.string.stats_cumulative_time_minutes };
|
||||
@ -446,7 +582,7 @@ public class Stats {
|
||||
}
|
||||
ArrayList<double[]> list = new ArrayList<>();
|
||||
Cursor cur = null;
|
||||
String query = "SELECT (cast((id/1000 - " + mCol.getSched().getDayCutoff() + ") / 86400.0 AS INT))/"
|
||||
String query = "SELECT (cast((id/1000 - " + mCol.getSched().getDayCutoff() + ") / "+SECONDS_PER_DAY+" AS INT))/"
|
||||
+ chunk + " AS day, " + "sum(CASE WHEN type = 0 THEN " + ti + " ELSE 0 END)"
|
||||
+ tf
|
||||
+ ", " // lrn
|
||||
@ -476,6 +612,7 @@ public class Stats {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// small adjustment for a proper chartbuilding with achartengine
|
||||
if (type != AxisType.TYPE_LIFE && (list.size() == 0 || list.get(0)[0] > -num)) {
|
||||
list.add(0, new double[] { -num, 0, 0, 0, 0, 0 });
|
||||
@ -681,8 +818,8 @@ public class Stats {
|
||||
mFirstElement = 0;
|
||||
|
||||
mMaxElements = list.size()-1;
|
||||
mAverage = Utils.timeSpan(context, (int)Math.round(avg*86400));
|
||||
mLongest = Utils.timeSpan(context, (int)Math.round(max_*86400));
|
||||
mAverage = Utils.timeSpan(context, (int)Math.round(avg*SECONDS_PER_DAY));
|
||||
mLongest = Utils.timeSpan(context, (int)Math.round(max_*SECONDS_PER_DAY));
|
||||
|
||||
//some adjustments to not crash the chartbuilding with emtpy data
|
||||
if(mMaxElements == 0){
|
||||
@ -711,7 +848,7 @@ public class Stats {
|
||||
mColors = new int[] { R.attr.stats_counts, R.attr.stats_hours};
|
||||
|
||||
mType = type;
|
||||
String lim = _revlogLimit().replaceAll("[\\[\\]]", "");
|
||||
String lim = _getDeckFilter().replaceAll("[\\[\\]]", "");
|
||||
|
||||
if (lim.length() > 0) {
|
||||
lim = " and " + lim;
|
||||
@ -722,7 +859,7 @@ public class Stats {
|
||||
|
||||
int pd = _periodDays();
|
||||
if(pd > 0){
|
||||
lim += " and id > "+ ((mCol.getSched().getDayCutoff()-(86400*pd))*1000);
|
||||
lim += " and id > "+ ((mCol.getSched().getDayCutoff()-(SECONDS_PER_DAY*pd))*1000);
|
||||
}
|
||||
long cutoff = mCol.getSched().getDayCutoff();
|
||||
long cut = cutoff - sd.get(Calendar.HOUR_OF_DAY)*3600;
|
||||
@ -849,7 +986,7 @@ public class Stats {
|
||||
mColors = new int[] { R.attr.stats_counts, R.attr.stats_hours};
|
||||
|
||||
mType = type;
|
||||
String lim = _revlogLimit().replaceAll("[\\[\\]]", "");
|
||||
String lim = _getDeckFilter().replaceAll("[\\[\\]]", "");
|
||||
|
||||
if (lim.length() > 0) {
|
||||
lim = " and " + lim;
|
||||
@ -861,7 +998,7 @@ public class Stats {
|
||||
|
||||
int pd = _periodDays();
|
||||
if(pd > 0){
|
||||
lim += " and id > "+ ((mCol.getSched().getDayCutoff()-(86400*pd))*1000);
|
||||
lim += " and id > "+ ((mCol.getSched().getDayCutoff()-(SECONDS_PER_DAY*pd))*1000);
|
||||
}
|
||||
|
||||
long cutoff = mCol.getSched().getDayCutoff();
|
||||
@ -977,7 +1114,7 @@ public class Stats {
|
||||
mColors = new int[] { R.attr.stats_learn, R.attr.stats_young, R.attr.stats_mature};
|
||||
|
||||
mType = type;
|
||||
String lim = _revlogLimit().replaceAll("[\\[\\]]", "");
|
||||
String lim = _getDeckFilter().replaceAll("[\\[\\]]", "");
|
||||
|
||||
Vector<String> lims = new Vector<>();
|
||||
int days = 0;
|
||||
@ -993,7 +1130,7 @@ public class Stats {
|
||||
days = -1;
|
||||
|
||||
if (days > 0)
|
||||
lims.add("id > " + ((mCol.getSched().getDayCutoff()-(days*86400))*1000));
|
||||
lims.add("id > " + ((mCol.getSched().getDayCutoff()-(days*SECONDS_PER_DAY))*1000));
|
||||
if (lims.size() > 0) {
|
||||
lim = "where " + lims.get(0);
|
||||
for(int i=1; i<lims.size(); i++)
|
||||
@ -1185,7 +1322,7 @@ public class Stats {
|
||||
}
|
||||
|
||||
|
||||
private String _revlogLimit() {
|
||||
private String _getDeckFilter() {
|
||||
if (mWholeCollection) {
|
||||
return "";
|
||||
} else {
|
||||
|
@ -193,6 +193,24 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a proper string for a time value in seconds
|
||||
*
|
||||
* @param context The application's environment.
|
||||
* @param time_s The time to format, in seconds
|
||||
* @return The formatted, localized time string. The time is always a float.
|
||||
*/
|
||||
public static String roundedTimeSpan(Context context, int time_s) {
|
||||
if (Math.abs(time_s) < TIME_DAY) {
|
||||
return context.getResources().getString(R.string.stats_overview_hours, time_s/TIME_HOUR);
|
||||
} else if (Math.abs(time_s) < TIME_MONTH) {
|
||||
return context.getResources().getString(R.string.stats_overview_days, time_s/TIME_DAY);
|
||||
} else if (Math.abs(time_s) < TIME_YEAR) {
|
||||
return context.getResources().getString(R.string.stats_overview_months,time_s/TIME_MONTH);
|
||||
} else {
|
||||
return context.getResources().getString(R.string.stats_overview_years, time_s/TIME_YEAR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locale
|
||||
|
@ -53,6 +53,28 @@
|
||||
<string name="stats_today_type_breakdown">Learn: <b>%1$s</b>, review: <b>%2$s</b>, relearn: <b>%3$s</b>, filtered: <b>%4$s</b></string>
|
||||
<string name="stats_today_mature_cards">Correct answers on mature cards: %1$d/%2$d (%3$.1f%%)</string>
|
||||
<string name="stats_today_no_mature_cards">No mature cards were studied today</string>
|
||||
|
||||
|
||||
|
||||
<string name="stats_overview_days_studied">Days studied: <b>%1$d%%</b> (%2$d of %3$d)</string>
|
||||
<string name="stats_overview_total_reviews">Total: <b>%1$d</b> reviews</string>
|
||||
<string name="stats_overview_reviews_per_day_studydays">Average for days studied: <b>%1$.1f</b> reviews/day</string>
|
||||
<string name="stats_overview_reviews_per_day_all">If you studied every day: <b>%1$.1f</b> reviews/day</string>
|
||||
|
||||
<string name="stats_overview_time_per_day_studydays">Average for days studied: <b>%1$.1f</b> minutes/day</string>
|
||||
<string name="stats_overview_time_per_day_all">If you studied every day: <b>%1$.1f</b> minutes/day</string>
|
||||
|
||||
<string name="stats_overview_new_cards_per_day">Average: <b>%1$.1f</b> new cards/day</string>
|
||||
<string name="stats_overview_total_new_cards">Total: <b>%1$d</b> new cards</string>
|
||||
|
||||
<string name="stats_overview_average_interval">"Average interval: "</string>
|
||||
<string name="stats_overview_longest_interval">"Longest interval: "</string>
|
||||
|
||||
<string name="stats_overview_years"><b>%1$.1f</b> years</string>
|
||||
<string name="stats_overview_months"><b>%1$.1f</b> months</string>
|
||||
<string name="stats_overview_days"><b>%1$.1f</b> days</string>
|
||||
<string name="stats_overview_hours"><b>%1$.1f</b> hours</string>
|
||||
|
||||
<string name="deck_summary_all_decks">All decks</string>
|
||||
<string name="stats_select_time_scale">Select timescale</string>
|
||||
<string-array name="due_x_axis_title">
|
||||
@ -61,9 +83,11 @@
|
||||
<item>Months</item>
|
||||
</string-array>
|
||||
<string name="stats_today">Today</string>
|
||||
<string name="stats_overview">Overview</string>
|
||||
<string name="stats_forecast">Forecast</string>
|
||||
<string name="stats_review_count">Review count</string>
|
||||
<string name="stats_review_time">Review time</string>
|
||||
<string name="stats_progress">Progress</string>
|
||||
<string name="stats_review_intervals">Intervals</string>
|
||||
<string name="stats_breakdown">Hourly breakdown</string>
|
||||
<string name="stats_weekly_breakdown">Weekly breakdown</string>
|
||||
|
Loading…
Reference in New Issue
Block a user