From 3b86a17b49c05b1bdfe25e7ae164e6da6bdfe801 Mon Sep 17 00:00:00 2001 From: "Alinson S. Xavier" Date: Tue, 29 Dec 2020 16:20:45 -0600 Subject: [PATCH] Simplify StreakList --- .../views/common/StreakChart/render.png | Bin 14607 -> 15808 bytes .../common/StreakChart/renderSmallSize.png | Bin 2438 -> 3145 bytes .../common/StreakChart/renderTransparent.png | Bin 14607 -> 15808 bytes .../isoron/uhabits/core/models/EntryList.kt | 16 ++ .../org/isoron/uhabits/core/models/Habit.kt | 7 +- .../isoron/uhabits/core/models/HabitList.java | 4 - .../uhabits/core/models/ModelFactory.kt | 1 - .../isoron/uhabits/core/models/ScoreList.kt | 5 + .../uhabits/core/models/StreakList.java | 183 ------------------ .../isoron/uhabits/core/models/StreakList.kt | 65 +++++++ .../uhabits/core/models/StreakListTest.java | 65 +------ 11 files changed, 100 insertions(+), 246 deletions(-) delete mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java create mode 100644 android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.kt diff --git a/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/render.png b/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/render.png index 657d433faf684d47fc00a040995a126c591b913e..ee64f761d8ec8b748388ba23c394f989c694a553 100644 GIT binary patch literal 15808 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 literal 14607 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 b7VCGriRRiJinicGjnD<%tLTDr){p)drC1x4 diff --git a/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/renderSmallSize.png b/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/renderSmallSize.png index 4014e47cf5e28fffacb270455e05bc02b62f7846..7f552d377a1da320877771823d6f33bf396f8fe4 100644 GIT binary patch literal 3145 zcmbtXXH?VKw*Mo9UJ?a@C=w!w3Kpa%v~a;tVyMw5FkpaSK?TA@L6E8hLJLKZHi$|u z0hAIDf}!PIK&%jg;0z$01QhARjq_&a-Fv^jz1BJVeA#Qa-!3QK5sQ|QRF(t)K*rX_ z%2~jBe;jd9!Ci%~E)XyglBKPyxPZdMeJB7R4cJ z`=ALFXo3r^zbD+K5S6K4l$8 zGQD(ly@|gq)YbMRi4!8VDhg^&Y z>Ac^xo>%nKAojb)zWvHGGu@fHuTf&xa3Xv)sy~z$zQ91=y4`ie!T;-vp?3Oh)(Z8P zhd=>gKk?@`!yR(5y}k*L&I4Ki&_PDo&Rpxd*Z{%L3oe3hDLh*YFD(4bE-Qy!sWY+u?yPsXO|0w72y+6N!+`<6sZ)|I@p>Ubq5vEQ?63s zNy{YBy;0I+-t)U8UUu--+ohqO+8aYy1v1wg4u!KmZ8eol*Wf?Xk$cIm3+wjmGU23d zO^rwrn1#zPTgmv4xmfOBI?7$RL5_>NkL0zk4Db|-6~AMc(CJeHy_nv-K*nk?=gDqp zSX^Igj0zeYqmTt1lFx$fHgBzdZLIXjHax!eV5{b0=zLLm_;sPr$+>e_vzczFOCH*d(@z;S&A(@;xsrA;j$eMSQg%}knLZo+rca;Iv zbObVC({v)@O?H;O!6Dw*TJHi{vMQmATwFCP516$2ZWsXE%`Ib|dDHYWY7$#4J<&>&Uj67>=WI{3ZWNTQZC~lm-oK@c%u#-Te2`?5;&94UU%zQ$nA=sS`*s5l zj?v~m_n&tmMa;IJdEqs%{&cyX(b!0C_;};gUD8)l%RM)^uIg$^day)7^-;TX6fDIo zT4vxm(5<$^KxA=|Zh&^cc123-I;QrO>kZo~Wqqw?#KCK^znq4YSd`jJSLM**KZj^Qb*0+~<Van!xucAp#p=}@gH8YK4YWV_fsk$bj=eweZ z+I9`CWaAe&ZR6p9`FLB=c!&hMYaS+A=f=8>s~qN?>|mJAFR!KSMs+8j#n=^4dfJv< zILoD~2T{_`1*oT)d^>ZnN^6nxeMB>te)$aogBj>iI85TR{o>S*JS7E+=r~r?yl^lE zKKppR3rPZvHBACQ>cloWN)OCljrgWbBc6Vw9<^`ZkW7z9z$e^*vE`@3IQUk zy`T+Q?eQgJo0*fr?^T`F`&`eFDIML!7nc|K(!;EUqvjq_4+j)Ew3$6d(Ker@)&pj~ zvg*7ChnTjAzlYCXSaQ^pX>E@xRn2)&FIkw$mbLW3UV2n?b#@pX z4ms)Wa1nQ9@Cl%0!ON;HtXae@zWmoVIn|LZ#)65}57KKA=Lm`B%+1X`%PCe6%D%}* z^Y&W$lbhXN@850~R+idpiNoO>B-IL1AEDko|K(Y6vDq=DFpm`iuklUnY{lW%*B&GYun5i`HIbS3Eo%#ZG8Ua!Dmt}2CgJsR8}To_KV#4 z3I%M*K7-tIX{rW5N*C$psDd18J#X&@echz)aTsDq)y@*~Z@f9|Qs%D6Fik^h>r33J{_7Es|Li%P><@ z6*ik~%H6kbUm*W8D&yfpxM7wjFZKT6KO6#Edc7}o3U5H>W3ABWC$+VZC<_Z`1OcTk zC9(%>X+ingu5rBozlb z|CAl1KB{wndneu?wrRVA$%3fI>Ydx#uPd#+ZNHBofZLkz(Y==L-jmbUa=xF8>@O=T zd#$|D{0CmG4MB^`oD5QzkVLN$?lS6@M_>V}8$n0&+wzuLT< z9A$QVsiC4(9i4uf1F;cvsHFt|hx-5BB>yUfKL&#xgFQJp*_&+fGC<`2=EZ+VsR#Ex zH)#G5x%KRA@NX+(!gA4C{6ZQR{0#tG MYphkZrC0R70Fm9r!T#x_Pui#3F7!?lEDtQ8e{ zZG&NktQkYuMy9bdme;ty`rJS2zVH3xp3hl6&*z-yIp61Vp6C0V$G=&ciGXFnAP`6d zi7>GS){gH(kRO=K9f<^B;kspQW(*=y^7Lg?@YaeQ5%_qeymF|Xbg3t!XUjL;cicWquaETL!1qjO|Z>#8<8k#=eJ zwP%1%K3Owe?G)wMrS_QpDy<53CRa?r2yk>f^Pk&O3w41sCY_Z?JY2*wtLa{3C{juI z-3ZYYN|I{sPI$#ugbE16k&&nIJkv6Gzu6weZ8u8bQONbT&j}o3&%2_iCo{NdJfN*O zBM=Wa*FHhs6Hv&9$e)D|H^U-Vdt%;7*}-l)4@1CDpRS20PtwL|uPchmijN(e9};0BkIoJHz__sR3966l{M(cCXDR7iMPUsk?Sj5t!wIn zcJpb((z0juP`P~$D*c@b;*Z`5wyGFjTV0A@A6y?+-AOroPPAz=oo&f7)WvT{S)@^& z?pRF4wGXnLcXCEr&f!NDk#E!;9m1H+*8(%dGp(_AF8!_$*k~d9;!v7-*Y5m`8NOki zUQSZ+q1|P%A}XkFf`+;@PP@D>xndow+>>Ll_c10pX4D2(y#Jei6+0g0C#_Mo&#t>x zcW*1k$SLS$F%Az;?~HFfnSXjH)P3ezVdt=rM4O9-wS-?X8dsrX0 zHt16PrlG@CvVJ#)*7VV~-_|WmJB|dNtezd3P3bA`v5Nci&$;rfAxe|?xX8p?uc^1| zDD^CV%7bOc^J-zA>lOTaW~(psn->p_68NUX9tqDMpKtDA`{S3siddoa*keA7wd>mH z$z0uvW|G3cVE4LqvcmbsjdCRtKV^!hOySkj-(yx- zGSb6ZxDAR5HsaP+_l!Edl+bU}oND6-^UbQR_xECaX*xqAhyj;cEZ{cAQKIGB=pT@-;_BRX^aLDvIi1(>S4% zAk`YBzUc2$qt9a?tNk^S>n@0&m-_%j@VXK77Xe635NKM+s-K!Z?JvqT4I{d9{r$-w zYGX2FQ@bL&?SWeIQT-<7J$jetDG9vt(T+Q>;#JqD4SzD+id| zJ|==8;LFm{n#HJwS~+Gy7>W=X(-`=ZiVtNs8@HdDgDoy5F8bhYL>D&u?(c|?w6URZ z@OI2JY{|!J#|0HJb8^>bB6l!neklK7I`gg-p1KkI3H6Y{~T0XdOIy0UDC0 z)OQ347HCdW{i7aW0p@JQ;9}#_A!qz2%oHVck0IP@P)uc2EgbpWMsbeLb5VD5|^FQEUTQFqPgUg&w{6Jf@;bF~{~iDS$4s4uJz*)d;W!+gHq5ls&PtuqF)!|a zO**?MhL9J0*b;NGU?rHdlV#ZFZTr~7m`1mC_;bi6HY&A1%t*A>%d9ksvs08e|5`+u} zKiCtZ&;xZ6PR;6l`%Hi1@9m99o@Vcm<+ja1y6Qle({p##;u&s*= zykfYF!-)Wl0X7mz1=ZEnXoZH)K=_dJ`%1X)Mouno&ZWY9Hs|e4O{2TWWU1ITHOt@# z55B6GB_)XDf#EY7V!byh*;mfy*h3*thMBKU%9=oj;={uTfrhB$ zSk_E*obvz3^*^dC@XFs?hlHN$?%sVyAzl+LCH$9K{8LCh(UaWAAENC>_}q>TX=bb* zlLVL6)j38qxmG#Gy@Sni0Wcwg{}6v1XHYA#6^$9IHiG6EL7^n{z~Erpi=>-C9k2k< z9!&voCKvv$;~%1!9dD#Ir8~D3pXm4tcm9_!QOL+}RPzes&5oA(>wn=VjF2Bb{XbLX Y!URJnQ5$=kmib)`X=-UgG`=47Z!hv}82|tP diff --git a/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/renderTransparent.png b/android/uhabits-android/src/androidTest/assets/views/common/StreakChart/renderTransparent.png index 657d433faf684d47fc00a040995a126c591b913e..ee64f761d8ec8b748388ba23c394f989c694a553 100644 GIT binary patch literal 15808 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 literal 14607 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 b7VCGriRRiJinicGjnD<%tLTDr){p)drC1x4 diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt index c2463103b..c7afddada 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/EntryList.kt @@ -25,10 +25,12 @@ import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.core.utils.* import java.util.* +import javax.annotation.concurrent.* import kotlin.collections.HashMap import kotlin.collections.set import kotlin.math.* +@ThreadSafe open class EntryList { private val entriesByTimestamp: HashMap = HashMap() @@ -37,6 +39,7 @@ open class EntryList { * Returns the entry corresponding to the given timestamp. If no entry with such timestamp * has been previously added, returns Entry(timestamp, UNKNOWN). */ + @Synchronized open fun get(timestamp: Timestamp): Entry { return entriesByTimestamp[timestamp] ?: Entry(timestamp, UNKNOWN) } @@ -46,6 +49,7 @@ open class EntryList { * newest entry, and the last element corresponds to the oldest. The interval endpoints are * included. */ + @Synchronized open fun getByInterval(from: Timestamp, to: Timestamp): List { val result = mutableListOf() if (from.isNewerThan(to)) return result @@ -61,6 +65,7 @@ open class EntryList { * Adds the given entry to the list. If another entry with the same timestamp already exists, * replaces it. */ + @Synchronized open fun add(entry: Entry) { entriesByTimestamp[entry.timestamp] = entry } @@ -69,6 +74,7 @@ open class EntryList { * Returns all entries whose values are known, sorted by timestamp. The first element * corresponds to the newest entry, and the last element corresponds to the oldest. */ + @Synchronized open fun getKnown(): List { return entriesByTimestamp.values.sortedBy { it.timestamp }.reversed() } @@ -82,6 +88,7 @@ open class EntryList { * entries. For numerical habits, the value is the total sum. The field [firstWeekday] is only * relevant when grouping by week. */ + @Synchronized open fun groupBy( original: List, field: DateUtils.TruncateField, @@ -117,6 +124,7 @@ open class EntryList { * For boolean habits, this function creates additional entries (with value YES_AUTO) according * to the frequency of the habit. For numerical habits, this function simply copies all entries. */ + @Synchronized open fun recomputeFrom( originalEntries: EntryList, frequency: Frequency, @@ -137,6 +145,7 @@ open class EntryList { /** * Removes all known entries. */ + @Synchronized open fun clear() { entriesByTimestamp.clear() } @@ -153,6 +162,7 @@ open class EntryList { * * @return total number of checkmarks by month versus day of week */ + @Synchronized fun computeWeekdayFrequency(isNumerical: Boolean): HashMap> { val entries = getKnown() val map = hashMapOf>() @@ -185,6 +195,7 @@ open class EntryList { * are included. */ @Deprecated("") + @Synchronized fun getValues(from: Timestamp, to: Timestamp): IntArray { if (from.isNewerThan(to)) throw IllegalArgumentException() val nDays = from.daysUntil(to) + 1 @@ -199,6 +210,7 @@ open class EntryList { } @Deprecated("") + @Synchronized fun getAllValues(): IntArray { val entries = getKnown() if (entries.isEmpty()) return IntArray(0) @@ -209,6 +221,7 @@ open class EntryList { } @Deprecated("") + @Synchronized open fun getThisWeekValue(firstWeekday: Int, isNumerical: Boolean): Int { return getThisIntervalValue( truncateField = DateUtils.TruncateField.WEEK_NUMBER, @@ -218,6 +231,7 @@ open class EntryList { } @Deprecated("") + @Synchronized open fun getThisMonthValue(isNumerical: Boolean): Int { return getThisIntervalValue( truncateField = DateUtils.TruncateField.MONTH, @@ -227,6 +241,7 @@ open class EntryList { } @Deprecated("") + @Synchronized open fun getThisQuarterValue(isNumerical: Boolean): Int { return getThisIntervalValue( truncateField = DateUtils.TruncateField.QUARTER, @@ -236,6 +251,7 @@ open class EntryList { } @Deprecated("") + @Synchronized open fun getThisYearValue(isNumerical: Boolean): Int { return getThisIntervalValue( truncateField = DateUtils.TruncateField.YEAR, diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt index c7a338954..797697c0f 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.kt @@ -70,7 +70,6 @@ data class Habit( } fun recompute() { - streaks.recompute() computedEntries.recomputeFrom( originalEntries = originalEntries, frequency = frequency, @@ -90,6 +89,12 @@ data class Habit( from = from, to = to, ) + + streaks.recompute( + computedEntries, + from, + to, + ) } fun copyFrom(other: Habit) { diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java index 631ad2918..323b3a635 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/HabitList.java @@ -176,10 +176,6 @@ public abstract class HabitList implements Iterable public void repair() { - for (Habit h : this) - { - h.getStreaks().recompute(); - } } /** diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ModelFactory.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ModelFactory.kt index 4bcf77b18..d7f06b3d0 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ModelFactory.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ModelFactory.kt @@ -36,7 +36,6 @@ interface ModelFactory { originalEntries = buildOriginalEntries(), computedEntries = buildComputedEntries(), ) - streaks.setHabit(habit) return habit } fun buildComputedEntries(): EntryList diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.kt index 4ca19bd07..8f5620e3d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.kt +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/ScoreList.kt @@ -20,8 +20,10 @@ package org.isoron.uhabits.core.models import org.isoron.uhabits.core.models.Score.Companion.compute import java.util.* +import javax.annotation.concurrent.* import kotlin.math.* +@ThreadSafe class ScoreList { private val map = HashMap() @@ -30,6 +32,7 @@ class ScoreList { * Returns the score for a given day. If the timestamp given happens before the first * repetition of the habit or after the last computed score, returns a score with value zero. */ + @Synchronized operator fun get(timestamp: Timestamp): Score { return map[timestamp] ?: Score(timestamp, 0.0) } @@ -41,6 +44,7 @@ class ScoreList { * included. The list is ordered by timestamp (decreasing). That is, the first score * corresponds to the newest timestamp, and the last score corresponds to the oldest timestamp. */ + @Synchronized fun getByInterval( fromTimestamp: Timestamp, toTimestamp: Timestamp, @@ -58,6 +62,7 @@ class ScoreList { /** * Recomputes all scores between the provided [from] and [to] timestamps. */ + @Synchronized fun recompute( frequency: Frequency, isNumerical: Boolean, diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java deleted file mode 100644 index 66ae0da27..000000000 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2016 Álinson Santos Xavier - * - * This file is part of Loop Habit Tracker. - * - * Loop Habit Tracker is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Loop Habit Tracker is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program. If not, see . - */ - -package org.isoron.uhabits.core.models; - -import androidx.annotation.*; - -import org.isoron.uhabits.core.utils.*; - -import java.util.*; - -/** - * The collection of {@link Streak}s that belong to a habit. - *

