mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-14 04:58:52 -06:00
Implement score and frequency widgets for habit groups
This commit is contained in:
@@ -128,6 +128,15 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".widgets.activities.HabitAndGroupPickerDialog"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.about.AboutActivity"
|
||||
android:label="@string/about">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<Habit>(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<HabitGroup> {
|
||||
val selectedIds = widgetPrefs.getHabitIdsFromWidgetId(widgetId)
|
||||
val selectedHabitGroups = ArrayList<HabitGroup>(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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Habit>,
|
||||
stacked: Boolean = true
|
||||
private val habits: List<Habit>?,
|
||||
private val habitGroups: List<HabitGroup>?,
|
||||
stacked: Boolean
|
||||
) : BaseWidget(context, widgetId, stacked) {
|
||||
|
||||
constructor(context: Context, widgetId: Int, widgetType: StackWidgetType, habits: List<Habit>, stacked: Boolean = true) : this(context, widgetId, widgetType, habits, null, stacked)
|
||||
constructor(context: Context, widgetId: Int, widgetType: StackWidgetType, habitGroups: List<HabitGroup>, 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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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">
|
||||
|
||||
</appwidget-provider>
|
||||
@@ -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">
|
||||
|
||||
</appwidget-provider>
|
||||
@@ -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<Timestamp, Array<Int>> {
|
||||
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 <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
|
||||
|
||||
Reference in New Issue
Block a user