From 96d23bdf22adb1ff3696011058e63bbf6e5c1b67 Mon Sep 17 00:00:00 2001 From: Thomas S Date: Sun, 12 Jul 2020 09:06:42 -0500 Subject: [PATCH] Update checkmark widget for numerical support Co-authored-by: Alinson S. Xavier --- .../views/CheckmarkWidgetViewTest.java | 1 + .../src/main/AndroidManifest.xml | 21 ++++- .../common/dialogs/NumberPickerFactory.kt | 3 + .../isoron/uhabits/intents/IntentParser.kt | 5 ++ .../uhabits/intents/PendingIntentFactory.kt | 15 ++++ .../uhabits/receivers/WidgetReceiver.java | 12 +++ .../isoron/uhabits/widgets/CheckmarkWidget.kt | 35 +++++++-- .../NumericalCheckmarkWidgetActivity.kt | 76 +++++++++++++++++++ .../widgets/views/CheckmarkWidgetView.java | 59 +++++++++----- .../org/isoron/uhabits/core/models/Habit.java | 4 +- .../habits/list/ListHabitsBehavior.java | 2 + .../core/ui/widgets/WidgetBehavior.java | 7 ++ .../isoron/uhabits/core/models/HabitTest.java | 12 +-- 13 files changed, 217 insertions(+), 35 deletions(-) create mode 100644 android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt diff --git a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java index f0354a5d5..de54d565c 100644 --- a/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java +++ b/android/uhabits-android/src/androidTest/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetViewTest.java @@ -53,6 +53,7 @@ public class CheckmarkWidgetViewTest extends BaseViewTest float percentage = (float) score; view.setActiveColor(PaletteUtils.getAndroidTestColor(0)); + view.setCheckmarkState(habit.getCheckmarks().getTodayValue()); view.setCheckmarkValue(habit.getCheckmarks().getTodayValue()); view.setPercentage(percentage); view.setName(habit.getName()); diff --git a/android/uhabits-android/src/main/AndroidManifest.xml b/android/uhabits-android/src/main/AndroidManifest.xml index 9a3e7961f..f2dbda768 100644 --- a/android/uhabits-android/src/main/AndroidManifest.xml +++ b/android/uhabits-android/src/main/AndroidManifest.xml @@ -84,7 +84,17 @@ + + + + + + @@ -174,7 +184,14 @@ - + + + + + + diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt index d0e959311..e9328cf44 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/NumberPickerFactory.kt @@ -69,6 +69,9 @@ class NumberPickerFactory val v = picker.value + 0.05 * picker2.value callback.onNumberPicked(v) } + .setOnDismissListener{ + callback.onNumberPickerDismissed() + } .create() dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt index 676e55a4f..ca2204c06 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentParser.kt @@ -36,6 +36,11 @@ class IntentParser return CheckmarkIntentData(parseHabit(uri), parseTimestamp(intent)) } + fun copyIntentData(source: Intent, destination: Intent) { + destination.data = source.data; + destination.putExtra("timestamp", source.getLongExtra("timestamp", DateUtils.getToday().unixTime)) + } + private fun parseHabit(uri: Uri): Habit { val habit = habits.getById(parseId(uri)) ?: throw IllegalArgumentException("habit not found") diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt index 0901a0836..cb2f84532 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/intents/PendingIntentFactory.kt @@ -103,4 +103,19 @@ class PendingIntentFactory if (timestamp != null) putExtra("timestamp", timestamp) }, FLAG_UPDATE_CURRENT) + + fun setNumericalValue(widgetContext: Context, + habit: Habit, + numericalValue: Int, + timestamp: Long?): + PendingIntent = + getBroadcast( + widgetContext, 2, + Intent(widgetContext, WidgetReceiver::class.java).apply { + data = Uri.parse(habit.uriString) + action = WidgetReceiver.ACTION_SET_NUMERICAL_VALUE + putExtra("numericalValue", numericalValue); + if (timestamp != null) putExtra("timestamp", timestamp) + }, + FLAG_UPDATE_CURRENT) } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java index c13d62d2d..20e0f65a7 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/receivers/WidgetReceiver.java @@ -27,6 +27,7 @@ import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.ui.widgets.*; import org.isoron.uhabits.intents.*; import org.isoron.uhabits.sync.*; +import org.isoron.uhabits.widgets.activities.*; import dagger.*; @@ -49,6 +50,9 @@ public class WidgetReceiver extends BroadcastReceiver public static final String ACTION_TOGGLE_REPETITION = "org.isoron.uhabits.ACTION_TOGGLE_REPETITION"; + public static final String ACTION_SET_NUMERICAL_VALUE = + "org.isoron.uhabits.ACTION_SET_NUMERICAL_VALUE"; + private static final String TAG = "WidgetReceiver"; @Override @@ -104,6 +108,14 @@ public class WidgetReceiver extends BroadcastReceiver controller.onRemoveRepetition(data.getHabit(), data.getTimestamp()); break; + case ACTION_SET_NUMERICAL_VALUE: + Intent numberSelectorIntent = new Intent(context, NumericalCheckmarkWidgetActivity.class); + numberSelectorIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + numberSelectorIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + numberSelectorIntent.setAction(NumericalCheckmarkWidgetActivity.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY); + parser.copyIntentData(intent,numberSelectorIntent); + context.startActivity(numberSelectorIntent); + break; } } catch (RuntimeException e) diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt index ad159eab6..588a7fa25 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/CheckmarkWidget.kt @@ -19,33 +19,58 @@ package org.isoron.uhabits.widgets +import android.app.* import android.content.* import android.view.* import org.isoron.uhabits.core.models.* import org.isoron.uhabits.utils.* import org.isoron.uhabits.widgets.views.* -class CheckmarkWidget( +open class CheckmarkWidget( context: Context, widgetId: Int, - private val habit: Habit + protected val habit: Habit ) : BaseWidget(context, widgetId) { - override fun getOnClickPendingIntent(context: Context) = + override fun getOnClickPendingIntent(context: Context): PendingIntent { + return if (habit.isNumerical) { + pendingIntentFactory.setNumericalValue(context, habit, 10, null) + } else { pendingIntentFactory.toggleCheckmark(habit, null) + } + } override fun refreshData(v: View) { (v as CheckmarkWidgetView).apply { setBackgroundAlpha(preferedBackgroundAlpha) - setPercentage(habit.scores.todayValue.toFloat()) + setActiveColor(PaletteUtils.getColor(context, habit.color)) setName(habit.name) setCheckmarkValue(habit.checkmarks.todayValue) + if (habit.isNumerical) { + setNumerical(true) + setCheckmarkState(getNumericalCheckmarkState()) + } else { + setCheckmarkState(habit.checkmarks.todayValue) + } + setPercentage(habit.scores.todayValue.toFloat()) refresh() } } - override fun buildView() = CheckmarkWidgetView(context) + override fun buildView(): View { + return CheckmarkWidgetView(context) + } + override fun getDefaultHeight() = 125 override fun getDefaultWidth() = 125 + + private fun getNumericalCheckmarkState(): Int { + return if (habit.isCompletedToday) { + Checkmark.CHECKED_EXPLICITLY + } else { + Checkmark.UNCHECKED + } + } + } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt new file mode 100644 index 000000000..cc1b22421 --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/NumericalCheckmarkWidgetActivity.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016-2020 Á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.widgets.activities + +import android.app.* +import android.content.* +import android.os.* +import android.view.* +import android.widget.FrameLayout +import org.isoron.uhabits.* +import org.isoron.uhabits.activities.* +import org.isoron.uhabits.activities.common.dialogs.* +import org.isoron.uhabits.core.ui.screens.habits.list.* +import org.isoron.uhabits.core.ui.widgets.* +import org.isoron.uhabits.intents.* +import org.isoron.uhabits.widgets.* + +class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPickerCallback { + + private lateinit var behavior: WidgetBehavior + private lateinit var data: IntentParser.CheckmarkIntentData + private lateinit var widgetUpdater: WidgetUpdater + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestWindowFeature(Window.FEATURE_NO_TITLE) + setContentView(FrameLayout(this)) + val app = this.applicationContext as HabitsApplication + val component = app.component + val parser = app.component.intentParser + data = parser.parseCheckmarkIntent(intent) + behavior = WidgetBehavior(component.habitList, component.commandRunner, component.notificationTray) + widgetUpdater = component.widgetUpdater + showNumberSelector(this) + } + + override fun onNumberPicked(newValue: Double) { + behavior.setNumericValue(data.habit, data.timestamp, (newValue * 1000).toInt()) + widgetUpdater.updateWidgets() + finish() + } + + override fun onNumberPickerDismissed() { + finish() + } + + private fun showNumberSelector(context: Context) { + val app = this.applicationContext as HabitsApplication + AndroidThemeSwitcher(this, app.component.preferences).apply() + val numberPickerFactory = NumberPickerFactory(context) + numberPickerFactory.create(data.habit.checkmarks.today!!.value.toDouble() / 1000, + data.habit.unit, + this).show() + } + + companion object { + const val ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY = "org.isoron.uhabits.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY" + } +} \ No newline at end of file diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java index f241d5a32..68971ca55 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/views/CheckmarkWidgetView.java @@ -28,26 +28,30 @@ import androidx.annotation.Nullable; import org.isoron.androidbase.utils.*; import org.isoron.uhabits.*; +import org.isoron.uhabits.activities.habits.list.views.*; import org.isoron.uhabits.core.models.*; import org.isoron.uhabits.activities.common.views.*; import org.isoron.uhabits.utils.*; import static org.isoron.androidbase.utils.InterfaceUtils.getDimension; -public class CheckmarkWidgetView extends HabitWidgetView -{ - private int activeColor; +public class CheckmarkWidgetView extends HabitWidgetView { + protected int activeColor; - private float percentage; + protected float percentage; @Nullable - private String name; + protected String name; - private RingView ring; + protected RingView ring; - private TextView label; + protected TextView label; - private int checkmarkValue; + protected int checkmarkValue; + + protected int checkmarkState; + + protected boolean isNumerical; public CheckmarkWidgetView(Context context) { @@ -67,14 +71,11 @@ public class CheckmarkWidgetView extends HabitWidgetView StyledResources res = new StyledResources(getContext()); - String text; int bgColor; int fgColor; - switch (checkmarkValue) - { + switch (checkmarkState) { case Checkmark.CHECKED_EXPLICITLY: - text = getResources().getString(R.string.fa_check); bgColor = activeColor; fgColor = res.getColor(R.attr.highContrastReverseTextColor); setShadowAlpha(0x4f); @@ -83,15 +84,9 @@ public class CheckmarkWidgetView extends HabitWidgetView break; case Checkmark.CHECKED_IMPLICITLY: - text = getResources().getString(R.string.fa_check); - bgColor = res.getColor(R.attr.cardBgColor); - fgColor = res.getColor(R.attr.mediumContrastTextColor); - setShadowAlpha(0x00); - break; - case Checkmark.UNCHECKED: default: - text = getResources().getString(R.string.fa_times); + getResources().getString(R.string.fa_times); bgColor = res.getColor(R.attr.cardBgColor); fgColor = res.getColor(R.attr.mediumContrastTextColor); setShadowAlpha(0x00); @@ -101,7 +96,7 @@ public class CheckmarkWidgetView extends HabitWidgetView ring.setPercentage(percentage); ring.setColor(fgColor); ring.setBackgroundColor(bgColor); - ring.setText(text); + ring.setText(getText()); label.setText(name); label.setTextColor(fgColor); @@ -110,6 +105,25 @@ public class CheckmarkWidgetView extends HabitWidgetView postInvalidate(); } + public void setCheckmarkState(int checkmarkState) + { + this.checkmarkState = checkmarkState; + } + + protected String getText() + { + if (isNumerical) return NumberButtonViewKt.toShortString(checkmarkValue / 1000.0); + switch (checkmarkState) { + case Checkmark.CHECKED_EXPLICITLY: + case Checkmark.CHECKED_IMPLICITLY: + return getResources().getString(R.string.fa_check); + + case Checkmark.UNCHECKED: + default: + return getResources().getString(R.string.fa_times); + } + } + public void setActiveColor(int activeColor) { this.activeColor = activeColor; @@ -130,6 +144,11 @@ public class CheckmarkWidgetView extends HabitWidgetView this.percentage = percentage; } + public void setNumerical(boolean isNumerical) + { + this.isNumerical = isNumerical; + } + @Override @NonNull protected Integer getInnerLayoutId() diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java index a290b56d9..3f5e79782 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/models/Habit.java @@ -327,9 +327,9 @@ public class Habit if (isNumerical()) { if(getTargetType() == AT_LEAST) - return todayCheckmark >= data.targetValue; + return todayCheckmark / 1000.0 >= data.targetValue; else - return todayCheckmark <= data.targetValue; + return todayCheckmark / 1000.0 <= data.targetValue; } else return (todayCheckmark != UNCHECKED); } diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java index 11491e5bf..d293cb3c8 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.java @@ -177,6 +177,8 @@ public class ListHabitsBehavior public interface NumberPickerCallback { void onNumberPicked(double newValue); + + default void onNumberPickerDismissed() {} } public interface Screen diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java index 1bcc97782..46966cecf 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/ui/widgets/WidgetBehavior.java @@ -73,4 +73,11 @@ public class WidgetBehavior new ToggleRepetitionCommand(habitList, habit, timestamp), habit.getId()); } + + public void setNumericValue(@NonNull Habit habit, Timestamp timestamp, int newValue) { + commandRunner.execute( + new CreateRepetitionCommand(habit, timestamp, newValue), + habit.getId()); + } + } diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java index 89d39dce5..e99e954c0 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/models/HabitTest.java @@ -102,19 +102,19 @@ public class HabitTest extends BaseUnitTest h.setTargetValue(100.0); assertFalse(h.isCompletedToday()); - h.getRepetitions().toggle(getToday(), 200); + h.getRepetitions().toggle(getToday(), 200_000); assertTrue(h.isCompletedToday()); - h.getRepetitions().toggle(getToday(), 100); + h.getRepetitions().toggle(getToday(), 100_000); assertTrue(h.isCompletedToday()); - h.getRepetitions().toggle(getToday(), 50); + h.getRepetitions().toggle(getToday(), 50_000); assertFalse(h.isCompletedToday()); h.setTargetType(Habit.AT_MOST); - h.getRepetitions().toggle(getToday(), 200); + h.getRepetitions().toggle(getToday(), 200_000); assertFalse(h.isCompletedToday()); - h.getRepetitions().toggle(getToday(), 100); + h.getRepetitions().toggle(getToday(), 100_000); assertTrue(h.isCompletedToday()); - h.getRepetitions().toggle(getToday(), 50); + h.getRepetitions().toggle(getToday(), 50_000); assertTrue(h.isCompletedToday()); }