From 5763d761673f9ef479db430549de5bc539670981 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 1 Apr 2016 16:08:51 -0400 Subject: [PATCH 01/26] Make setup method public --- app/src/androidTest/java/org/isoron/uhabits/BaseTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java index 4c63a0086..1eafd98c7 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java @@ -39,7 +39,7 @@ public class BaseTest public static final long FIXED_LOCAL_TIME = 1422172800000L; // 8:00am, January 25th, 2015 (UTC) @Before - protected void setup() + public void setup() { if(!isLooperPrepared) { From bc54f3f916cd1cb06990b0970504180fc6b08b6c Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 1 Apr 2016 16:21:22 -0400 Subject: [PATCH 02/26] Improve performance of toggleCheckmark --- .../unit/models/RepetitionListTest.java | 14 ++++ .../uhabits/helpers/DatabaseHelper.java | 18 +++++ .../isoron/uhabits/models/CheckmarkList.java | 47 +++++++----- .../isoron/uhabits/models/RepetitionList.java | 32 ++++++--- .../org/isoron/uhabits/models/ScoreList.java | 71 ++++++++++++------- 5 files changed, 133 insertions(+), 49 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java index bb3c0887b..a15841faa 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/models/RepetitionListTest.java @@ -25,6 +25,7 @@ import android.test.suitebuilder.annotation.SmallTest; import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.models.Repetition; import org.isoron.uhabits.unit.HabitFixtures; import org.junit.After; import org.junit.Before; @@ -37,6 +38,7 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Random; +import static junit.framework.Assert.assertFalse; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -174,4 +176,16 @@ public class RepetitionListTest extends BaseTest from = to - 5 * DateHelper.millisecondsInOneDay; assertThat(habit.repetitions.count(from, to), equalTo(3)); } + + @Test + public void getOldest() + { + long expectedOldestTimestamp = DateHelper.getStartOfToday() - 9 * DateHelper.millisecondsInOneDay; + + assertThat(habit.repetitions.getOldestTimestamp(), equalTo(expectedOldestTimestamp)); + + Repetition oldest = habit.repetitions.getOldest(); + assertFalse(oldest == null); + assertThat(oldest.timestamp, equalTo(expectedOldestTimestamp)); + } } diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java index 03f66ec5a..fa8c509b6 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/DatabaseHelper.java @@ -20,11 +20,13 @@ package org.isoron.uhabits.helpers; import android.content.Context; +import android.database.Cursor; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.ContextCompat; import com.activeandroid.ActiveAndroid; +import com.activeandroid.Cache; import com.activeandroid.Configuration; import org.isoron.uhabits.BuildConfig; @@ -163,4 +165,20 @@ public class DatabaseHelper ActiveAndroid.initialize(dbConfig); } + + public static long longQuery(String query, String args[]) + { + Cursor c = null; + + try + { + c = Cache.openDatabase().rawQuery(query, args); + if (!c.moveToFirst()) return 0; + return c.getLong(0); + } + finally + { + if(c != null) c.close(); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 8c5f5f577..f761f26c9 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -21,10 +21,10 @@ package org.isoron.uhabits.models; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import com.activeandroid.ActiveAndroid; import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.Select; @@ -134,10 +134,9 @@ public class CheckmarkList */ protected void computeAll() { - Repetition oldestRep = habit.repetitions.getOldest(); - if(oldestRep == null) return; + long fromTimestamp = habit.repetitions.getOldestTimestamp(); + if(fromTimestamp == 0) return; - Long fromTimestamp = oldestRep.timestamp; Long toTimestamp = DateHelper.getStartOfToday(); compute(fromTimestamp, toTimestamp); @@ -150,9 +149,9 @@ public class CheckmarkList * @param from timestamp for the beginning of the interval * @param to timestamp for the end of the interval */ - protected void compute(long from, long to) + protected void compute(long from, final long to) { - long day = DateHelper.millisecondsInOneDay; + final long day = DateHelper.millisecondsInOneDay; Checkmark newestCheckmark = findNewest(); if(newestCheckmark != null) @@ -165,9 +164,9 @@ public class CheckmarkList .selectFromTo(fromExtended, to) .execute(); - int nDays = (int) ((to - from) / day) + 1; + final int nDays = (int) ((to - from) / day) + 1; int nDaysExtended = (int) ((to - fromExtended) / day) + 1; - int checks[] = new int[nDaysExtended]; + final int checks[] = new int[nDaysExtended]; for (Repetition rep : reps) { @@ -187,24 +186,38 @@ public class CheckmarkList checks[i] = Checkmark.CHECKED_IMPLICITLY; } - ActiveAndroid.beginTransaction(); + + long timestamps[] = new long[nDays]; + for (int i = 0; i < nDays; i++) + timestamps[i] = to - i * day; + + insert(timestamps, checks); + } + + private void insert(long timestamps[], int values[]) + { + String query = "insert into Checkmarks(habit, timestamp, value) values (?,?,?)"; + + SQLiteDatabase db = Cache.openDatabase(); + db.beginTransaction(); try { - for (int i = 0; i < nDays; i++) + SQLiteStatement statement = db.compileStatement(query); + statement.bindString(1, habit.getId().toString()); + + for (int i = 0; i < timestamps.length; i++) { - Checkmark c = new Checkmark(); - c.habit = habit; - c.timestamp = to - i * day; - c.value = checks[i]; - c.save(); + statement.bindLong(2, timestamps[i]); + statement.bindLong(3, values[i]); + statement.execute(); } - ActiveAndroid.setTransactionSuccessful(); + db.setTransactionSuccessful(); } finally { - ActiveAndroid.endTransaction(); + db.endTransaction(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 6e6d720ed..5bfe22fba 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -28,7 +28,9 @@ import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.From; import com.activeandroid.query.Select; +import com.activeandroid.util.SQLiteUtils; +import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DateHelper; import java.util.Arrays; @@ -96,22 +98,21 @@ public class RepetitionList timestamp = DateHelper.getStartOfDay(timestamp); if (contains(timestamp)) - { delete(timestamp); - } else - { - Repetition rep = new Repetition(); - rep.habit = habit; - rep.timestamp = timestamp; - rep.save(); - } + insert(timestamp); habit.scores.invalidateNewerThan(timestamp); habit.checkmarks.deleteNewerThan(timestamp); habit.streaks.deleteNewerThan(timestamp); } + private void insert(long timestamp) + { + String[] args = { habit.getId().toString(), Long.toString(timestamp) }; + SQLiteUtils.execSql("insert into Repetitions(habit, timestamp) values (?,?)", args); + } + /** * Returns the oldest repetition for the habit. If there is no repetition, returns null. * Repetitions in the future are discarded. @@ -124,6 +125,21 @@ public class RepetitionList return (Repetition) select().limit(1).executeSingle(); } + /** + * Returns the timestamp of the oldest repetition. If there are no repetitions, returns zero. + * Repetitions in the future are discarded. + * + * @return timestamp of the oldest repetition + */ + public long getOldestTimestamp() + { + String[] args = { habit.getId().toString(), Long.toString(DateHelper.getStartOfToday()) }; + String query = "select timestamp from Repetitions where habit = ? and timestamp <= ? " + + "order by timestamp limit 1"; + + return DatabaseHelper.longQuery(query, args); + } + /** * Returns the total number of repetitions for each month, from the first repetition until * today, grouped by day of week. The repetitions are returned in a HashMap. The key is the diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 1972dc411..36905ebd6 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -21,6 +21,7 @@ package org.isoron.uhabits.models; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteStatement; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -28,8 +29,8 @@ import com.activeandroid.Cache; import com.activeandroid.query.Delete; import com.activeandroid.query.From; import com.activeandroid.query.Select; +import com.activeandroid.util.SQLiteUtils; -import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DateHelper; import java.io.IOException; @@ -104,10 +105,9 @@ public class ScoreList */ private void computeAll() { - Repetition oldestRep = habit.repetitions.getOldest(); - if(oldestRep == null) return; + long fromTimestamp = habit.repetitions.getOldestTimestamp(); + if(fromTimestamp == 0) return; - long fromTimestamp = oldestRep.timestamp; long toTimestamp = DateHelper.getStartOfToday(); compute(fromTimestamp, toTimestamp); } @@ -136,28 +136,51 @@ public class ScoreList from = newestScore.timestamp + day; final int checkmarkValues[] = habit.checkmarks.getValues(from, to); - final int firstScore = newestScoreValue; final long beginning = from; - DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + + int lastScore = newestScoreValue; + int size = checkmarkValues.length; + + long timestamps[] = new long[size]; + long values[] = new long[size]; + + for (int i = 0; i < checkmarkValues.length; i++) + { + int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1]; + lastScore = Score.compute(freq, lastScore, checkmarkValue); + timestamps[i] = beginning + day * i; + values[i] = lastScore; + } + + insert(timestamps, values); + } + + private void insert(long timestamps[], long values[]) + { + String query = "insert into Score(habit, timestamp, score) values (?,?,?)"; + + SQLiteDatabase db = Cache.openDatabase(); + db.beginTransaction(); + + try { - @Override - public void execute() + SQLiteStatement statement = db.compileStatement(query); + statement.bindString(1, habit.getId().toString()); + + for (int i = 0; i < timestamps.length; i++) { - int lastScore = firstScore; - - for (int i = 0; i < checkmarkValues.length; i++) - { - int checkmarkValue = checkmarkValues[checkmarkValues.length - i - 1]; - - Score s = new Score(); - s.habit = habit; - s.timestamp = beginning + day * i; - s.score = lastScore = Score.compute(freq, lastScore, checkmarkValue); - s.save(); - } + statement.bindLong(2, timestamps[i]); + statement.bindLong(3, values[i]); + statement.execute(); } - }); + + db.setTransactionSuccessful(); + } + finally + { + db.endTransaction(); + } } /** @@ -185,9 +208,9 @@ public class ScoreList */ public int getValue(long timestamp) { - Score s = get(timestamp); - if(s == null) return 0; - else return s.score; + computeAll(); + String[] args = { habit.getId().toString(), Long.toString(timestamp) }; + return SQLiteUtils.intQuery("select score from Score where habit = ? and timestamp = ?", args); } /** From a9bcae0f2fb365544c3be68f17a4ca6b8155e861 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 1 Apr 2016 17:24:12 -0400 Subject: [PATCH 03/26] Clean up CheckmarkView --- .../isoron/uhabits/views/CheckmarkView.java | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java index 5455e974d..471675f10 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java @@ -30,7 +30,6 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import org.isoron.uhabits.R; @@ -43,7 +42,6 @@ public class CheckmarkView extends View private Paint pIcon; private int primaryColor; - private int backgroundColor; private int timesColor; private int darkGrey; @@ -56,12 +54,8 @@ public class CheckmarkView extends View private String fa_check; private String fa_times; - private String fa_full_star; - private String fa_half_star; - private String fa_empty_star; private int check_status; - private int star_status; private Rect rect; private TextPaint textPaint; @@ -99,19 +93,14 @@ public class CheckmarkView extends View fa_check = context.getString(R.string.fa_check); fa_times = context.getString(R.string.fa_times); - fa_empty_star = context.getString(R.string.fa_star_o); - fa_half_star = context.getString(R.string.fa_star_half_o); - fa_full_star = context.getString(R.string.fa_star); primaryColor = ColorHelper.palette[10]; - backgroundColor = Color.argb(255, 255, 255, 255); timesColor = Color.argb(128, 255, 255, 255); darkGrey = Color.argb(64, 0, 0, 0); rect = new Rect(); - check_status = 2; - star_status = 0; - label = "Wake up early"; + check_status = 0; + label = "Habit"; } public void setHabit(Habit habit) @@ -190,21 +179,19 @@ public class CheckmarkView extends View padding = 8 * leftMargin; textPaint.setTextSize(0.15f * width); - refreshData(); + labelLayout = new StaticLayout(label, textPaint, + (int) (width - 2 * leftMargin - 2 * padding), + Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); } public void refreshData() { this.check_status = habit.checkmarks.getTodayValue(); - this.star_status = habit.scores.getTodayStarStatus(); this.primaryColor = Color.argb(230, Color.red(habit.color), Color.green(habit.color), Color.blue(habit.color)); this.label = habit.name; textPaint.setColor(Color.WHITE); - labelLayout = new StaticLayout(label, textPaint, - (int) (width - 2 * leftMargin - 2 * padding), - Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + requestLayout(); } - } From 3c927e009a38a70eea9f7c63f16c6677a8030169 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 1 Apr 2016 17:49:17 -0400 Subject: [PATCH 04/26] Improve performance of Score computation --- .../org/isoron/uhabits/models/ScoreList.java | 55 +++++++++---------- 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 36905ebd6..9146ed3c8 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -31,6 +31,7 @@ import com.activeandroid.query.From; import com.activeandroid.query.Select; import com.activeandroid.util.SQLiteUtils; +import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DateHelper; import java.io.IOException; @@ -61,31 +62,6 @@ public class ScoreList .orderBy("timestamp desc"); } - /** - * Returns the most recent score already computed. If no score has been computed yet, returns - * null. - * - * @return newest score, or null if none exist - */ - @Nullable - protected Score findNewest() - { - return select().limit(1).executeSingle(); - } - - /** - * Returns the value of the most recent score that was already computed. If no score has been - * computed yet, returns zero. - * - * @return value of newest score, or zero if none exist - */ - protected int findNewestValue() - { - Score newest = findNewest(); - if(newest == null) return 0; - else return newest.score; - } - /** * Marks all scores that have timestamp equal to or newer than the given timestamp as invalid. * Any following getValue calls will trigger the scores to be recomputed. @@ -119,7 +95,7 @@ public class ScoreList * included. * * This function assumes that there are no gaps on the scores. That is, if the newest score has - * timestamp t, then every score with timestamp lower than t has already been computed. + * timestamp t, then every score with timestamp lower than t has already been computed. * * @param from timestamp of the beginning of the interval * @param to timestamp of the end of the time interval @@ -130,15 +106,14 @@ public class ScoreList final double freq = ((double) habit.freqNum) / habit.freqDen; int newestScoreValue = findNewestValue(); - Score newestScore = findNewest(); + long newestTimestamp = findNewestTimestamp(); - if(newestScore != null) - from = newestScore.timestamp + day; + if(newestTimestamp > 0) + from = newestTimestamp + day; final int checkmarkValues[] = habit.checkmarks.getValues(from, to); final long beginning = from; - int lastScore = newestScoreValue; int size = checkmarkValues.length; @@ -156,6 +131,26 @@ public class ScoreList insert(timestamps, values); } + /** + * Returns the value of the most recent score that was already computed. If no score has been + * computed yet, returns zero. + * + * @return value of newest score, or zero if none exist + */ + protected int findNewestValue() + { + String args[] = { habit.getId().toString() }; + String query = "select score from Score where habit = ? order by timestamp desc limit 1"; + return SQLiteUtils.intQuery(query, args); + } + + private long findNewestTimestamp() + { + String args[] = { habit.getId().toString() }; + String query = "select timestamp from Score where habit = ? order by timestamp desc limit 1"; + return DatabaseHelper.longQuery(query, args); + } + private void insert(long timestamps[], long values[]) { String query = "insert into Score(habit, timestamp, score) values (?,?,?)"; From bf6562f854e86ea4435244e9b975ed01dceff702 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 1 Apr 2016 17:50:14 -0400 Subject: [PATCH 05/26] Update views on background thread --- .../uhabits/fragments/ShowHabitFragment.java | 28 ++++++++++++++++--- .../isoron/uhabits/views/HabitDataView.java | 2 ++ .../uhabits/views/HabitFrequencyView.java | 2 -- .../uhabits/views/HabitHistoryView.java | 2 -- .../isoron/uhabits/views/HabitScoreView.java | 2 -- .../isoron/uhabits/views/HabitStreakView.java | 1 - 6 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index fa3afb932..f9f6f2da7 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -47,6 +47,7 @@ import org.isoron.uhabits.dialogs.HistoryEditorDialog; import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitFrequencyView; import org.isoron.uhabits.views.HabitHistoryView; @@ -151,6 +152,7 @@ public class ShowHabitFragment extends Fragment private void updateScoreRing(View view) { if(habit == null) return; + if(view == null) return; RingView scoreRing = (RingView) view.findViewById(R.id.scoreRing); scoreRing.setColor(habit.color); @@ -230,11 +232,29 @@ public class ShowHabitFragment extends Fragment public void refreshData() { - if(dataViews == null) return; - updateScoreRing(getView()); + new BaseTask() + { + @Override + protected Void doInBackground(Void... params) + { + if(dataViews == null) return null; + updateScoreRing(getView()); + + for(HabitDataView view : dataViews) + view.refreshData(); + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) + { + if(dataViews == null) return; + for(HabitDataView view : dataViews) + view.invalidate(); + } + }.execute(); - for(HabitDataView view : dataViews) - view.refreshData(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java b/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java index f6d3c712a..271f49aa2 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java @@ -26,4 +26,6 @@ public interface HabitDataView void setHabit(Habit habit); void refreshData(); + + void invalidate(); } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java index 94b34af32..283dd777d 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -180,8 +180,6 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV generateRandomData(); else if(habit != null) frequency = habit.repetitions.getWeekdayFrequency(); - - invalidate(); } private void generateRandomData() diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index a15059295..1e3e4514c 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -26,7 +26,6 @@ import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.RectF; import android.util.AttributeSet; -import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; @@ -221,7 +220,6 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie } updateDate(); - invalidate(); } private void generateRandomData() diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 02cd33947..87de29d72 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -185,8 +185,6 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView if (habit == null) return; scores = habit.scores.getAllValues(bucketSize); } - - invalidate(); } public void setBucketSize(int bucketSize) diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index e3f1692e1..326e40b6a 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -169,7 +169,6 @@ public class HabitStreakView extends View implements HabitDataView if(habit == null) return; streaks = habit.streaks.getAll(maxStreakCount); updateMaxMin(); - postInvalidate(); } @Override From 02e45dbe27871190ddb7c35c98e2e1a7ea623219 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 2 Apr 2016 10:14:21 -0400 Subject: [PATCH 06/26] Rename DialogHelper to UIHelper --- .../java/org/isoron/uhabits/unit/views/ViewTest.java | 4 ++-- app/src/main/java/org/isoron/uhabits/MainActivity.java | 6 +++--- .../isoron/uhabits/fragments/EditHabitFragment.java | 2 +- .../uhabits/fragments/HabitSelectionCallback.java | 6 +++--- .../isoron/uhabits/fragments/ListHabitsFragment.java | 3 +-- .../isoron/uhabits/fragments/ShowHabitFragment.java | 4 ++-- .../helpers/{DialogHelper.java => UIHelper.java} | 2 +- .../org/isoron/uhabits/views/HabitHistoryView.java | 4 ++-- .../main/java/org/isoron/uhabits/views/NumberView.java | 8 ++++---- .../org/isoron/uhabits/views/RepetitionCountView.java | 8 ++++---- .../main/java/org/isoron/uhabits/views/RingView.java | 8 ++++---- .../org/isoron/uhabits/widgets/BaseWidgetProvider.java | 10 +++++----- 12 files changed, 32 insertions(+), 33 deletions(-) rename app/src/main/java/org/isoron/uhabits/helpers/{DialogHelper.java => UIHelper.java} (99%) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index cb54fc396..2cf950e3a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -27,7 +27,7 @@ import android.view.MotionEvent; import android.view.View; import org.isoron.uhabits.BaseTest; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import java.io.File; import java.io.FileOutputStream; @@ -179,7 +179,7 @@ public class ViewTest extends BaseTest protected int dpToPixels(int dp) { - return (int) DialogHelper.dpToPixels(targetContext, dp); + return (int) UIHelper.dpToPixels(targetContext, dp); } protected void tap(GestureDetector.OnGestureListener view, int x, int y) throws InterruptedException diff --git a/app/src/main/java/org/isoron/uhabits/MainActivity.java b/app/src/main/java/org/isoron/uhabits/MainActivity.java index 7bd02995f..d3bb4dc88 100644 --- a/app/src/main/java/org/isoron/uhabits/MainActivity.java +++ b/app/src/main/java/org/isoron/uhabits/MainActivity.java @@ -37,7 +37,7 @@ import android.view.Menu; import android.view.MenuItem; import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.fragments.ListHabitsFragment; import org.isoron.uhabits.helpers.ReminderHelper; import org.isoron.uhabits.models.Habit; @@ -85,8 +85,8 @@ public class MainActivity extends BaseActivity private void onStartup() { PreferenceManager.setDefaultValues(this, R.xml.preferences, false); - DialogHelper.incrementLaunchCount(this); - DialogHelper.updateLastAppVersion(this); + UIHelper.incrementLaunchCount(this); + UIHelper.updateLastAppVersion(this); showTutorial(); new AsyncTask() { diff --git a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java index 628f85148..3f1a117c9 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/EditHabitFragment.java @@ -42,7 +42,7 @@ import com.android.datetimepicker.time.TimePickerDialog; import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.DialogHelper.OnSavedListener; +import org.isoron.uhabits.helpers.UIHelper.OnSavedListener; import org.isoron.uhabits.R; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.CreateHabitCommand; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java index cd5031757..9d124a691 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/HabitSelectionCallback.java @@ -36,7 +36,7 @@ import org.isoron.uhabits.commands.ChangeHabitColorCommand; import org.isoron.uhabits.commands.DeleteHabitsCommand; import org.isoron.uhabits.commands.UnarchiveHabitsCommand; import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.loaders.HabitListLoader; import org.isoron.uhabits.models.Habit; @@ -49,7 +49,7 @@ public class HabitSelectionCallback implements ActionMode.Callback private List selectedPositions; private BaseActivity activity; private Listener listener; - private DialogHelper.OnSavedListener onSavedListener; + private UIHelper.OnSavedListener onSavedListener; private ProgressBar progressBar; public interface Listener @@ -74,7 +74,7 @@ public class HabitSelectionCallback implements ActionMode.Callback this.progressBar = progressBar; } - public void setOnSavedListener(DialogHelper.OnSavedListener onSavedListener) + public void setOnSavedListener(UIHelper.OnSavedListener onSavedListener) { this.onSavedListener = onSavedListener; } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 6f419f802..62bbaaf47 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -56,8 +56,7 @@ import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.commands.ToggleRepetitionCommand; import org.isoron.uhabits.dialogs.FilePickerDialog; import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.DialogHelper; -import org.isoron.uhabits.helpers.DialogHelper.OnSavedListener; +import org.isoron.uhabits.helpers.UIHelper.OnSavedListener; import org.isoron.uhabits.helpers.HintManager; import org.isoron.uhabits.helpers.ListHabitsHelper; import org.isoron.uhabits.helpers.ReminderHelper; diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index f9f6f2da7..eda4d64cc 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -38,7 +38,7 @@ import android.widget.Spinner; import android.widget.TextView; import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.ShowHabitActivity; @@ -60,7 +60,7 @@ import java.util.LinkedList; import java.util.List; public class ShowHabitFragment extends Fragment - implements DialogHelper.OnSavedListener, HistoryEditorDialog.Listener, + implements UIHelper.OnSavedListener, HistoryEditorDialog.Listener, Spinner.OnItemSelectedListener { @Nullable diff --git a/app/src/main/java/org/isoron/uhabits/helpers/DialogHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java similarity index 99% rename from app/src/main/java/org/isoron/uhabits/helpers/DialogHelper.java rename to app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java index a3b660b7b..3a2a8c7bd 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/DialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java @@ -33,7 +33,7 @@ import android.view.inputmethod.InputMethodManager; import org.isoron.uhabits.BuildConfig; import org.isoron.uhabits.commands.Command; -public abstract class DialogHelper +public abstract class UIHelper { public static final String ISORON_NAMESPACE = "http://isoron.org/android"; diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 1e3e4514c..6719e3f50 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -32,7 +32,7 @@ import android.view.MotionEvent; import org.isoron.uhabits.R; import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.tasks.ToggleRepetitionTask; @@ -135,7 +135,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie float baseSize = height / 8.0f; setScrollerBucketSize((int) baseSize); - squareSpacing = DialogHelper.dpToPixels(getContext(), 1.0f); + squareSpacing = UIHelper.dpToPixels(getContext(), 1.0f); float maxTextSize = getResources().getDimensionPixelSize(R.dimen.regularTextSize); float textSize = Math.min(baseSize * 0.5f, maxTextSize); diff --git a/app/src/main/java/org/isoron/uhabits/views/NumberView.java b/app/src/main/java/org/isoron/uhabits/views/NumberView.java index f8f8695c4..fb1caa2c1 100644 --- a/app/src/main/java/org/isoron/uhabits/views/NumberView.java +++ b/app/src/main/java/org/isoron/uhabits/views/NumberView.java @@ -33,7 +33,7 @@ import android.view.View; import org.isoron.uhabits.R; import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; public class NumberView extends View { @@ -66,9 +66,9 @@ public class NumberView extends View this.textSize = getResources().getDimension(R.dimen.regularTextSize); - this.label = DialogHelper.getAttribute(context, attrs, "label", "Number"); - this.number = DialogHelper.getIntAttribute(context, attrs, "number", 0); - this.textSize = DialogHelper.getFloatAttribute(context, attrs, "textSize", + this.label = UIHelper.getAttribute(context, attrs, "label", "Number"); + this.number = UIHelper.getIntAttribute(context, attrs, "number", 0); + this.textSize = UIHelper.getFloatAttribute(context, attrs, "textSize", getResources().getDimension(R.dimen.regularTextSize)); this.color = ColorHelper.palette[7]; diff --git a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java index 4797cea1b..efc535346 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java @@ -24,7 +24,7 @@ import android.util.AttributeSet; import org.isoron.uhabits.R; import org.isoron.uhabits.helpers.DateHelper; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.models.Habit; import java.util.Calendar; @@ -38,9 +38,9 @@ public class RepetitionCountView extends NumberView implements HabitDataView public RepetitionCountView(Context context, AttributeSet attrs) { super(context, attrs); - this.interval = DialogHelper.getIntAttribute(context, attrs, "interval", 7); - int labelValue = DialogHelper.getIntAttribute(context, attrs, "labelValue", 7); - String labelFormat = DialogHelper.getAttribute(context, attrs, "labelFormat", + this.interval = UIHelper.getIntAttribute(context, attrs, "interval", 7); + int labelValue = UIHelper.getIntAttribute(context, attrs, "labelValue", 7); + String labelFormat = UIHelper.getAttribute(context, attrs, "labelFormat", getResources().getString(R.string.last_x_days)); setLabel(String.format(labelFormat, labelValue)); diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index fad31fa3b..f599031f2 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -33,7 +33,7 @@ import android.util.Log; import android.view.View; import org.isoron.uhabits.R; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; public class RingView extends View { @@ -63,9 +63,9 @@ public class RingView extends View { super(context, attrs); - this.label = DialogHelper.getAttribute(context, attrs, "label", "Label"); - this.maxDiameter = DialogHelper.getFloatAttribute(context, attrs, "maxDiameter", 100); - this.maxDiameter = DialogHelper.dpToPixels(context, maxDiameter); + this.label = UIHelper.getAttribute(context, attrs, "label", "Label"); + this.maxDiameter = UIHelper.getFloatAttribute(context, attrs, "maxDiameter", 100); + this.maxDiameter = UIHelper.dpToPixels(context, maxDiameter); init(); } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 4dc96e4a1..469ba1dae 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -34,7 +34,7 @@ import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews; -import org.isoron.uhabits.helpers.DialogHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; @@ -170,13 +170,13 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider if (options != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { - maxWidth = (int) DialogHelper.dpToPixels(context, + maxWidth = (int) UIHelper.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)); - maxHeight = (int) DialogHelper.dpToPixels(context, + maxHeight = (int) UIHelper.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)); - minWidth = (int) DialogHelper.dpToPixels(context, + minWidth = (int) UIHelper.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)); - minHeight = (int) DialogHelper.dpToPixels(context, + minHeight = (int) UIHelper.dpToPixels(context, options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)); } From 4fcaa68baf7fb566f8d34b6134ca9e59a83cf95f Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 2 Apr 2016 10:27:45 -0400 Subject: [PATCH 07/26] Throw exception when slow methods are executed on the main thread --- .../org/isoron/uhabits/helpers/UIHelper.java | 16 ++++++++++++++++ .../org/isoron/uhabits/models/CheckmarkList.java | 3 +++ .../org/isoron/uhabits/models/ScoreList.java | 3 +++ .../org/isoron/uhabits/models/StreakList.java | 3 +++ 4 files changed, 25 insertions(+) diff --git a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java index 3a2a8c7bd..a5983e8b5 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Typeface; +import android.os.Looper; import android.preference.PreferenceManager; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -110,4 +111,19 @@ public abstract class UIHelper DisplayMetrics metrics = resources.getDisplayMetrics(); return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, metrics); } + + /** + * Throws a runtime exception if called from the main thread. Useful to make sure that + * slow methods never accidentally slow the application down. + * + * @throws RuntimeException when run from main thread + */ + public static void throwIfMainThread() throws RuntimeException + { + Looper looper = Looper.myLooper(); + if(looper == null) return; + + if(looper == Looper.getMainLooper()) + throw new RuntimeException("This method should never be called from the main thread"); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index f761f26c9..865e4c358 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -30,6 +30,7 @@ import com.activeandroid.query.Delete; import com.activeandroid.query.Select; import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.helpers.UIHelper; import java.io.IOException; import java.io.Writer; @@ -151,6 +152,8 @@ public class CheckmarkList */ protected void compute(long from, final long to) { + UIHelper.throwIfMainThread(); + final long day = DateHelper.millisecondsInOneDay; Checkmark newestCheckmark = findNewest(); diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 9146ed3c8..06331885d 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -33,6 +33,7 @@ import com.activeandroid.util.SQLiteUtils; import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.helpers.UIHelper; import java.io.IOException; import java.io.Writer; @@ -102,6 +103,8 @@ public class ScoreList */ protected void compute(long from, long to) { + UIHelper.throwIfMainThread(); + final long day = DateHelper.millisecondsInOneDay; final double freq = ((double) habit.freqNum) / habit.freqDen; diff --git a/app/src/main/java/org/isoron/uhabits/models/StreakList.java b/app/src/main/java/org/isoron/uhabits/models/StreakList.java index f8a296ae2..691403d25 100644 --- a/app/src/main/java/org/isoron/uhabits/models/StreakList.java +++ b/app/src/main/java/org/isoron/uhabits/models/StreakList.java @@ -28,6 +28,7 @@ import com.activeandroid.query.Delete; import com.activeandroid.query.Select; import org.isoron.uhabits.helpers.DateHelper; +import org.isoron.uhabits.helpers.UIHelper; import java.util.ArrayList; import java.util.LinkedList; @@ -86,6 +87,8 @@ public class StreakList public void rebuild() { + UIHelper.throwIfMainThread(); + long beginning; long today = DateHelper.getStartOfToday(); long day = DateHelper.millisecondsInOneDay; From e2c814a9825706a602cb871f30cfb4f9f216ee11 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 2 Apr 2016 10:48:33 -0400 Subject: [PATCH 08/26] Fix ShowHabitFragment and HistoryEditorDialog --- .../uhabits/dialogs/HistoryEditorDialog.java | 21 +++++++++++++++-- .../uhabits/fragments/ShowHabitFragment.java | 5 ++-- .../isoron/uhabits/views/CheckmarkView.java | 2 +- .../uhabits/views/HabitFrequencyView.java | 5 ++-- .../uhabits/views/HabitHistoryView.java | 23 ++++++++++++++----- .../isoron/uhabits/views/HabitScoreView.java | 5 ++-- .../isoron/uhabits/views/HabitStreakView.java | 4 +--- .../uhabits/views/RepetitionCountView.java | 5 ++-- 8 files changed, 47 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java index 59d4c1645..6196a7d31 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java @@ -30,6 +30,7 @@ import android.util.Log; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.views.HabitHistoryView; public class HistoryEditorDialog extends DialogFragment @@ -44,7 +45,6 @@ public class HistoryEditorDialog extends DialogFragment { Context context = getActivity(); historyView = new HabitHistoryView(context, null); - int p = (int) getResources().getDimension(R.dimen.history_editor_padding); if(savedInstanceState != null) { @@ -52,7 +52,8 @@ public class HistoryEditorDialog extends DialogFragment if(id > 0) this.habit = Habit.get(id); } - historyView.setPadding(p, 0, p, 0); + int padding = (int) getResources().getDimension(R.dimen.history_editor_padding); + historyView.setPadding(padding, 0, padding, 0); historyView.setHabit(habit); historyView.setIsEditable(true); @@ -61,9 +62,25 @@ public class HistoryEditorDialog extends DialogFragment .setView(historyView) .setPositiveButton(android.R.string.ok, this); + refreshData(); + return builder.create(); } + private void refreshData() + { + new BaseTask() + { + @Override + @SuppressWarnings("ResourceType") + protected Void doInBackground(Void... params) + { + historyView.refreshData(); + return null; + } + }.execute(); + } + @Override public void onResume() { diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index eda4d64cc..5d114c716 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -117,7 +117,6 @@ public class ShowHabitFragment extends Fragment dataViews.add((RepetitionCountView) llRepetition.getChildAt(i)); updateHeaders(view); - updateScoreRing(view); for(HabitDataView dataView : dataViews) dataView.setHabit(habit); @@ -146,6 +145,8 @@ public class ShowHabitFragment extends Fragment } setHasOptionsMenu(true); + refreshData(); + return view; } @@ -272,7 +273,7 @@ public class ShowHabitFragment extends Fragment if(scoreView != null) { scoreView.setBucketSize(size); - scoreView.refreshData(); + refreshData(); } if(prefs != null) diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java index 471675f10..29b3a7ac0 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java @@ -106,7 +106,6 @@ public class CheckmarkView extends View public void setHabit(Habit habit) { this.habit = habit; - refreshData(); } @Override @@ -192,6 +191,7 @@ public class CheckmarkView extends View this.label = habit.name; textPaint.setColor(Color.WHITE); + postInvalidate(); requestLayout(); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java index 283dd777d..ceee57489 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitFrequencyView.java @@ -84,13 +84,10 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV { this.habit = habit; createColors(); - refreshData(); - postInvalidate(); } private void init() { - refreshData(); createPaints(); createColors(); @@ -180,6 +177,8 @@ public class HabitFrequencyView extends ScrollableDataView implements HabitDataV generateRandomData(); else if(habit != null) frequency = habit.repetitions.getWeekdayFrequency(); + + postInvalidate(); } private void generateRandomData() diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 6719e3f50..8d942bece 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -34,6 +34,7 @@ import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; import org.isoron.uhabits.tasks.ToggleRepetitionTask; import java.text.SimpleDateFormat; @@ -88,13 +89,10 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie { this.habit = habit; createColors(); - refreshData(); - postInvalidate(); } private void init() { - refreshData(); createPaints(); createColors(); @@ -220,6 +218,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie } updateDate(); + postInvalidate(); } private void generateRandomData() @@ -386,11 +385,23 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie this.isEditable = isEditable; } - @Override public void onToggleRepetitionFinished() { - refreshData(); - invalidate(); + new BaseTask() { + @Override + @SuppressWarnings("ResourceType") + protected Void doInBackground(Void... params) + { + refreshData(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) + { + invalidate(); + } + }.execute(); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java index 87de29d72..d92b4cd03 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitScoreView.java @@ -93,15 +93,12 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView { this.habit = habit; createColors(); - refreshData(); - postInvalidate(); } private void init() { createPaints(); createColors(); - if(isInEditMode()) refreshData(); dfYear = new SimpleDateFormat("yyyy", Locale.getDefault()); dfMonth = new SimpleDateFormat("MMM", Locale.getDefault()); @@ -185,6 +182,8 @@ public class HabitScoreView extends ScrollableDataView implements HabitDataView if (habit == null) return; scores = habit.scores.getAllValues(bucketSize); } + + postInvalidate(); } public void setBucketSize(int bucketSize) diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index 326e40b6a..ee808c3e7 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -78,9 +78,7 @@ public class HabitStreakView extends View implements HabitDataView public void setHabit(Habit habit) { this.habit = habit; - createColors(); - postInvalidate(); } private void init() @@ -118,7 +116,6 @@ public class HabitStreakView extends View implements HabitDataView em = paint.getFontSpacing(); textMargin = 0.5f * em; - refreshData(); updateMaxMin(); } @@ -169,6 +166,7 @@ public class HabitStreakView extends View implements HabitDataView if(habit == null) return; streaks = habit.streaks.getAll(maxStreakCount); updateMaxMin(); + postInvalidate(); } @Override diff --git a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java index efc535346..e0f80aa90 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RepetitionCountView.java @@ -44,8 +44,6 @@ public class RepetitionCountView extends NumberView implements HabitDataView getResources().getString(R.string.last_x_days)); setLabel(String.format(labelFormat, labelValue)); - - refreshData(); } @Override @@ -73,6 +71,8 @@ public class RepetitionCountView extends NumberView implements HabitDataView if(habit != null) setNumber(habit.repetitions.count(from, to)); + + postInvalidate(); } @Override @@ -80,6 +80,5 @@ public class RepetitionCountView extends NumberView implements HabitDataView { this.habit = habit; setColor(habit.color); - refreshData(); } } From e476096ae17d3ef6463c8e0c6a61e29ed6e33a96 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 2 Apr 2016 11:47:28 -0400 Subject: [PATCH 09/26] Fix view tests --- .../java/org/isoron/uhabits/BaseTest.java | 3 +-- .../uhabits/unit/views/CheckmarkViewTest.java | 1 + .../unit/views/HabitFrequencyViewTest.java | 1 + .../unit/views/HabitHistoryViewTest.java | 1 + .../unit/views/HabitScoreViewTest.java | 1 + .../unit/views/HabitStreakViewTest.java | 7 ++++-- .../isoron/uhabits/unit/views/ViewTest.java | 24 +++++++++++++++++++ .../org/isoron/uhabits/tasks/BaseTask.java | 20 +++++++++------- .../isoron/uhabits/views/CheckmarkView.java | 2 +- .../uhabits/views/HabitHistoryView.java | 1 + 10 files changed, 47 insertions(+), 14 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java index 1eafd98c7..1fac47da5 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java @@ -27,7 +27,6 @@ import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.tasks.BaseTask; import org.junit.Before; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class BaseTest @@ -55,6 +54,6 @@ public class BaseTest protected void waitForAsyncTasks() throws InterruptedException, TimeoutException { - BaseTask.waitForTasks(30, TimeUnit.SECONDS); + BaseTask.waitForTasks(30000); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java index 257ff92cc..03b05e7c3 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/CheckmarkViewTest.java @@ -47,6 +47,7 @@ public class CheckmarkViewTest extends ViewTest habit = HabitFixtures.createShortHabit(); view = new CheckmarkView(targetContext); view.setHabit(habit); + refreshData(view); measureView(dpToPixels(100), dpToPixels(200), view); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java index 4eba3bebb..ce1651dc4 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitFrequencyViewTest.java @@ -45,6 +45,7 @@ public class HabitFrequencyViewTest extends ViewTest view = new HabitFrequencyView(targetContext); view.setHabit(habit); + refreshData(view); measureView(dpToPixels(300), dpToPixels(100), view); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index e624148e2..787dfc11f 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -53,6 +53,7 @@ public class HabitHistoryViewTest extends ViewTest view = new HabitHistoryView(targetContext); view.setHabit(habit); + refreshData(view); measureView(dpToPixels(300), dpToPixels(100), view); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java index a832733c4..8324e4929 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitScoreViewTest.java @@ -48,6 +48,7 @@ public class HabitScoreViewTest extends ViewTest view = new HabitScoreView(targetContext); view.setHabit(habit); view.setBucketSize(7); + refreshData(view); measureView(dpToPixels(300), dpToPixels(100), view); } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java index 4dfb24206..b192d757b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitStreakViewTest.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.unit.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.unit.HabitFixtures; @@ -45,8 +44,10 @@ public class HabitStreakViewTest extends ViewTest Habit habit = HabitFixtures.createLongHabit(); view = new HabitStreakView(targetContext); - view.setHabit(habit); measureView(dpToPixels(300), dpToPixels(100), view); + + view.setHabit(habit); + refreshData(view); } @Test @@ -66,6 +67,8 @@ public class HabitStreakViewTest extends ViewTest public void render_withSmallSize() throws Throwable { measureView(dpToPixels(100), dpToPixels(100), view); + refreshData(view); + assertRenders(view, "HabitStreakView/renderSmallSize.png"); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index 2cf950e3a..8fdedd60a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -28,6 +28,8 @@ import android.view.View; import org.isoron.uhabits.BaseTest; import org.isoron.uhabits.helpers.UIHelper; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.views.HabitDataView; import java.io.File; import java.io.FileOutputStream; @@ -190,4 +192,26 @@ public class ViewTest extends BaseTest view.onSingleTapUp(e); e.recycle(); } + + protected void refreshData(final HabitDataView view) + { + new BaseTask() + { + @Override + protected Void doInBackground(Void... params) + { + view.refreshData(); + return null; + } + }.execute(); + + try + { + waitForAsyncTasks(); + } + catch (Exception e) + { + throw new RuntimeException("Time out"); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java index e97896be9..d880775d4 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java @@ -21,13 +21,10 @@ package org.isoron.uhabits.tasks; import android.os.AsyncTask; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class BaseTask extends AsyncTask { - private static CountDownLatch latch; private static int activeTaskCount; @Override @@ -40,22 +37,27 @@ public class BaseTask extends AsyncTask protected void onPreExecute() { activeTaskCount++; - latch = new CountDownLatch(activeTaskCount); } @Override protected void onPostExecute(Void aVoid) { activeTaskCount--; - latch.countDown(); } - public static void waitForTasks(long timeout, TimeUnit unit) + public static void waitForTasks(long timeout) throws TimeoutException, InterruptedException { - if(activeTaskCount == 0) return; + int poolInterval = 100; - boolean successful = latch.await(timeout, unit); - if(!successful) throw new TimeoutException(); + while(timeout > 0) + { + if(activeTaskCount == 0) return; + + timeout -= poolInterval; + Thread.sleep(poolInterval); + } + + throw new TimeoutException(); } } diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java index 29b3a7ac0..f63b43aed 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java @@ -36,7 +36,7 @@ import org.isoron.uhabits.R; import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.models.Habit; -public class CheckmarkView extends View +public class CheckmarkView extends View implements HabitDataView { private Paint pCard; private Paint pIcon; diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index 8d942bece..aa5c1926f 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -401,6 +401,7 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie protected void onPostExecute(Void aVoid) { invalidate(); + super.onPostExecute(null); } }.execute(); } From 2d2bb8b4ed95dc7a4b3bcbeb1eb518ca966f55a2 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sat, 2 Apr 2016 13:32:04 -0400 Subject: [PATCH 10/26] Render widgets in background --- .../isoron/uhabits/views/CheckmarkView.java | 15 ++-- .../uhabits/widgets/BaseWidgetProvider.java | 83 ++++++++++++++----- .../widgets/CheckmarkWidgetProvider.java | 11 ++- .../widgets/FrequencyWidgetProvider.java | 7 ++ .../widgets/HistoryWidgetProvider.java | 7 ++ .../uhabits/widgets/ScoreWidgetProvider.java | 7 ++ .../uhabits/widgets/StreakWidgetProvider.java | 7 ++ 7 files changed, 112 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java index f63b43aed..8819e8e37 100644 --- a/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java +++ b/app/src/main/java/org/isoron/uhabits/views/CheckmarkView.java @@ -178,9 +178,7 @@ public class CheckmarkView extends View implements HabitDataView padding = 8 * leftMargin; textPaint.setTextSize(0.15f * width); - labelLayout = new StaticLayout(label, textPaint, - (int) (width - 2 * leftMargin - 2 * padding), - Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); + updateLabel(); } public void refreshData() @@ -190,8 +188,15 @@ public class CheckmarkView extends View implements HabitDataView Color.blue(habit.color)); this.label = habit.name; - textPaint.setColor(Color.WHITE); + updateLabel(); postInvalidate(); - requestLayout(); + } + + private void updateLabel() + { + textPaint.setColor(Color.WHITE); + labelLayout = new StaticLayout(label, textPaint, + (int) (width - 2 * leftMargin - 2 * padding), + Layout.Alignment.ALIGN_CENTER, 1.0f, 0.0f, false); } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 469ba1dae..4538a9a30 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -34,9 +34,10 @@ import android.view.View; import android.widget.ImageView; import android.widget.RemoteViews; -import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.R; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; import java.io.FileOutputStream; import java.io.IOException; @@ -92,12 +93,12 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider } } - private void updateWidget(Context context, AppWidgetManager manager, int widgetId, Bundle options) + private void updateWidget(Context context, AppWidgetManager manager, + int widgetId, Bundle options) { updateWidgetSize(context, options); Context appContext = context.getApplicationContext(); - RemoteViews remoteViews = new RemoteViews(context.getPackageName(), getLayoutId()); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(appContext); Long habitId = prefs.getLong(getHabitIdKey(widgetId), -1L); @@ -112,24 +113,11 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider return; } - View widgetView = buildCustomView(context, habit); - measureCustomView(context, width, height, widgetView); - - widgetView.setDrawingCacheEnabled(true); - widgetView.buildDrawingCache(true); - Bitmap drawingCache = widgetView.getDrawingCache(); - - remoteViews.setTextViewText(R.id.label, habit.name); - remoteViews.setImageViewBitmap(R.id.imageView, drawingCache); - - //savePreview(context, widgetId, drawingCache); - - PendingIntent onClickIntent = getOnClickPendingIntent(context, habit); - if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent); - - manager.updateAppWidget(widgetId, remoteViews); + new RenderWidgetTask(widgetId, context, habit, manager).execute(); } + protected abstract void refreshCustomViewData(View widgetView); + private void savePreview(Context context, int widgetId, Bitmap widgetCache) { try @@ -204,4 +192,61 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider customView.measure(specWidth, specHeight); customView.layout(0, 0, customView.getMeasuredWidth(), customView.getMeasuredHeight()); } + + private class RenderWidgetTask extends BaseTask + { + private final int widgetId; + private final Context context; + private final Habit habit; + private final AppWidgetManager manager; + public RemoteViews remoteViews; + public View widgetView; + + public RenderWidgetTask(int widgetId, Context context, Habit habit, + AppWidgetManager manager) + { + this.widgetId = widgetId; + this.context = context; + this.habit = habit; + this.manager = manager; + } + + @Override + protected void onPreExecute() + { + super.onPreExecute(); + + remoteViews = new RemoteViews(context.getPackageName(), getLayoutId()); + widgetView = buildCustomView(context, habit); + measureCustomView(context, width, height, widgetView); + manager.updateAppWidget(widgetId, remoteViews); + } + + @Override + protected Void doInBackground(Void... params) + { + refreshCustomViewData(widgetView); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) + { + widgetView.invalidate(); + widgetView.setDrawingCacheEnabled(true); + widgetView.buildDrawingCache(true); + Bitmap drawingCache = widgetView.getDrawingCache(); + remoteViews.setTextViewText(R.id.label, habit.name); + remoteViews.setImageViewBitmap(R.id.imageView, drawingCache); + + savePreview(context, widgetId, drawingCache); + + PendingIntent onClickIntent = getOnClickPendingIntent(context, habit); + if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent); + + manager.updateAppWidget(widgetId, remoteViews); + + super.onPostExecute(aVoid); + } + } } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java index 849fdf52e..bd04e8eb0 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidgetProvider.java @@ -26,8 +26,9 @@ import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.views.CheckmarkView; +import org.isoron.uhabits.views.HabitDataView; -public class CheckmarkWidgetProvider extends BaseWidgetProvider +public class CheckmarkWidgetProvider extends BaseWidgetProvider { @Override protected View buildCustomView(Context context, Habit habit) @@ -37,6 +38,12 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider return view; } + @Override + protected void refreshCustomViewData(View view) + { + ((HabitDataView) view).refreshData(); + } + @Override protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) { @@ -60,4 +67,6 @@ public class CheckmarkWidgetProvider extends BaseWidgetProvider { return R.layout.widget_checkmark; } + + } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java index 82af6dcb3..2cac07c06 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.java @@ -26,6 +26,7 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitFrequencyView; public class FrequencyWidgetProvider extends BaseWidgetProvider @@ -39,6 +40,12 @@ public class FrequencyWidgetProvider extends BaseWidgetProvider return view; } + @Override + protected void refreshCustomViewData(View view) + { + ((HabitDataView) view).refreshData(); + } + @Override protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java index 957e1b63c..e02cb608c 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/HistoryWidgetProvider.java @@ -25,6 +25,7 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitHistoryView; public class HistoryWidgetProvider extends BaseWidgetProvider @@ -38,6 +39,12 @@ public class HistoryWidgetProvider extends BaseWidgetProvider return view; } + @Override + protected void refreshCustomViewData(View view) + { + ((HabitDataView) view).refreshData(); + } + @Override protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java index f1d64439b..456491efe 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.java @@ -25,6 +25,7 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitScoreView; public class ScoreWidgetProvider extends BaseWidgetProvider @@ -38,6 +39,12 @@ public class ScoreWidgetProvider extends BaseWidgetProvider return view; } + @Override + protected void refreshCustomViewData(View view) + { + ((HabitDataView) view).refreshData(); + } + @Override protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) { diff --git a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java index 05d4af462..f5d81d4f6 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/StreakWidgetProvider.java @@ -25,6 +25,7 @@ import android.view.View; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.views.HabitDataView; import org.isoron.uhabits.views.HabitStreakView; public class StreakWidgetProvider extends BaseWidgetProvider @@ -38,6 +39,12 @@ public class StreakWidgetProvider extends BaseWidgetProvider return view; } + @Override + protected void refreshCustomViewData(View view) + { + ((HabitDataView) view).refreshData(); + } + @Override protected PendingIntent getOnClickPendingIntent(Context context, Habit habit) { From 4cec7d7367ce36bba33c4af282599bf96632b17f Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 05:08:57 -0400 Subject: [PATCH 11/26] Implement methods to generate huge test database --- .../isoron/uhabits/unit/HabitFixtures.java | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java index c6401cab4..22a425266 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitFixtures.java @@ -19,9 +19,23 @@ package org.isoron.uhabits.unit; +import android.content.Context; +import android.support.annotation.Nullable; +import android.util.Log; + import org.isoron.uhabits.helpers.ColorHelper; +import org.isoron.uhabits.helpers.DatabaseHelper; import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; +import org.isoron.uhabits.tasks.ExportDBTask; +import org.isoron.uhabits.tasks.ImportDataTask; + +import java.io.File; +import java.io.InputStream; +import java.util.Random; + +import static org.junit.Assert.fail; public class HabitFixtures { @@ -79,6 +93,74 @@ public class HabitFixtures return habit; } + public static void generateHugeDataSet() throws Throwable + { + final int nHabits = 30; + final int nYears = 5; + + DatabaseHelper.executeAsTransaction(new DatabaseHelper.Command() + { + @Override + public void execute() + { + Random rand = new Random(); + + for(int i = 0; i < nHabits; i++) + { + Log.i("HabitFixture", String.format("Creating habit %d / %d", i, nHabits)); + + Habit habit = new Habit(); + habit.name = String.format("Habit %d", i); + habit.save(); + + long today = DateHelper.getStartOfToday(); + long day = DateHelper.millisecondsInOneDay; + + + for(int j = 0; j < 365 * nYears; j++) + { + if(rand.nextBoolean()) + habit.repetitions.toggle(today - j * day); + } + + habit.scores.getTodayValue(); + habit.streaks.getAll(1); + } + } + }); + + ExportDBTask task = new ExportDBTask(null); + task.setListener(new ExportDBTask.Listener() + { + @Override + public void onExportDBFinished(@Nullable String filename) + { + if(filename != null) + Log.i("HabitFixture", String.format("Huge data set exported to %s", filename)); + else + Log.i("HabitFixture", "Failed to save database"); + } + }); + task.execute(); + + BaseTask.waitForTasks(30000); + } + + public static void loadHugeDataSet(Context testContext) throws Throwable + { + File baseDir = DatabaseHelper.getFilesDir("Backups"); + if(baseDir == null) fail("baseDir should not be null"); + + File dst = new File(String.format("%s/%s", baseDir.getPath(), "loopHuge.db")); + InputStream in = testContext.getAssets().open("fixtures/loopHuge.db"); + DatabaseHelper.copy(in, dst); + + ImportDataTask task = new ImportDataTask(dst, null); + task.execute(); + + BaseTask.waitForTasks(30000); + } + public static void purgeHabits() { for(Habit h : Habit.getAll(true)) From 7f009e236594499342d985d34888bb770f845efe Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 05:10:13 -0400 Subject: [PATCH 12/26] Create indexes in database --- app/build.gradle | 2 +- app/src/main/assets/migrations/13.sql | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 app/src/main/assets/migrations/13.sql diff --git a/app/build.gradle b/app/build.gradle index 695bd53a3..d98191ed1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,7 +9,7 @@ android { minSdkVersion 15 targetSdkVersion 23 - buildConfigField "Integer", "databaseVersion", "12" + buildConfigField "Integer", "databaseVersion", "13" buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/assets/migrations/13.sql b/app/src/main/assets/migrations/13.sql new file mode 100644 index 000000000..1d7eeafcf --- /dev/null +++ b/app/src/main/assets/migrations/13.sql @@ -0,0 +1,4 @@ +create index idx_score_habit_timestamp on score(habit, timestamp); +create index idx_checkmark_habit_timestamp on checkmarks(habit, timestamp); +create index idx_repetitions_habit_timestamp on repetitions(habit, timestamp); +create index idx_streak_habit_end on streak(habit, end); \ No newline at end of file From 1cd8eb684906533ac67ef6e2b0f6eb5be02e786b Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 05:11:45 -0400 Subject: [PATCH 13/26] NumberView: do not request layout on setNumber --- app/src/main/java/org/isoron/uhabits/views/NumberView.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/isoron/uhabits/views/NumberView.java b/app/src/main/java/org/isoron/uhabits/views/NumberView.java index fb1caa2c1..2d5ab4402 100644 --- a/app/src/main/java/org/isoron/uhabits/views/NumberView.java +++ b/app/src/main/java/org/isoron/uhabits/views/NumberView.java @@ -92,7 +92,6 @@ public class NumberView extends View public void setNumber(int number) { this.number = number; - requestLayout(); postInvalidate(); } From 7433a2413da81b7633f0a57cdb97bcb282135351 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 06:45:10 -0400 Subject: [PATCH 14/26] Refactor BaseTask interface --- .../org/isoron/uhabits/unit/views/ViewTest.java | 3 +-- .../uhabits/dialogs/HistoryEditorDialog.java | 4 +--- .../java/org/isoron/uhabits/tasks/BaseTask.java | 15 +++++++++------ .../org/isoron/uhabits/tasks/ExportCSVTask.java | 7 ++----- .../org/isoron/uhabits/tasks/ExportDBTask.java | 7 ++----- .../org/isoron/uhabits/tasks/ImportDataTask.java | 5 +---- .../uhabits/tasks/ToggleRepetitionTask.java | 3 +-- .../isoron/uhabits/views/HabitHistoryView.java | 7 +++---- .../uhabits/widgets/BaseWidgetProvider.java | 3 +-- 9 files changed, 21 insertions(+), 33 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index 8fdedd60a..b6099f9a6 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -198,10 +198,9 @@ public class ViewTest extends BaseTest new BaseTask() { @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { view.refreshData(); - return null; } }.execute(); diff --git a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java index 6196a7d31..7654fb0ed 100644 --- a/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java +++ b/app/src/main/java/org/isoron/uhabits/dialogs/HistoryEditorDialog.java @@ -72,11 +72,9 @@ public class HistoryEditorDialog extends DialogFragment new BaseTask() { @Override - @SuppressWarnings("ResourceType") - protected Void doInBackground(Void... params) + protected void doInBackground() { historyView.refreshData(); - return null; } }.execute(); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java index d880775d4..29d9937f1 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java @@ -23,24 +23,27 @@ import android.os.AsyncTask; import java.util.concurrent.TimeoutException; -public class BaseTask extends AsyncTask +public abstract class BaseTask extends AsyncTask { private static int activeTaskCount; @Override - protected Void doInBackground(Void... params) + protected final Void doInBackground(Void... params) { + register(); + doInBackground(); + unregister(); return null; } - @Override - protected void onPreExecute() + protected abstract void doInBackground(); + + protected void register() { activeTaskCount++; } - @Override - protected void onPostExecute(Void aVoid) + protected void unregister() { activeTaskCount--; } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java index dc3a53305..5c85bc89e 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportCSVTask.java @@ -19,7 +19,6 @@ package org.isoron.uhabits.tasks; -import android.os.AsyncTask; import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; @@ -80,12 +79,12 @@ public class ExportCSVTask extends BaseTask } @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { try { File dir = DatabaseHelper.getFilesDir("CSV"); - if(dir == null) return null; + if(dir == null) return; HabitsCSVExporter exporter = new HabitsCSVExporter(selectedHabits, dir); archiveFilename = exporter.writeArchive(); @@ -94,7 +93,5 @@ public class ExportCSVTask extends BaseTask { e.printStackTrace(); } - - return null; } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java index ebdb213a9..4e184335f 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ExportDBTask.java @@ -19,7 +19,6 @@ package org.isoron.uhabits.tasks; -import android.os.AsyncTask; import android.support.annotation.Nullable; import android.view.View; import android.widget.ProgressBar; @@ -75,14 +74,14 @@ public class ExportDBTask extends BaseTask } @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { filename = null; try { File dir = DatabaseHelper.getFilesDir("Backups"); - if(dir == null) return null; + if(dir == null) return; filename = DatabaseHelper.saveDatabaseCopy(dir); } @@ -90,7 +89,5 @@ public class ExportDBTask extends BaseTask { e.printStackTrace(); } - - return null; } } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java index fe7f03528..477f1f51b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ImportDataTask.java @@ -19,7 +19,6 @@ package org.isoron.uhabits.tasks; -import android.os.AsyncTask; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.view.View; @@ -86,7 +85,7 @@ public class ImportDataTask extends BaseTask } @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { try { @@ -106,7 +105,5 @@ public class ImportDataTask extends BaseTask result = FAILED; e.printStackTrace(); } - - return null; } } \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java index bece42414..3b46ec95c 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/ToggleRepetitionTask.java @@ -38,10 +38,9 @@ public class ToggleRepetitionTask extends BaseTask } @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { habit.repetitions.toggle(timestamp); - return null; } @Override diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java index aa5c1926f..67297101e 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitHistoryView.java @@ -388,13 +388,12 @@ public class HabitHistoryView extends ScrollableDataView implements HabitDataVie @Override public void onToggleRepetitionFinished() { - new BaseTask() { + new BaseTask() + { @Override - @SuppressWarnings("ResourceType") - protected Void doInBackground(Void... params) + protected void doInBackground() { refreshData(); - return null; } @Override diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index 4538a9a30..b1d75f418 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -223,10 +223,9 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider } @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { refreshCustomViewData(widgetView); - return null; } @Override From cc3e2153d07aa5c0ba9595e615a1bf6f52d209d5 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 07:26:40 -0400 Subject: [PATCH 15/26] Allow BaseTask to publish integer progress --- app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java index 29d9937f1..f22ebb43d 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java @@ -23,7 +23,7 @@ import android.os.AsyncTask; import java.util.concurrent.TimeoutException; -public abstract class BaseTask extends AsyncTask +public abstract class BaseTask extends AsyncTask { private static int activeTaskCount; From ae5be202b2ad05c2a67351991d8844166dc6e9d4 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 07:27:16 -0400 Subject: [PATCH 16/26] Add convenience methods for tracing --- .../org/isoron/uhabits/helpers/UIHelper.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java index a5983e8b5..00c012b1c 100644 --- a/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java +++ b/app/src/main/java/org/isoron/uhabits/helpers/UIHelper.java @@ -23,6 +23,8 @@ import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.Typeface; +import android.os.Build; +import android.os.Debug; import android.os.Looper; import android.preference.PreferenceManager; import android.util.AttributeSet; @@ -126,4 +128,22 @@ public abstract class UIHelper if(looper == Looper.getMainLooper()) throw new RuntimeException("This method should never be called from the main thread"); } + + public static void startTracing() + { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) + { + throw new UnsupportedOperationException(); + } + else + { + Debug.startMethodTracingSampling("Android/data/org.isoron.uhabits/perf", + 32 * 1024 * 1024, 100); + } + } + + public static void stopTracing() + { + Debug.stopMethodTracing(); + } } From 357646152fbd15bfec42f126328c3389d64f502b Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 07:27:37 -0400 Subject: [PATCH 17/26] Use postInvalidate instead of invalidate --- app/src/main/java/org/isoron/uhabits/views/HabitDataView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java b/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java index 271f49aa2..b1e239d5e 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitDataView.java @@ -27,5 +27,5 @@ public interface HabitDataView void refreshData(); - void invalidate(); + void postInvalidate(); } From eb8dd1d4509991659816a2c43fed1d3162371312 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 07:46:08 -0400 Subject: [PATCH 18/26] Simplify and optimize loading time of ShowHabitFragment --- .../org/isoron/uhabits/ShowHabitActivity.java | 42 ++--------- .../uhabits/fragments/ShowHabitFragment.java | 55 ++++++++------- .../isoron/uhabits/views/HabitStreakView.java | 2 +- app/src/main/res/layout/show_habit.xml | 69 ------------------- 4 files changed, 37 insertions(+), 131 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java index f802419ea..4fa707bd9 100644 --- a/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java +++ b/app/src/main/java/org/isoron/uhabits/ShowHabitActivity.java @@ -20,27 +20,16 @@ package org.isoron.uhabits; import android.app.ActionBar; -import android.content.BroadcastReceiver; import android.content.ContentUris; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; -import android.support.v4.content.LocalBroadcastManager; -import org.isoron.uhabits.fragments.ShowHabitFragment; import org.isoron.uhabits.models.Habit; public class ShowHabitActivity extends BaseActivity { - - public Habit habit; - private Receiver receiver; - private LocalBroadcastManager localBroadcastManager; - - private ShowHabitFragment fragment; + private Habit habit; @Override protected void onCreate(Bundle savedInstanceState) @@ -51,37 +40,18 @@ public class ShowHabitActivity extends BaseActivity habit = Habit.get(ContentUris.parseId(data)); ActionBar actionBar = getActionBar(); - if(actionBar != null) + if(actionBar != null && getHabit() != null) { - actionBar.setTitle(habit.name); - + actionBar.setTitle(getHabit().name); if (android.os.Build.VERSION.SDK_INT >= 21) - actionBar.setBackgroundDrawable(new ColorDrawable(habit.color)); + actionBar.setBackgroundDrawable(new ColorDrawable(getHabit().color)); } setContentView(R.layout.show_habit_activity); - - fragment = (ShowHabitFragment) getFragmentManager().findFragmentById(R.id.fragment2); - - receiver = new Receiver(); - localBroadcastManager = LocalBroadcastManager.getInstance(this); - localBroadcastManager.registerReceiver(receiver, - new IntentFilter(MainActivity.ACTION_REFRESH)); } - class Receiver extends BroadcastReceiver - { - @Override - public void onReceive(Context context, Intent intent) - { - fragment.refreshData(); - } - } - - @Override - protected void onDestroy() + public Habit getHabit() { - localBroadcastManager.unregisterReceiver(receiver); - super.onDestroy(); + return habit; } } diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java index 5d114c716..8ebbfb40a 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ShowHabitFragment.java @@ -33,18 +33,17 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.Button; -import android.widget.LinearLayout; import android.widget.Spinner; import android.widget.TextView; -import org.isoron.uhabits.helpers.ColorHelper; -import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.HabitBroadcastReceiver; import org.isoron.uhabits.R; import org.isoron.uhabits.ShowHabitActivity; import org.isoron.uhabits.commands.Command; import org.isoron.uhabits.dialogs.HistoryEditorDialog; +import org.isoron.uhabits.helpers.ColorHelper; import org.isoron.uhabits.helpers.ReminderHelper; +import org.isoron.uhabits.helpers.UIHelper; import org.isoron.uhabits.models.Habit; import org.isoron.uhabits.models.Score; import org.isoron.uhabits.tasks.BaseTask; @@ -53,7 +52,6 @@ import org.isoron.uhabits.views.HabitFrequencyView; import org.isoron.uhabits.views.HabitHistoryView; import org.isoron.uhabits.views.HabitScoreView; import org.isoron.uhabits.views.HabitStreakView; -import org.isoron.uhabits.views.RepetitionCountView; import org.isoron.uhabits.views.RingView; import java.util.LinkedList; @@ -78,6 +76,8 @@ public class ShowHabitFragment extends Fragment @Nullable private SharedPreferences prefs; + private int previousScoreInterval; + @Override public void onStart() { @@ -90,7 +90,7 @@ public class ShowHabitFragment extends Fragment { View view = inflater.inflate(R.layout.show_habit, container, false); activity = (ShowHabitActivity) getActivity(); - habit = activity.habit; + habit = activity.getHabit(); dataViews = new LinkedList<>(); @@ -102,19 +102,17 @@ public class ShowHabitFragment extends Fragment prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); int defaultScoreInterval = prefs.getInt("pref_score_view_interval", 1); if(defaultScoreInterval > 5 || defaultScoreInterval < 0) defaultScoreInterval = 1; + previousScoreInterval = defaultScoreInterval; setScoreBucketSize(defaultScoreInterval); + + sStrengthInterval.setSelection(defaultScoreInterval); sStrengthInterval.setOnItemSelectedListener(this); - dataViews.add((HabitStreakView) view.findViewById(R.id.streakView)); - dataViews.add((HabitStreakView) view.findViewById(R.id.smallStreakView)); dataViews.add((HabitScoreView) view.findViewById(R.id.scoreView)); dataViews.add((HabitHistoryView) view.findViewById(R.id.historyView)); dataViews.add((HabitFrequencyView) view.findViewById(R.id.punchcardView)); - - LinearLayout llRepetition = (LinearLayout) view.findViewById(R.id.llRepetition); - for(int i = 0; i < llRepetition.getChildCount(); i++) - dataViews.add((RepetitionCountView) llRepetition.getChildAt(i)); + dataViews.add((HabitStreakView) view.findViewById(R.id.streakView)); updateHeaders(view); @@ -145,11 +143,17 @@ public class ShowHabitFragment extends Fragment } setHasOptionsMenu(true); - refreshData(); return view; } + @Override + public void onResume() + { + super.onResume(); + refreshData(); + } + private void updateScoreRing(View view) { if(habit == null) return; @@ -175,7 +179,6 @@ public class ShowHabitFragment extends Fragment updateColor(view, R.id.tvStrength); updateColor(view, R.id.tvStreaks); updateColor(view, R.id.tvWeekdayFreq); - updateColor(view, R.id.tvCount); } private void updateColor(View view, int viewId) @@ -236,23 +239,24 @@ public class ShowHabitFragment extends Fragment new BaseTask() { @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { - if(dataViews == null) return null; + if(dataViews == null) return; updateScoreRing(getView()); + int count = 0; for(HabitDataView view : dataViews) + { view.refreshData(); - - return null; + onProgressUpdate(count++); + } } @Override - protected void onPostExecute(Void aVoid) + protected void onProgressUpdate(Integer... values) { if(dataViews == null) return; - for(HabitDataView view : dataViews) - view.invalidate(); + dataViews.get(values[0]).postInvalidate(); } }.execute(); @@ -267,17 +271,18 @@ public class ShowHabitFragment extends Fragment private void setScoreBucketSize(int position) { + if(scoreView == null) return; + int sizes[] = { 1, 7, 31, 92, 365 }; int size = sizes[position]; - if(scoreView != null) - { - scoreView.setBucketSize(size); - refreshData(); - } + scoreView.setBucketSize(size); + if(position != previousScoreInterval) refreshData(); if(prefs != null) prefs.edit().putInt("pref_score_view_interval", position).apply(); + + previousScoreInterval = position; } @Override diff --git a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java index ee808c3e7..37d2e16c9 100644 --- a/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java +++ b/app/src/main/java/org/isoron/uhabits/views/HabitStreakView.java @@ -91,7 +91,7 @@ public class HabitStreakView extends View implements HabitDataView dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM); dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); rect = new RectF(); - + maxStreakCount = 10; baseSize = getResources().getDimensionPixelSize(R.dimen.baseSize); } diff --git a/app/src/main/res/layout/show_habit.xml b/app/src/main/res/layout/show_habit.xml index 6505299c6..fd5da21b5 100644 --- a/app/src/main/res/layout/show_habit.xml +++ b/app/src/main/res/layout/show_habit.xml @@ -53,75 +53,6 @@ app:maxDiameter="80" app:textSize="@dimen/smallTextSize"/> - - - - - - - - - - - - - - - - - - - - - - - - - - From c51b1fd399cf4ea8c4752f074804ce55c71e685a Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Sun, 3 Apr 2016 07:51:54 -0400 Subject: [PATCH 19/26] Remove progress bar; switch to BaseTask --- .../uhabits/fragments/ListHabitsFragment.java | 1 - .../uhabits/loaders/HabitListLoader.java | 73 +++---------------- 2 files changed, 10 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index 62bbaaf47..f6f24dcc8 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -115,7 +115,6 @@ public class ListHabitsFragment extends Fragment loader.setListener(this); loader.setCheckmarkCount(helper.getButtonCount()); - loader.setProgressBar(progressBar); llHint.setOnClickListener(this); tvStarEmpty.setTypeface(helper.getFontawesome()); diff --git a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java index 48a736455..0f35ba44c 100644 --- a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java +++ b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java @@ -19,13 +19,9 @@ package org.isoron.uhabits.loaders; -import android.os.AsyncTask; -import android.os.Handler; -import android.view.View; -import android.widget.ProgressBar; - import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.models.Habit; +import org.isoron.uhabits.tasks.BaseTask; import java.util.HashMap; import java.util.List; @@ -37,9 +33,8 @@ public class HabitListLoader void onLoadFinished(); } - private AsyncTask currentFetchTask; + private BaseTask currentFetchTask; private int checkmarkCount; - private ProgressBar progressBar; private Listener listener; private Long lastLoadTimestamp; @@ -56,11 +51,6 @@ public class HabitListLoader this.includeArchived = includeArchived; } - public void setProgressBar(ProgressBar progressBar) - { - this.progressBar = progressBar; - } - public void setCheckmarkCount(int checkmarkCount) { this.checkmarkCount = checkmarkCount; @@ -98,7 +88,7 @@ public class HabitListLoader { if (currentFetchTask != null) currentFetchTask.cancel(true); - currentFetchTask = new AsyncTask() + currentFetchTask = new BaseTask() { public HashMap newHabits; public HashMap newCheckmarks; @@ -106,7 +96,7 @@ public class HabitListLoader public List newHabitList; @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { newHabits = new HashMap<>(); newCheckmarks = new HashMap<>(); @@ -136,12 +126,12 @@ public class HabitListLoader commit(); - if(!updateScoresAndCheckmarks) return null; + if(!updateScoresAndCheckmarks) return; int current = 0; for (Habit h : newHabitList) { - if (isCancelled()) return null; + if (isCancelled()) return; Long id = h.getId(); newScores.put(id, h.scores.getTodayValue()); @@ -149,8 +139,6 @@ public class HabitListLoader publishProgress(current++, newHabits.size()); } - - return null; } private void commit() @@ -161,26 +149,9 @@ public class HabitListLoader habitsList = newHabitList; } - @Override - protected void onPreExecute() - { - if(progressBar != null) - { - progressBar.setIndeterminate(false); - progressBar.setProgress(0); - progressBar.setVisibility(View.VISIBLE); - } - } - @Override protected void onProgressUpdate(Integer... values) { - if(progressBar != null) - { - progressBar.setMax(values[1]); - progressBar.setProgress(values[0]); - } - if(listener != null) listener.onLoadFinished(); } @@ -189,7 +160,6 @@ public class HabitListLoader { if (isCancelled()) return; - if(progressBar != null) progressBar.setVisibility(View.INVISIBLE); lastLoadTimestamp = DateHelper.getStartOfToday(); currentFetchTask = null; @@ -203,50 +173,27 @@ public class HabitListLoader public void updateHabit(final Long id) { - new AsyncTask() + new BaseTask() { @Override - protected Void doInBackground(Void... params) + protected void doInBackground() { long dateTo = DateHelper.getStartOfDay(DateHelper.getLocalTime()); long dateFrom = dateTo - (checkmarkCount - 1) * DateHelper.millisecondsInOneDay; Habit h = Habit.get(id); + if(h == null) return; + habits.put(id, h); scores.put(id, h.scores.getTodayValue()); checkmarks.put(id, h.checkmarks.getValues(dateFrom, dateTo)); - - return null; - } - - @Override - protected void onPreExecute() - { - new Handler().postDelayed(new Runnable() - { - @Override - public void run() - { - if (getStatus() == Status.RUNNING) - { - if(progressBar != null) - { - progressBar.setIndeterminate(true); - progressBar.setVisibility(View.VISIBLE); - } - } - } - }, 500); } @Override protected void onPostExecute(Void aVoid) { - if(progressBar != null) progressBar.setVisibility(View.GONE); - if(listener != null) listener.onLoadFinished(); - } }.execute(); } From 0de52d4fa369d94767c9b993788db29a8aa2f55c Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 05:42:27 -0400 Subject: [PATCH 20/26] BaseTask: move register/unregister to onPre/PostExecute --- .../unit/tasks/ImportDataTaskTest.java | 2 -- .../uhabits/loaders/HabitListLoader.java | 4 ++++ .../org/isoron/uhabits/tasks/BaseTask.java | 24 ++++++++++--------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java index 43546ab60..f134f6267 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/tasks/ImportDataTaskTest.java @@ -34,8 +34,6 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; diff --git a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java index 0f35ba44c..12a8e9e9f 100644 --- a/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java +++ b/app/src/main/java/org/isoron/uhabits/loaders/HabitListLoader.java @@ -164,6 +164,8 @@ public class HabitListLoader currentFetchTask = null; if(listener != null) listener.onLoadFinished(); + + super.onPostExecute(null); } }; @@ -194,6 +196,8 @@ public class HabitListLoader { if(listener != null) listener.onLoadFinished(); + + super.onPostExecute(null); } }.execute(); } diff --git a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java index f22ebb43d..b1cc0058b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java @@ -28,26 +28,28 @@ public abstract class BaseTask extends AsyncTask private static int activeTaskCount; @Override - protected final Void doInBackground(Void... params) + protected void onPreExecute() { - register(); - doInBackground(); - unregister(); - return null; + super.onPreExecute(); + activeTaskCount++; } - protected abstract void doInBackground(); - - protected void register() + @Override + protected void onPostExecute(Void aVoid) { - activeTaskCount++; + activeTaskCount--; + super.onPostExecute(null); } - protected void unregister() + @Override + protected final Void doInBackground(Void... params) { - activeTaskCount--; + doInBackground(); + return null; } + protected abstract void doInBackground(); + public static void waitForTasks(long timeout) throws TimeoutException, InterruptedException { From 96c46b655d0e29153434935f4f95feabd0b84771 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 05:43:07 -0400 Subject: [PATCH 21/26] Minor refactoring --- .../org/isoron/uhabits/unit/views/HabitHistoryViewTest.java | 4 +--- .../main/java/org/isoron/uhabits/models/CheckmarkList.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java index 787dfc11f..5957f4a7a 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/HabitHistoryViewTest.java @@ -21,7 +21,6 @@ package org.isoron.uhabits.unit.views; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; -import android.util.Log; import org.isoron.uhabits.helpers.DateHelper; import org.isoron.uhabits.models.Habit; @@ -53,14 +52,13 @@ public class HabitHistoryViewTest extends ViewTest view = new HabitHistoryView(targetContext); view.setHabit(habit); - refreshData(view); measureView(dpToPixels(300), dpToPixels(100), view); + refreshData(view); } @Test public void render() throws Throwable { - Log.d("HabitHistoryViewTest", String.format("height=%d", dpToPixels(100))); assertRenders(view, "HabitHistoryView/render.png"); } diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 865e4c358..80b565c9c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -207,10 +207,10 @@ public class CheckmarkList try { SQLiteStatement statement = db.compileStatement(query); - statement.bindString(1, habit.getId().toString()); for (int i = 0; i < timestamps.length; i++) { + statement.bindLong(1, habit.getId()); statement.bindLong(2, timestamps[i]); statement.bindLong(3, values[i]); statement.execute(); From cbc8cbfea3844067c40ccb9ec0d9f67e66a7d5d4 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 06:02:56 -0400 Subject: [PATCH 22/26] Fix scores with null habit --- app/src/main/java/org/isoron/uhabits/models/ScoreList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java index 06331885d..eca89b9e0 100644 --- a/app/src/main/java/org/isoron/uhabits/models/ScoreList.java +++ b/app/src/main/java/org/isoron/uhabits/models/ScoreList.java @@ -164,10 +164,10 @@ public class ScoreList try { SQLiteStatement statement = db.compileStatement(query); - statement.bindString(1, habit.getId().toString()); for (int i = 0; i < timestamps.length; i++) { + statement.bindLong(1, habit.getId()); statement.bindLong(2, timestamps[i]); statement.bindLong(3, values[i]); statement.execute(); From 9891139b5a98bba95b0e5fa76213e6b6910441ce Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 06:43:42 -0400 Subject: [PATCH 23/26] Fix tests on ICS --- .../androidTest/java/org/isoron/uhabits/BaseTest.java | 9 ++++++++- .../org/isoron/uhabits/unit/HabitsApplicationTest.java | 4 ++++ .../java/org/isoron/uhabits/unit/views/ViewTest.java | 2 +- app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java | 4 ++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java index 1fac47da5..a73f5cd9d 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/BaseTest.java @@ -20,6 +20,7 @@ package org.isoron.uhabits; import android.content.Context; +import android.os.Build; import android.os.Looper; import android.support.test.InstrumentationRegistry; @@ -54,6 +55,12 @@ public class BaseTest protected void waitForAsyncTasks() throws InterruptedException, TimeoutException { - BaseTask.waitForTasks(30000); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + { + Thread.sleep(1000); + return; + } + + BaseTask.waitForTasks(10000); } } diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java index 8bd20370f..6c1e669fb 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/HabitsApplicationTest.java @@ -19,6 +19,7 @@ package org.isoron.uhabits.unit; +import android.os.Build; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; @@ -38,6 +39,9 @@ public class HabitsApplicationTest @Test public void getLogcat() throws IOException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + return; + String msg = "LOGCAT TEST"; new RuntimeException(msg).printStackTrace(); diff --git a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java index b6099f9a6..d1953ff58 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/unit/views/ViewTest.java @@ -40,7 +40,7 @@ import static junit.framework.Assert.fail; public class ViewTest extends BaseTest { - protected static final double SIMILARITY_CUTOFF = 0.08; + protected static final double SIMILARITY_CUTOFF = 0.09; public static final int HISTOGRAM_BIN_SIZE = 8; protected void measureView(int width, int height, View view) diff --git a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java index b1cc0058b..d9542c84b 100644 --- a/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java +++ b/app/src/main/java/org/isoron/uhabits/tasks/BaseTask.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.tasks; import android.os.AsyncTask; +import android.os.Build; import java.util.concurrent.TimeoutException; @@ -53,6 +54,9 @@ public abstract class BaseTask extends AsyncTask public static void waitForTasks(long timeout) throws TimeoutException, InterruptedException { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) + throw new UnsupportedOperationException("waitForTasks requires API 16+"); + int poolInterval = 100; while(timeout > 0) From 83db9fe2f2b08c62a5ee2633bf11f21095f03a70 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 06:46:16 -0400 Subject: [PATCH 24/26] Hide progress bar on startup --- .../java/org/isoron/uhabits/fragments/ListHabitsFragment.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java index f6f24dcc8..4c8bc4190 100644 --- a/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java +++ b/app/src/main/java/org/isoron/uhabits/fragments/ListHabitsFragment.java @@ -106,7 +106,9 @@ public class ListHabitsFragment extends Fragment listView = (DragSortListView) view.findViewById(R.id.listView); llButtonsHeader = (LinearLayout) view.findViewById(R.id.llButtonsHeader); llEmpty = view.findViewById(R.id.llEmpty); + progressBar = (ProgressBar) view.findViewById(R.id.progressBar); + progressBar.setVisibility(View.GONE); selectedPositions = new LinkedList<>(); loader = new HabitListLoader(); From 8b2f31c44a811cec16ab3142b75c3929d11b3a35 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 06:53:55 -0400 Subject: [PATCH 25/26] Update widget preview --- .../res/drawable/widget_preview_streaks.png | Bin 6793 -> 35263 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/main/res/drawable/widget_preview_streaks.png b/app/src/main/res/drawable/widget_preview_streaks.png index 9977ca6f030105914ec4b2aa1de1a0467bf32d5f..b7771c2b82a0a84954e36465963d16d85a5a0582 100644 GIT binary patch literal 35263 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 afqoaBX=xk!q91%)f^@YFQP~&nKKeJ&*Qcid From e2e9e2e27a2ba3826b49bbedf02f4abc07a33cf3 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Mon, 4 Apr 2016 06:54:12 -0400 Subject: [PATCH 26/26] Remove debug code --- app/src/main/java/org/isoron/uhabits/views/RingView.java | 2 -- .../java/org/isoron/uhabits/widgets/BaseWidgetProvider.java | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/views/RingView.java b/app/src/main/java/org/isoron/uhabits/views/RingView.java index f599031f2..7007d9924 100644 --- a/app/src/main/java/org/isoron/uhabits/views/RingView.java +++ b/app/src/main/java/org/isoron/uhabits/views/RingView.java @@ -29,7 +29,6 @@ import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.util.AttributeSet; -import android.util.Log; import android.view.View; import org.isoron.uhabits.R; @@ -98,7 +97,6 @@ public class RingView extends View fadedTextColor = getResources().getColor(R.color.fadedTextColor); textSize = getResources().getDimension(R.dimen.smallTextSize); - Log.d("RingView", String.format("textSize=%f", textSize)); rect = new RectF(); } diff --git a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java index b1d75f418..b75493a76 100644 --- a/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java +++ b/app/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.java @@ -238,7 +238,7 @@ public abstract class BaseWidgetProvider extends AppWidgetProvider remoteViews.setTextViewText(R.id.label, habit.name); remoteViews.setImageViewBitmap(R.id.imageView, drawingCache); - savePreview(context, widgetId, drawingCache); + //savePreview(context, widgetId, drawingCache); PendingIntent onClickIntent = getOnClickPendingIntent(context, habit); if(onClickIntent != null) remoteViews.setOnClickPendingIntent(R.id.imageView, onClickIntent);