diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml
index 36f3928c0..1d9dc6620 100644
--- a/uhabits-android/src/main/AndroidManifest.xml
+++ b/uhabits-android/src/main/AndroidManifest.xml
@@ -128,6 +128,15 @@
+
+
+
+
+
+
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
index 03775abeb..45a70cd14 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt
@@ -31,6 +31,7 @@ import android.net.Uri
import android.os.Build
import org.isoron.uhabits.activities.habits.list.ListHabitsActivity
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
+import org.isoron.uhabits.activities.habits.show.ShowHabitGroupActivity
import org.isoron.uhabits.core.AppScope
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitGroup
@@ -124,6 +125,15 @@ class PendingIntentFactory
)
}
+ fun showHabitGroupTemplate(): PendingIntent {
+ return getActivity(
+ context,
+ 0,
+ Intent(context, ShowHabitGroupActivity::class.java),
+ getIntentTemplateFlags()
+ )
+ }
+
fun showHabitFillIn(habit: Habit) =
Intent().apply {
data = Uri.parse(habit.uriString)
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt
index a93b81de8..a22fbb4ae 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/BaseWidgetProvider.kt
@@ -27,6 +27,7 @@ import android.widget.RemoteViews
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitGroupList
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitNotFoundException
@@ -114,12 +115,25 @@ abstract class BaseWidgetProvider : AppWidgetProvider() {
val selectedHabits = ArrayList(selectedIds.size)
for (id in selectedIds) {
val h = habits.getById(id) ?: habitGroups.getHabitByID(id)
- ?: throw HabitNotFoundException()
- selectedHabits.add(h)
+ if (h != null) {
+ selectedHabits.add(h)
+ }
}
return selectedHabits
}
+ protected fun getHabitGroupsFromWidgetId(widgetId: Int): List {
+ val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)
+ val selectedHabitGroups = ArrayList(selectedIds.size)
+ for (id in selectedIds) {
+ val hgr = habitGroups.getById(id)
+ if (hgr != null) {
+ selectedHabitGroups.add(hgr)
+ }
+ }
+ return selectedHabitGroups
+ }
+
protected abstract fun getWidgetFromId(
context: Context,
id: Int
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt
index 88c6b7655..9ae960695 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidget.kt
@@ -25,32 +25,51 @@ import android.view.View
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.FrequencyChart
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
+import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView
-class FrequencyWidget(
+class FrequencyWidget private constructor(
context: Context,
widgetId: Int,
- private val habit: Habit,
+ private val habit: Habit?,
+ private val habitGroup: HabitGroup?,
private val firstWeekday: Int,
- stacked: Boolean = false
+ stacked: Boolean
) : BaseWidget(context, widgetId, stacked) {
+
+ constructor(context: Context, widgetId: Int, habit: Habit, firstWeekday: Int, stacked: Boolean = false) : this(context, widgetId, habit, null, firstWeekday, stacked)
+ constructor(context: Context, widgetId: Int, habitGroup: HabitGroup, firstWeekday: Int, stacked: Boolean = false) : this(context, widgetId, null, habitGroup, firstWeekday, stacked)
+
override val defaultHeight: Int = 200
override val defaultWidth: Int = 200
- override fun getOnClickPendingIntent(context: Context): PendingIntent =
- pendingIntentFactory.showHabit(habit)
+ override fun getOnClickPendingIntent(context: Context): PendingIntent {
+ return if (habit != null) {
+ pendingIntentFactory.showHabit(habit)
+ } else {
+ pendingIntentFactory.showHabitGroup(habitGroup!!)
+ }
+ }
override fun refreshData(v: View) {
val widgetView = v as GraphWidgetView
- widgetView.setTitle(habit.name)
+ widgetView.setTitle(habit?.name ?: habitGroup!!.name)
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
+ val color = habit?.color ?: habitGroup!!.color
+ val isNumerical = habit?.isNumerical ?: true
+ val frequency = if (habit != null) {
+ habit.originalEntries.computeWeekdayFrequency(habit.isNumerical)
+ } else {
+ FrequencyCardPresenter.getFrequenciesFromHabitGroup(habitGroup!!)
+ }
(widgetView.dataView as FrequencyChart).apply {
setFirstWeekday(firstWeekday)
- setColor(WidgetTheme().color(habit.color).toInt())
- setIsNumerical(habit.isNumerical)
- setFrequency(habit.originalEntries.computeWeekdayFrequency(habit.isNumerical))
+ setColor(WidgetTheme().color(color).toInt())
+ setIsNumerical(isNumerical)
+ setFrequency(frequency)
}
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt
index 698f002f4..dc2f18fd2 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/FrequencyWidgetProvider.kt
@@ -24,15 +24,29 @@ import android.content.Context
class FrequencyWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
- return if (habits.size == 1) {
- FrequencyWidget(
- context,
- id,
- habits[0],
- preferences.firstWeekdayInt
- )
+ if (habits.isNotEmpty()) {
+ return if (habits.size == 1) {
+ FrequencyWidget(
+ context,
+ id,
+ habits[0],
+ preferences.firstWeekdayInt
+ )
+ } else {
+ StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
+ }
} else {
- StackWidget(context, id, StackWidgetType.FREQUENCY, habits)
+ val habitGroups = getHabitGroupsFromWidgetId(id)
+ return if (habitGroups.size == 1) {
+ FrequencyWidget(
+ context,
+ id,
+ habitGroups[0],
+ preferences.firstWeekdayInt
+ )
+ } else {
+ StackWidget(context, id, StackWidgetType.FREQUENCY, habitGroups, true)
+ }
}
}
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt
index 8be9dfed5..b6786cb66 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt
@@ -25,42 +25,62 @@ import android.view.View
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.activities.common.views.ScoreChart
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.core.ui.views.WidgetTheme
import org.isoron.uhabits.widgets.views.GraphWidgetView
-class ScoreWidget(
+class ScoreWidget private constructor(
context: Context,
id: Int,
- private val habit: Habit,
- stacked: Boolean = false
+ private val habit: Habit?,
+ private val habitGroup: HabitGroup?,
+ stacked: Boolean
) : BaseWidget(context, id, stacked) {
+ constructor(context: Context, id: Int, habit: Habit, stacked: Boolean = false) : this(context, id, habit, null, stacked)
+ constructor(context: Context, id: Int, habitGroup: HabitGroup, stacked: Boolean = false) : this(context, id, null, habitGroup, stacked)
+
override val defaultHeight: Int = 300
override val defaultWidth: Int = 300
- override fun getOnClickPendingIntent(context: Context): PendingIntent =
- pendingIntentFactory.showHabit(habit)
+ override fun getOnClickPendingIntent(context: Context): PendingIntent {
+ return if (habit != null) {
+ pendingIntentFactory.showHabit(habit)
+ } else {
+ pendingIntentFactory.showHabitGroup(habitGroup!!)
+ }
+ }
override fun refreshData(view: View) {
- val viewModel = ScoreCardPresenter.buildState(
- habit = habit,
- firstWeekday = prefs.firstWeekdayInt,
- spinnerPosition = prefs.scoreCardSpinnerPosition,
- theme = WidgetTheme()
- )
+ val viewModel = if (habit != null) {
+ ScoreCardPresenter.buildState(
+ habit = habit,
+ firstWeekday = prefs.firstWeekdayInt,
+ spinnerPosition = prefs.scoreCardSpinnerPosition,
+ theme = WidgetTheme()
+ )
+ } else {
+ ScoreCardPresenter.buildState(
+ habitGroup = habitGroup!!,
+ firstWeekday = prefs.firstWeekdayInt,
+ spinnerPosition = prefs.scoreCardSpinnerPosition,
+ theme = WidgetTheme()
+ )
+ }
val widgetView = view as GraphWidgetView
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
+ val color = habit?.color ?: habitGroup!!.color
(widgetView.dataView as ScoreChart).apply {
setIsTransparencyEnabled(true)
setBucketSize(viewModel.bucketSize)
- setColor(WidgetTheme().color(habit.color).toInt())
+ setColor(WidgetTheme().color(color).toInt())
setScores(viewModel.scores)
}
}
override fun buildView() =
GraphWidgetView(context, ScoreChart(context)).apply {
- setTitle(habit.name)
+ setTitle(habit?.name ?: habitGroup!!.name)
}
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt
index 160054da1..794413219 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidgetProvider.kt
@@ -23,10 +23,19 @@ import android.content.Context
class ScoreWidgetProvider : BaseWidgetProvider() {
override fun getWidgetFromId(context: Context, id: Int): BaseWidget {
val habits = getHabitsFromWidgetId(id)
- return if (habits.size == 1) {
- ScoreWidget(context, id, habits[0])
+ if (habits.isNotEmpty()) {
+ return if (habits.size == 1) {
+ ScoreWidget(context, id, habits[0])
+ } else {
+ StackWidget(context, id, StackWidgetType.SCORE, habits)
+ }
} else {
- StackWidget(context, id, StackWidgetType.SCORE, habits)
+ val habitGroups = getHabitGroupsFromWidgetId(id)
+ return if (habitGroups.size == 1) {
+ ScoreWidget(context, id, habitGroups[0])
+ } else {
+ StackWidget(context, id, StackWidgetType.SCORE, habitGroups, true)
+ }
}
}
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt
index 6c59c1e1d..47e906538 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidget.kt
@@ -28,14 +28,20 @@ import android.view.View
import android.widget.RemoteViews
import org.isoron.platform.utils.StringUtils
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
-class StackWidget(
+class StackWidget private constructor(
context: Context,
widgetId: Int,
private val widgetType: StackWidgetType,
- private val habits: List,
- stacked: Boolean = true
+ private val habits: List?,
+ private val habitGroups: List?,
+ stacked: Boolean
) : BaseWidget(context, widgetId, stacked) {
+
+ constructor(context: Context, widgetId: Int, widgetType: StackWidgetType, habits: List, stacked: Boolean = true) : this(context, widgetId, widgetType, habits, null, stacked)
+ constructor(context: Context, widgetId: Int, widgetType: StackWidgetType, habitGroups: List, stacked: Boolean = true, isHabitGroups: Boolean = false) : this(context, widgetId, widgetType, null, habitGroups, stacked)
+
override val defaultHeight: Int = 0
override val defaultWidth: Int = 0
@@ -55,7 +61,11 @@ class StackWidget(
val remoteViews =
RemoteViews(context.packageName, StackWidgetType.getStackWidgetLayoutId(widgetType))
val serviceIntent = Intent(context, StackWidgetService::class.java)
- val habitIds = StringUtils.joinLongs(habits.map { it.id!! }.toLongArray())
+ val habitIds = if (habits != null) {
+ StringUtils.joinLongs(habits.map { it.id!! }.toLongArray())
+ } else {
+ StringUtils.joinLongs(habitGroups!!.map { it.id!! }.toLongArray())
+ }
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, id)
serviceIntent.putExtra(StackWidgetService.WIDGET_TYPE, widgetType.value)
@@ -73,9 +83,14 @@ class StackWidget(
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
StackWidgetType.getStackWidgetEmptyViewId(widgetType)
)
+ val pendingIntentTemplate = if (habits != null) {
+ StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, habits)
+ } else {
+ StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, true)
+ }
remoteViews.setPendingIntentTemplate(
StackWidgetType.getStackWidgetAdapterViewId(widgetType),
- StackWidgetType.getPendingIntentTemplate(pendingIntentFactory, widgetType, habits)
+ pendingIntentTemplate
)
return remoteViews
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt
index 1e3a5d217..b8929f5f6 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/StackWidgetType.kt
@@ -24,7 +24,6 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.intents.PendingIntentFactory
-import java.lang.IllegalStateException
enum class StackWidgetType(val value: Int) {
CHECKMARK(0), FREQUENCY(1), SCORE(2), // habit strength widget
@@ -95,6 +94,17 @@ enum class StackWidgetType(val value: Int) {
}
}
+ fun getPendingIntentTemplate(
+ factory: PendingIntentFactory,
+ widgetType: StackWidgetType,
+ isHabitGroups: Boolean
+ ): PendingIntent {
+ return when (widgetType) {
+ CHECKMARK, HISTORY, STREAKS, TARGET -> throw RuntimeException()
+ FREQUENCY, SCORE -> factory.showHabitGroupTemplate()
+ }
+ }
+
fun getIntentFillIn(
factory: PendingIntentFactory,
widgetType: StackWidgetType,
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/HabitPickerDialog.kt b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/HabitPickerDialog.kt
index b9d1bd26a..2f2dc9682 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/HabitPickerDialog.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/HabitPickerDialog.kt
@@ -43,6 +43,10 @@ class NumericalHabitPickerDialog : HabitPickerDialog() {
override fun getEmptyMessage() = R.string.no_numerical_habits
}
+class HabitAndGroupPickerDialog : HabitPickerDialog() {
+ override fun shouldShowGroups(): Boolean = true
+}
+
open class HabitPickerDialog : Activity() {
private var widgetId = 0
@@ -51,6 +55,8 @@ open class HabitPickerDialog : Activity() {
protected open fun shouldHideNumerical() = false
protected open fun shouldHideBoolean() = false
+
+ protected open fun shouldShowGroups() = false
protected open fun getEmptyMessage() = R.string.no_habits
override fun onCreate(savedInstanceState: Bundle?) {
@@ -75,6 +81,10 @@ open class HabitPickerDialog : Activity() {
for (hgr in habitGroupList) {
if (hgr.isArchived) continue
+ if (shouldShowGroups()) {
+ habitIds.add(hgr.id!!)
+ habitNames.add(hgr.name)
+ }
for (h in hgr.habitList) {
if (h.isArchived) continue
diff --git a/uhabits-android/src/main/res/xml/widget_frequency_info.xml b/uhabits-android/src/main/res/xml/widget_frequency_info.xml
index 65cdd1e92..6058570fe 100644
--- a/uhabits-android/src/main/res/xml/widget_frequency_info.xml
+++ b/uhabits-android/src/main/res/xml/widget_frequency_info.xml
@@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_frequency"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
- android:configure="org.isoron.uhabits.widgets.activities.HabitPickerDialog"
+ android:configure="org.isoron.uhabits.widgets.activities.HabitAndGroupPickerDialog"
android:widgetCategory="home_screen">
\ No newline at end of file
diff --git a/uhabits-android/src/main/res/xml/widget_score_info.xml b/uhabits-android/src/main/res/xml/widget_score_info.xml
index 60ba4d64d..9dd728edb 100644
--- a/uhabits-android/src/main/res/xml/widget_score_info.xml
+++ b/uhabits-android/src/main/res/xml/widget_score_info.xml
@@ -27,7 +27,7 @@
android:previewImage="@drawable/widget_preview_score"
android:resizeMode="vertical|horizontal"
android:updatePeriodMillis="3600000"
- android:configure="org.isoron.uhabits.widgets.activities.HabitPickerDialog"
+ android:configure="org.isoron.uhabits.widgets.activities.HabitAndGroupPickerDialog"
android:widgetCategory="home_screen">
\ No newline at end of file
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt
index 66a71b1bd..37ee7788f 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/FrequencyCard.kt
@@ -54,14 +54,7 @@ class FrequencyCardPresenter {
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) }
- }
+ val frequencies = getFrequenciesFromHabitGroup(habitGroup)
return FrequencyCardState(
color = habitGroup.color,
@@ -72,6 +65,18 @@ class FrequencyCardPresenter {
)
}
+ fun getFrequenciesFromHabitGroup(habitGroup: HabitGroup): HashMap> {
+ 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 frequencies
+ }
+
private fun mergeMaps(map1: HashMap, map2: HashMap, mergeFunction: (V, V) -> V): HashMap {
val result = map1 // Step 1
for ((key, value) in map2) { // Step 2