- * This list is populated automatically from the list of repetitions. - */ -public class StreakList -{ - protected Habit habit; - - protected ModelObservable observable = new ModelObservable(); - - ArrayList list = new ArrayList<>(); - - public void setHabit(Habit habit) - { - this.habit = habit; - } - - public List getAll() - { - rebuild(); - return new LinkedList<>(list); - } - - @NonNull - public List getBest(int limit) - { - List streaks = getAll(); - Collections.sort(streaks, (s1, s2) -> s2.compareLonger(s1)); - streaks = streaks.subList(0, Math.min(streaks.size(), limit)); - Collections.sort(streaks, (s1, s2) -> s2.compareNewer(s1)); - return streaks; - } - - @Nullable - public Streak getNewestComputed() - { - Streak newest = null; - - for (Streak s : list) - if (newest == null || s.getEnd().isNewerThan(newest.getEnd())) - newest = s; - - return newest; - - } - - @NonNull - public ModelObservable getObservable() - { - return observable; - } - - public void recompute() - { - list.clear(); - observable.notifyListeners(); - } - - public synchronized void rebuild() - { - Timestamp today = DateUtils.getTodayWithOffset(); - Timestamp beginning = findBeginning(); - if (beginning == null || beginning.isNewerThan(today)) return; - - int checks[] = habit.getComputedEntries().getValues(beginning, today); - List streaks = checkmarksToStreaks(beginning, checks); - - removeNewestComputed(); - add(streaks); - } - - /** - * Converts a list of checkmark values to a list of streaks. - * - * @param beginning the timestamp corresponding to the first checkmark - * value. - * @param checks the checkmarks values, ordered by decreasing timestamp. - * @return the list of streaks. - */ - @NonNull - protected List checkmarksToStreaks(Timestamp beginning, int[] checks) - { - ArrayList transitions = getTransitions(beginning, checks); - - List streaks = new LinkedList<>(); - for (int i = 0; i < transitions.size(); i += 2) - { - Timestamp start = transitions.get(i); - Timestamp end = transitions.get(i + 1); - streaks.add(new Streak(start, end)); - } - - return streaks; - } - - /** - * Finds the place where we should start when recomputing the streaks. - * - * @return - */ - @Nullable - protected Timestamp findBeginning() - { - Streak newestStreak = getNewestComputed(); - if (newestStreak != null) return newestStreak.getStart(); - - List entries = habit.getOriginalEntries().getKnown(); - if(entries.isEmpty()) return null; - return entries.get(entries.size() - 1).getTimestamp(); - } - - /** - * Returns the timestamps where there was a transition from performing a - * habit to not performing a habit, and vice-versa. - * - * @param beginning the timestamp for the first checkmark - * @param checks the checkmarks, ordered by decreasing timestamp - * @return the list of transitions - */ - @NonNull - protected ArrayList getTransitions(Timestamp beginning, int[] checks) - { - ArrayList list = new ArrayList<>(); - Timestamp current = beginning; - list.add(current); - - for (int i = 1; i < checks.length; i++) - { - current = current.plus(1); - int j = checks.length - i - 1; - - if ((checks[j + 1] <= 0 && checks[j] > 0)) list.add(current); - if ((checks[j + 1] > 0 && checks[j] <= 0)) list.add(current.minus(1)); - } - - if (list.size() % 2 == 1) list.add(current); - - return list; - } - - protected void add(@NonNull List streaks) - { - list.addAll(streaks); - Collections.sort(list, (s1, s2) -> s2.compareNewer(s1)); - observable.notifyListeners(); - - } - - protected void removeNewestComputed() - { - Streak newest = getNewestComputed(); - if (newest != null) list.remove(newest); - - } -} diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.kt b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.kt new file mode 100644 index 000000000..c0db79f1c --- /dev/null +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/StreakList.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Álinson Santos Xavier + * + * This file is part of Loop Habit Tracker. + * + * Loop Habit Tracker is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * Loop Habit Tracker is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ +package org.isoron.uhabits.core.models + +import javax.annotation.concurrent.* +import kotlin.math.* + +@ThreadSafe +class StreakList { + private val list = ArrayList() + + @Synchronized + fun getBest(limit: Int): List { + list.sortWith { s1: Streak, s2: Streak -> s2.compareLonger(s1) } + return list.subList(0, min(list.size, limit)).apply { + sortWith { s1: Streak, s2: Streak -> s2.compareNewer(s1) } + } + } + + @Synchronized + fun recompute( + computedEntries: EntryList, + from: Timestamp, + to: Timestamp, + ) { + list.clear() + val timestamps = computedEntries + .getByInterval(from, to) + .filter { it.value > 0 } + .map { it.timestamp } + .toTypedArray() + + if (timestamps.isEmpty()) return + + var begin = timestamps[0] + var end = timestamps[0] + for (i in 1 until timestamps.size) { + val current = timestamps[i] + if (current == begin.minus(1)) { + begin = current + } else { + list.add(Streak(begin, end)) + begin = current + end = current + } + } + list.add(Streak(begin, end)) + } +} \ No newline at end of file diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/StreakListTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/StreakListTest.java index b33b024e5..833e3f69c 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/StreakListTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/StreakListTest.java @@ -36,12 +36,8 @@ public class StreakListTest extends BaseUnitTest private StreakList streaks; - private long day; - private Timestamp today; - private ModelObservable.Listener listener; - @Override public void setUp() throws Exception { @@ -51,49 +47,14 @@ public class StreakListTest extends BaseUnitTest habit.recompute(); streaks = habit.getStreaks(); - streaks.rebuild(); - - listener = mock(ModelObservable.Listener.class); - streaks.getObservable().addListener(listener); today = DateUtils.getToday(); } - @Test - public void testFindBeginning_withEmptyHistory() - { - Habit habit2 = fixtures.createEmptyHabit(); - Timestamp beginning = habit2.getStreaks().findBeginning(); - assertNull(beginning); - } - - @Test - public void testFindBeginning_withLongHistory() - { - streaks.rebuild(); - streaks.recompute(); - assertThat(streaks.findBeginning(), equalTo(today.minus(120))); - } - - @Test - public void testGetAll() throws Exception - { - List all = streaks.getAll(); - - assertThat(all.size(), equalTo(22)); - - assertThat(all.get(3).getEnd(), equalTo(today.minus(7))); - assertThat(all.get(3).getStart(), equalTo(today.minus(10))); - - assertThat(all.get(17).getEnd(), equalTo(today.minus(89))); - assertThat(all.get(17).getStart(), equalTo(today.minus(91))); - } - @Test public void testGetBest() throws Exception { List best = streaks.getBest(4); assertThat(best.size(), equalTo(4)); - assertThat(best.get(0).getLength(), equalTo(4)); assertThat(best.get(1).getLength(), equalTo(3)); assertThat(best.get(2).getLength(), equalTo(5)); @@ -101,30 +62,20 @@ public class StreakListTest extends BaseUnitTest best = streaks.getBest(2); assertThat(best.size(), equalTo(2)); - assertThat(best.get(0).getLength(), equalTo(5)); assertThat(best.get(1).getLength(), equalTo(6)); } @Test - public void testInvalidateNewer() + public void testGetBest_withUnknowns() { - Streak s = streaks.getNewestComputed(); - assertThat(s.getEnd(), equalTo(today)); - - streaks.recompute(); - verify(listener).onModelChange(); - - s = streaks.getNewestComputed(); - assertNull(s); - } + habit.getOriginalEntries().clear(); + habit.getOriginalEntries().add(new Entry(today, Entry.YES_MANUAL)); + habit.getOriginalEntries().add(new Entry(today.minus(5), Entry.NO)); + habit.recompute(); - @Test - public void testToString() throws Exception - { - Timestamp time = Timestamp.ZERO.plus(100); - Streak streak = new Streak(time, time.plus(10)); - assertThat(streak.toString(), equalTo( - "{start: 1970-04-11, end: 1970-04-21}")); + List best = streaks.getBest(5); + assertThat(best.size(), equalTo(1)); + assertThat(best.get(0).getLength(), equalTo(1)); } } \ No newline at end of file