mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 21:18:51 -06:00
Implement all relevant card views for habit groups
This commit is contained in:
@@ -110,6 +110,15 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".widgets.activities.BooleanHabitPickerDialog"
|
||||||
|
android:exported="true"
|
||||||
|
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".widgets.activities.NumericalHabitPickerDialog"
|
android:name=".widgets.activities.NumericalHabitPickerDialog"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -25,8 +25,29 @@ class ShowHabitGroupView(context: Context) : FrameLayout(context) {
|
|||||||
binding.subtitleCard.setState(data.subtitle)
|
binding.subtitleCard.setState(data.subtitle)
|
||||||
binding.overviewCard.setState(data.overview)
|
binding.overviewCard.setState(data.overview)
|
||||||
binding.notesCard.setState(data.notes)
|
binding.notesCard.setState(data.notes)
|
||||||
|
binding.targetCard.setState(data.target)
|
||||||
binding.streakCard.setState(data.streaks)
|
binding.streakCard.setState(data.streaks)
|
||||||
binding.scoreCard.setState(data.scores)
|
binding.scoreCard.setState(data.scores)
|
||||||
|
binding.barCard.setState(data.bar)
|
||||||
|
binding.frequencyCard.setState(data.frequency)
|
||||||
|
if (!data.isBoolean) {
|
||||||
|
binding.streakCard.visibility = GONE
|
||||||
|
if (!data.isNumerical) {
|
||||||
|
binding.barCard.visibility = GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!data.isNumerical) {
|
||||||
|
binding.targetCard.visibility = GONE
|
||||||
|
}
|
||||||
|
if (data.isEmpty) {
|
||||||
|
binding.targetCard.visibility = GONE
|
||||||
|
binding.barCard.visibility = GONE
|
||||||
|
binding.streakCard.visibility = GONE
|
||||||
|
binding.overviewCard.visibility = GONE
|
||||||
|
binding.scoreCard.visibility = GONE
|
||||||
|
binding.frequencyCard.visibility = GONE
|
||||||
|
binding.noSubHabitsCard.visibility = VISIBLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setListener(presenter: ShowHabitGroupPresenter) {
|
fun setListener(presenter: ShowHabitGroupPresenter) {
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
|||||||
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
import org.isoron.uhabits.core.preferences.WidgetPreferences
|
||||||
import org.isoron.uhabits.widgets.WidgetUpdater
|
import org.isoron.uhabits.widgets.WidgetUpdater
|
||||||
|
|
||||||
|
class BooleanHabitPickerDialog : HabitPickerDialog() {
|
||||||
|
override fun shouldHideNumerical() = true
|
||||||
|
override fun getEmptyMessage() = R.string.no_boolean_habits
|
||||||
|
}
|
||||||
|
|
||||||
class NumericalHabitPickerDialog : HabitPickerDialog() {
|
class NumericalHabitPickerDialog : HabitPickerDialog() {
|
||||||
override fun shouldHideBoolean() = true
|
override fun shouldHideBoolean() = true
|
||||||
override fun getEmptyMessage() = R.string.no_numerical_habits
|
override fun getEmptyMessage() = R.string.no_numerical_habits
|
||||||
@@ -44,6 +49,7 @@ open class HabitPickerDialog : Activity() {
|
|||||||
private lateinit var widgetPreferences: WidgetPreferences
|
private lateinit var widgetPreferences: WidgetPreferences
|
||||||
private lateinit var widgetUpdater: WidgetUpdater
|
private lateinit var widgetUpdater: WidgetUpdater
|
||||||
|
|
||||||
|
protected open fun shouldHideNumerical() = false
|
||||||
protected open fun shouldHideBoolean() = false
|
protected open fun shouldHideBoolean() = false
|
||||||
protected open fun getEmptyMessage() = R.string.no_habits
|
protected open fun getEmptyMessage() = R.string.no_habits
|
||||||
|
|
||||||
@@ -61,6 +67,7 @@ open class HabitPickerDialog : Activity() {
|
|||||||
val habitNames = ArrayList<String>()
|
val habitNames = ArrayList<String>()
|
||||||
for (h in habitList) {
|
for (h in habitList) {
|
||||||
if (h.isArchived) continue
|
if (h.isArchived) continue
|
||||||
|
if (h.isNumerical and shouldHideNumerical()) continue
|
||||||
if (!h.isNumerical and shouldHideBoolean()) continue
|
if (!h.isNumerical and shouldHideBoolean()) continue
|
||||||
habitIds.add(h.id!!)
|
habitIds.add(h.id!!)
|
||||||
habitNames.add(h.name)
|
habitNames.add(h.name)
|
||||||
|
|||||||
@@ -57,15 +57,44 @@
|
|||||||
style="@style/Card"
|
style="@style/Card"
|
||||||
android:paddingTop="12dp"/>
|
android:paddingTop="12dp"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/noSubHabitsCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_sub_habits" />
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.TargetCardView
|
||||||
|
android:id="@+id/targetCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:paddingTop="12dp"/>
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.habits.show.views.ScoreCardView
|
<org.isoron.uhabits.activities.habits.show.views.ScoreCardView
|
||||||
android:id="@+id/scoreCard"
|
android:id="@+id/scoreCard"
|
||||||
style="@style/Card"
|
style="@style/Card"
|
||||||
android:gravity="center"/>
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.BarCardView
|
||||||
|
android:id="@+id/barCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
<org.isoron.uhabits.activities.habits.show.views.StreakCardView
|
<org.isoron.uhabits.activities.habits.show.views.StreakCardView
|
||||||
android:id="@+id/streakCard"
|
android:id="@+id/streakCard"
|
||||||
style="@style/Card"/>
|
style="@style/Card"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.FrequencyCardView
|
||||||
|
android:id="@+id/frequencyCard"
|
||||||
|
style="@style/Card"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
|
|||||||
@@ -237,4 +237,5 @@
|
|||||||
<string name="activity_not_found">No app was found to support this action</string>
|
<string name="activity_not_found">No app was found to support this action</string>
|
||||||
<string name="pref_midnight_delay_title">Extend day a few hours past midnight</string>
|
<string name="pref_midnight_delay_title">Extend day a few hours past midnight</string>
|
||||||
<string name="pref_midnight_delay_description">Wait until 3:00 AM to show a new day. Useful if you typically go to sleep after midnight. Requires app restart.</string>
|
<string name="pref_midnight_delay_description">Wait until 3:00 AM to show a new day. Useful if you typically go to sleep after midnight. Requires app restart.</string>
|
||||||
|
<string name="no_sub_habits">Group contains no habits.</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
android:previewImage="@drawable/widget_preview_streaks"
|
android:previewImage="@drawable/widget_preview_streaks"
|
||||||
android:resizeMode="vertical|horizontal"
|
android:resizeMode="vertical|horizontal"
|
||||||
android:updatePeriodMillis="3600000"
|
android:updatePeriodMillis="3600000"
|
||||||
android:configure="org.isoron.uhabits.widgets.activities.HabitPickerDialog"
|
android:configure="org.isoron.uhabits.widgets.activities.BooleanHabitPickerDialog"
|
||||||
android:widgetCategory="home_screen">
|
android:widgetCategory="home_screen">
|
||||||
|
|
||||||
</appwidget-provider>
|
</appwidget-provider>
|
||||||
@@ -24,12 +24,12 @@ import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
|||||||
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
|
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.models.Entry.Companion.YES_MANUAL
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
import java.util.ArrayList
|
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import javax.annotation.concurrent.ThreadSafe
|
import javax.annotation.concurrent.ThreadSafe
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
open class EntryList {
|
open class EntryList {
|
||||||
@@ -151,6 +151,22 @@ open class EntryList {
|
|||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized fun normalizeEntries(
|
||||||
|
isNumerical: Boolean,
|
||||||
|
frequency: Frequency,
|
||||||
|
targetValue: Double
|
||||||
|
): EntryList {
|
||||||
|
val entries = getKnown()
|
||||||
|
val normalized = EntryList()
|
||||||
|
val dailyTarget = frequency.toDouble() * (if (isNumerical) targetValue else 0.001)
|
||||||
|
for (entry in entries) {
|
||||||
|
if (!isNumerical && entry.value != YES_MANUAL) continue
|
||||||
|
val newValue = (entry.value.toDouble() / dailyTarget).roundToInt()
|
||||||
|
normalized.add(Entry(entry.timestamp, newValue))
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
|
data class Interval(val begin: Timestamp, val center: Timestamp, val end: Timestamp) {
|
||||||
val length: Int
|
val length: Int
|
||||||
get() = begin.daysUntil(end) + 1
|
get() = begin.daysUntil(end) + 1
|
||||||
@@ -318,6 +334,16 @@ fun List<Entry>.groupedSum(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun List<Entry>.groupedSum(): List<Entry> {
|
||||||
|
return this
|
||||||
|
.groupBy { entry -> entry.timestamp }
|
||||||
|
.entries.map { (timestamp, entries) ->
|
||||||
|
Entry(timestamp, entries.sumOf { it.value })
|
||||||
|
}.sortedBy { (timestamp, _) ->
|
||||||
|
-timestamp.unixTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Counts the number of days with vaLue SKIP in the given period.
|
* Counts the number of days with vaLue SKIP in the given period.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import org.isoron.uhabits.core.commands.CommandRunner
|
|||||||
import org.isoron.uhabits.core.models.HabitGroup
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardState
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardPresenter
|
||||||
@@ -14,16 +18,24 @@ import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
|
|||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCartPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCartPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardPresenter
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardPresenter
|
||||||
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
|
||||||
|
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
|
||||||
import org.isoron.uhabits.core.ui.views.Theme
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
|
|
||||||
data class ShowHabitGroupState(
|
data class ShowHabitGroupState(
|
||||||
val title: String = "",
|
val title: String = "",
|
||||||
|
val isEmpty: Boolean = false,
|
||||||
|
val isNumerical: Boolean = false,
|
||||||
|
val isBoolean: Boolean = false,
|
||||||
val color: PaletteColor = PaletteColor(1),
|
val color: PaletteColor = PaletteColor(1),
|
||||||
val subtitle: SubtitleCardState,
|
val subtitle: SubtitleCardState,
|
||||||
val overview: OverviewCardState,
|
val overview: OverviewCardState,
|
||||||
val notes: NotesCardState,
|
val notes: NotesCardState,
|
||||||
|
val target: TargetCardState,
|
||||||
val streaks: StreakCardState,
|
val streaks: StreakCardState,
|
||||||
val scores: ScoreCardState,
|
val scores: ScoreCardState,
|
||||||
|
val frequency: FrequencyCardState,
|
||||||
|
val bar: BarCardState,
|
||||||
val theme: Theme
|
val theme: Theme
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,6 +45,12 @@ class ShowHabitGroupPresenter(
|
|||||||
val screen: Screen,
|
val screen: Screen,
|
||||||
val commandRunner: CommandRunner
|
val commandRunner: CommandRunner
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val barCardPresenter = BarCardPresenter(
|
||||||
|
preferences = preferences,
|
||||||
|
screen = screen
|
||||||
|
)
|
||||||
|
|
||||||
val scoreCardPresenter = ScoreCardPresenter(
|
val scoreCardPresenter = ScoreCardPresenter(
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
screen = screen
|
screen = screen
|
||||||
@@ -46,6 +64,9 @@ class ShowHabitGroupPresenter(
|
|||||||
): ShowHabitGroupState {
|
): ShowHabitGroupState {
|
||||||
return ShowHabitGroupState(
|
return ShowHabitGroupState(
|
||||||
title = habitGroup.name,
|
title = habitGroup.name,
|
||||||
|
isEmpty = habitGroup.habitList.isEmpty,
|
||||||
|
isNumerical = habitGroup.habitList.all { it.isNumerical },
|
||||||
|
isBoolean = habitGroup.habitList.all { !it.isNumerical },
|
||||||
color = habitGroup.color,
|
color = habitGroup.color,
|
||||||
theme = theme,
|
theme = theme,
|
||||||
subtitle = SubtitleCardPresenter.buildState(
|
subtitle = SubtitleCardPresenter.buildState(
|
||||||
@@ -59,6 +80,11 @@ class ShowHabitGroupPresenter(
|
|||||||
notes = NotesCardPresenter.buildState(
|
notes = NotesCardPresenter.buildState(
|
||||||
habitGroup = habitGroup
|
habitGroup = habitGroup
|
||||||
),
|
),
|
||||||
|
target = TargetCardPresenter.buildState(
|
||||||
|
habitGroup = habitGroup,
|
||||||
|
firstWeekday = preferences.firstWeekdayInt,
|
||||||
|
theme = theme
|
||||||
|
),
|
||||||
streaks = StreakCartPresenter.buildState(
|
streaks = StreakCartPresenter.buildState(
|
||||||
habitGroup = habitGroup,
|
habitGroup = habitGroup,
|
||||||
theme = theme
|
theme = theme
|
||||||
@@ -68,11 +94,24 @@ class ShowHabitGroupPresenter(
|
|||||||
habitGroup = habitGroup,
|
habitGroup = habitGroup,
|
||||||
firstWeekday = preferences.firstWeekdayInt,
|
firstWeekday = preferences.firstWeekdayInt,
|
||||||
theme = theme
|
theme = theme
|
||||||
|
),
|
||||||
|
frequency = FrequencyCardPresenter.buildState(
|
||||||
|
habitGroup = habitGroup,
|
||||||
|
firstWeekday = preferences.firstWeekdayInt,
|
||||||
|
theme = theme
|
||||||
|
),
|
||||||
|
bar = BarCardPresenter.buildState(
|
||||||
|
habitGroup = habitGroup,
|
||||||
|
firstWeekday = preferences.firstWeekdayInt,
|
||||||
|
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
|
||||||
|
numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition,
|
||||||
|
theme = theme
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Screen :
|
interface Screen :
|
||||||
|
BarCardPresenter.Screen,
|
||||||
ScoreCardPresenter.Screen
|
ScoreCardPresenter.Screen
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
|
|||||||
|
|
||||||
import org.isoron.uhabits.core.models.Entry
|
import org.isoron.uhabits.core.models.Entry
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.groupedSum
|
import org.isoron.uhabits.core.models.groupedSum
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
@@ -74,6 +75,53 @@ class BarCardPresenter(
|
|||||||
boolSpinnerPosition = boolSpinnerPosition
|
boolSpinnerPosition = boolSpinnerPosition
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildState(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
firstWeekday: Int,
|
||||||
|
numericalSpinnerPosition: Int,
|
||||||
|
boolSpinnerPosition: Int,
|
||||||
|
theme: Theme
|
||||||
|
): BarCardState {
|
||||||
|
val isNumerical = habitGroup.habitList.all { it.isNumerical }
|
||||||
|
val isBoolean = habitGroup.habitList.all { !it.isNumerical }
|
||||||
|
if ((!isNumerical && !isBoolean) || habitGroup.habitList.isEmpty) {
|
||||||
|
return BarCardState(
|
||||||
|
theme = theme,
|
||||||
|
entries = listOf(Entry(DateUtils.getTodayWithOffset(), 0)),
|
||||||
|
bucketSize = 1,
|
||||||
|
color = habitGroup.color,
|
||||||
|
isNumerical = isNumerical,
|
||||||
|
numericalSpinnerPosition = numericalSpinnerPosition,
|
||||||
|
boolSpinnerPosition = boolSpinnerPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val bucketSize = if (isNumerical) {
|
||||||
|
numericalBucketSizes[numericalSpinnerPosition]
|
||||||
|
} else {
|
||||||
|
boolBucketSizes[boolSpinnerPosition]
|
||||||
|
}
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
val allEntries = habitGroup.habitList.map { habit ->
|
||||||
|
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
|
||||||
|
habit.computedEntries.getByInterval(oldest, today).groupedSum(
|
||||||
|
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),
|
||||||
|
firstWeekday = firstWeekday,
|
||||||
|
isNumerical = habit.isNumerical
|
||||||
|
)
|
||||||
|
}.flatten()
|
||||||
|
|
||||||
|
val summedEntries = allEntries.groupedSum()
|
||||||
|
return BarCardState(
|
||||||
|
theme = theme,
|
||||||
|
entries = summedEntries,
|
||||||
|
bucketSize = bucketSize,
|
||||||
|
color = habitGroup.color,
|
||||||
|
isNumerical = isNumerical,
|
||||||
|
numericalSpinnerPosition = numericalSpinnerPosition,
|
||||||
|
boolSpinnerPosition = boolSpinnerPosition
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNumericalSpinnerPosition(position: Int) {
|
fun onNumericalSpinnerPosition(position: Int) {
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
package org.isoron.uhabits.core.ui.screens.habits.show.views
|
package org.isoron.uhabits.core.ui.screens.habits.show.views
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.ui.views.Theme
|
import org.isoron.uhabits.core.ui.views.Theme
|
||||||
import java.util.HashMap
|
|
||||||
|
|
||||||
data class FrequencyCardState(
|
data class FrequencyCardState(
|
||||||
val color: PaletteColor,
|
val color: PaletteColor,
|
||||||
@@ -48,5 +48,42 @@ class FrequencyCardPresenter {
|
|||||||
firstWeekday = firstWeekday,
|
firstWeekday = firstWeekday,
|
||||||
theme = theme
|
theme = theme
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun buildState(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
firstWeekday: Int,
|
||||||
|
theme: Theme
|
||||||
|
): FrequencyCardState {
|
||||||
|
val normalizedEntries = habitGroup.habitList.map {
|
||||||
|
it.computedEntries.normalizeEntries(it.isNumerical, it.frequency, it.targetValue)
|
||||||
|
}
|
||||||
|
val frequencies = normalizedEntries.map {
|
||||||
|
it.computeWeekdayFrequency(isNumerical = true)
|
||||||
|
}.reduce { acc, hashMap ->
|
||||||
|
mergeMaps(acc, hashMap) { value1, value2 -> addArray(value1, value2) }
|
||||||
|
}
|
||||||
|
|
||||||
|
return FrequencyCardState(
|
||||||
|
color = habitGroup.color,
|
||||||
|
isNumerical = true,
|
||||||
|
frequency = frequencies,
|
||||||
|
firstWeekday = firstWeekday,
|
||||||
|
theme = theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <K, V> mergeMaps(map1: HashMap<K, V>, map2: HashMap<K, V>, mergeFunction: (V, V) -> V): HashMap<K, V> {
|
||||||
|
val result = map1 // Step 1
|
||||||
|
for ((key, value) in map2) { // Step 2
|
||||||
|
result[key] = result[key]?.let { existingValue ->
|
||||||
|
mergeFunction(existingValue, value) // Step 3 (merge logic)
|
||||||
|
} ?: value
|
||||||
|
}
|
||||||
|
return result // Step 4
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addArray(array1: Array<Int>, array2: Array<Int>): Array<Int> {
|
||||||
|
return array1.zip(array2) { a, b -> a + b }.toTypedArray()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
package org.isoron.uhabits.core.ui.screens.habits.show.views
|
package org.isoron.uhabits.core.ui.screens.habits.show.views
|
||||||
|
|
||||||
import org.isoron.uhabits.core.models.Habit
|
import org.isoron.uhabits.core.models.Habit
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
import org.isoron.uhabits.core.models.PaletteColor
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.models.countSkippedDays
|
import org.isoron.uhabits.core.models.countSkippedDays
|
||||||
@@ -176,5 +177,66 @@ class TargetCardPresenter {
|
|||||||
if (thisWeek.daysUntil(newest) < 7) newest = thisWeek.plus(7)
|
if (thisWeek.daysUntil(newest) < 7) newest = thisWeek.plus(7)
|
||||||
return Pair(yearBegin, newest)
|
return Pair(yearBegin, newest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun buildState(
|
||||||
|
habitGroup: HabitGroup,
|
||||||
|
firstWeekday: Int,
|
||||||
|
theme: Theme
|
||||||
|
): TargetCardState {
|
||||||
|
val maxDen = habitGroup.habitList.maxOfOrNull { habit -> habit.frequency.denominator }
|
||||||
|
val isNumerical = habitGroup.habitList.all { it.isNumerical }
|
||||||
|
if (maxDen == null || !isNumerical) {
|
||||||
|
return TargetCardState(
|
||||||
|
color = habitGroup.color,
|
||||||
|
values = arrayListOf(0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
targets = arrayListOf(0.0, 0.0, 0.0, 0.0, 0.0),
|
||||||
|
intervals = arrayListOf(1, 7, 30, 91, 365),
|
||||||
|
theme = theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val states = habitGroup.habitList.map { Companion.buildState(it, firstWeekday, theme) }
|
||||||
|
|
||||||
|
val values = states
|
||||||
|
.map {
|
||||||
|
val startIdx = it.intervals.indexOf(maxDen)
|
||||||
|
val endIdx = it.intervals.size
|
||||||
|
it.values.subList(startIdx, endIdx)
|
||||||
|
}
|
||||||
|
.reduce { acc, list ->
|
||||||
|
acc.zip(list) { a, b -> a + b }
|
||||||
|
}
|
||||||
|
|
||||||
|
val targets = states
|
||||||
|
.map {
|
||||||
|
val startIdx = it.intervals.indexOf(maxDen)
|
||||||
|
val endIdx = it.intervals.size
|
||||||
|
it.targets.subList(startIdx, endIdx)
|
||||||
|
}
|
||||||
|
.reduce { acc, list ->
|
||||||
|
acc.zip(list) { a, b -> a + b }
|
||||||
|
}
|
||||||
|
|
||||||
|
val intervals = arrayListOf(1, 7, 30, 91, 365).filter { it >= maxDen }
|
||||||
|
|
||||||
|
return TargetCardState(
|
||||||
|
color = habitGroup.color,
|
||||||
|
values = values,
|
||||||
|
targets = targets,
|
||||||
|
intervals = intervals,
|
||||||
|
theme = theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getYearRange(firstWeekday: Int): Pair<Timestamp, Timestamp> {
|
||||||
|
val today = DateUtils.getTodayWithOffset()
|
||||||
|
val yearBegin = today.truncate(DateUtils.TruncateField.YEAR, firstWeekday)
|
||||||
|
val cali = yearBegin.toCalendar()
|
||||||
|
cali.add(Calendar.YEAR, 1)
|
||||||
|
var newest = Timestamp(cali)
|
||||||
|
val thisWeek = today.truncate(DateUtils.TruncateField.WEEK_NUMBER, firstWeekday)
|
||||||
|
if (thisWeek.daysUntil(newest) < 7) newest = thisWeek.plus(7)
|
||||||
|
return Pair(yearBegin, newest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user