From fdd9346d1c37245338cbaa70184ee29b7cf65e60 Mon Sep 17 00:00:00 2001 From: KristianTashkov Date: Tue, 21 Jul 2020 14:46:42 +0300 Subject: [PATCH] Advanced Options Dialog with explicit Fail --- .../render_implicit_check.png | Bin 543 -> 620 bytes .../habits/list/CheckmarkPanelView/render.png | Bin 1470 -> 1599 bytes .../src/main/AndroidManifest.xml | 11 ++ .../dialogs/CheckmarkOptionPickerFactory.kt | 103 ++++++++++++++++++ .../activities/common/views/HistoryChart.java | 26 +++-- .../habits/list/ListHabitsScreen.kt | 8 ++ .../habits/list/views/CheckmarkButtonView.kt | 28 ++++- .../habits/list/views/CheckmarkPanelView.kt | 7 ++ .../habits/list/views/HabitCardView.kt | 4 + .../habits/show/ShowHabitScreen.java | 26 ++++- .../uhabits/intents/PendingIntentFactory.kt | 10 ++ .../notifications/AndroidNotificationTray.kt | 45 +++++--- .../uhabits/receivers/WidgetReceiver.java | 16 +++ .../isoron/uhabits/widgets/CheckmarkWidget.kt | 8 +- .../NumericalCheckmarkWidgetActivity.kt | 2 +- .../YesNoCheckmarkWidgetActivity.kt | 76 +++++++++++++ .../widgets/views/CheckmarkWidgetView.java | 27 ++++- .../layout/checkmark_option_picker_dialog.xml | 85 +++++++++++++++ .../src/main/res/values/fontawesome.xml | 1 + .../src/main/res/values/strings.xml | 9 ++ .../src/main/res/xml/preferences.xml | 7 ++ .../commands/CreateRepetitionCommand.java | 1 + .../isoron/uhabits/core/models/Checkmark.java | 9 +- .../uhabits/core/models/CheckmarkList.java | 18 ++- .../org/isoron/uhabits/core/models/Habit.java | 2 +- .../uhabits/core/models/Repetition.java | 14 ++- .../uhabits/core/models/RepetitionList.java | 2 +- .../isoron/uhabits/core/models/ScoreList.java | 16 +-- .../uhabits/core/models/StreakList.java | 35 ++++-- .../uhabits/core/preferences/Preferences.java | 10 ++ .../habits/list/ListHabitsBehavior.java | 24 ++++ .../core/ui/widgets/WidgetBehavior.java | 3 + 32 files changed, 570 insertions(+), 63 deletions(-) mode change 100644 => 100755 android/uhabits-android/src/androidTest/assets/views-v26/habits/list/CheckmarkButtonView/render_implicit_check.png mode change 100644 => 100755 android/uhabits-android/src/androidTest/assets/views-v26/habits/list/CheckmarkPanelView/render.png create mode 100644 android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/dialogs/CheckmarkOptionPickerFactory.kt create mode 100644 android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/YesNoCheckmarkWidgetActivity.kt create mode 100644 android/uhabits-android/src/main/res/layout/checkmark_option_picker_dialog.xml diff --git a/android/uhabits-android/src/androidTest/assets/views-v26/habits/list/CheckmarkButtonView/render_implicit_check.png b/android/uhabits-android/src/androidTest/assets/views-v26/habits/list/CheckmarkButtonView/render_implicit_check.png old mode 100644 new mode 100755 index 2f752976811ab29c905ee1e6173c7abb6c262717..e13702bafbd8912eb4c172d34bf52563296339a1 GIT binary patch delta 595 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 Y0PnUPO*&PU(EtDd07*qoM6N<$g4`M8&j0`b delta 1296 zcmV+r1@HR5489AHB!2{RLP=Bz2nYy#2xN!=00nhPL_t(|obBDsYg}a%$MN6EOw1Lm z(#BweohH$Me?Z$bH5CeOlxPW7+l9IjA-Zv4#BO{Eh|(@np@MazxX?`l8mg@z2%!{B z-}WMbNt=W*lr*IU8Jp=mZf0~E=Pl`-dG5@7KfBy}Gn^dA?*=f>bLWl(k-<%o&^PO z_bZu99yPOfb3ZysANTh5zVEZ9Ms$DW%9W>`b0;Njan79&g5de??(S>Fu1%#T*!rcL8B47e10?QfgCb)9rfJ)Jo{TUlqb%&bgH!2yWK1rbd;?WOka_|H5CA z%xqUWoj#pPrG_P`O!!MuXKsK0Ad&;1&36Ii>^Zh)OSRVyObm^d3m+%9wCb|YOpi}z z!l|zL(SN@d7Z;zX_S$4Jd8AzUOS%z@#a`*^>bey|X{}(&Id{I)$@~zg5dO-&##Buz z3_&E9fclU3cXX~k@~xR2aL%1ty48MYZhX4r?)$*R&}f_47p2ZSxut(hHoNcgJHP#2 zn~IwY{r78OVWBgV$xKPwD`}>qqho)mjYsL<@IPGUC2zE~wH?~Nef!U$7u&=tEbUZO zYQG>#A~yhX;d9c=_WyU({q*O}_7{qU5aAOp`tMgR{C|`*m_Jr7NC^Mh+UO8ygz%Rl zxCGRh+4{}h;Uh`*?ACt=SHJ5W-xm}fC49n1|NUBBUHx7K;V<2_=G;|yoba!`?(Xhu zK@bc(=Pp+JJB^QRHq4xXkzftPEbMDO2t@Zs zKl<<2)vH$@&t|hFHwz1crC`m!)c-KyAEIjC2{cmpOA#9YlC*#O*xpH#UMzLqId?{~ zGcxxX;S)ak?^iOJJZol8l{)X7yJ%)-CA}@ezit=+by9o{x_X4qy`lep725+?C&E8u z>)i)vq#+Qw0U&KWgb1H-(0{+~VF(cZA#3(c;7AStY5gHY_=Jo8`?Y}~K=_A@+dw_5 zHiQVD+Mxe_tz&-(5dI;{J=JqM_h7f8@^=PR@jZOid^{5eT8WiBK9Yh$ER_y<8STJ5_v6;Ck-0A}R6%Kq_4xLWEEK(0{*53<1Kwh7yUy zteG8@v{HDy*t&k1A+Ta*2c!BVP$}Yj0BiLh@9%hcb?YZiddx|OFN~f0!*@-sg#P=L z+Y>k;=}|NLAj;2~Zx(zZ3;?vSX8uY@k-<%o&;=N%tA7B^i|D*6+x*G^0000 + + + + + + (buttonId) + button.setOnClickListener{ + callback.onCheckmarkOptionPicked(buttonValue) + dialog.dismiss() + } + button.isPressed = ( + valuesToButton.containsKey(value) && + valuesToButton[value] == buttonId) + button.typeface = InterfaceUtils.getFontAwesome(context) + button.background.setTint(habitColor) + } + + val questionTextView = view.findViewById(R.id.choose_checkmark_question_textview) + var question = context.resources.getString(R.string.default_checkmark_option_question) + if (habit.question.isNotEmpty()) { + question = habit.question.trim('?') + } + val questionFullText = context.resources.getString( + R.string.choose_checkmark_question, question, habitTimestamp) + questionTextView.text = questionFullText + questionTextView.setTextColor(habitColor) + + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) + + return dialog + } +} diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java index 68d761034..cc7356d88 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java @@ -154,7 +154,6 @@ public class HistoryChart extends ScrollableChart newValue = Repetition.nextToggleValue(checkmarks[offset]); checkmarks[offset] = newValue; } - controller.onToggleCheckmark(timestamp, newValue); postInvalidate(); return true; @@ -363,6 +362,18 @@ public class HistoryChart extends ScrollableChart headerOverflow = Math.max(0, headerOverflow - columnWidth); } + private boolean isNotCompleted(int checkmark) + { + return (checkmark == 0 || + (!isNumerical && checkmark == UNCHECKED_EXPLICITLY)); + } + + private boolean isImplicitlyCompleted(int checkmark) + { + if (isNumerical) return checkmark < target; + return (checkmark == SKIPPED || checkmark == CHECKED_IMPLICITLY); + } + private void drawSquare(Canvas canvas, RectF location, GregorianCalendar date, @@ -378,12 +389,12 @@ public class HistoryChart extends ScrollableChart else { checkmark = checkmarks[checkmarkOffset]; - if(checkmark == 0) + if(isNotCompleted(checkmark)) { pSquareBg.setColor(colors[0]); pSquareFg.setColor(textColors[1]); } - else if(checkmark < target) + else if(isImplicitlyCompleted(checkmark)) { pSquareBg.setColor(colors[1]); pSquareFg.setColor(textColors[2]); @@ -452,16 +463,13 @@ public class HistoryChart extends ScrollableChart if (isBackgroundTransparent) primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); - - int red = Color.red(primaryColor); - int green = Color.green(primaryColor); - int blue = Color.blue(primaryColor); + int lighterPrimaryColor = ColorUtils.setAlpha(primaryColor, 0.5f); if (isBackgroundTransparent) { colors = new int[3]; colors[0] = Color.argb(16, 255, 255, 255); - colors[1] = Color.argb(128, red, green, blue); + colors[1] = lighterPrimaryColor; colors[2] = primaryColor; textColors = new int[3]; @@ -474,7 +482,7 @@ public class HistoryChart extends ScrollableChart { colors = new int[3]; colors[0] = res.getColor(R.attr.lowContrastTextColor); - colors[1] = Color.argb(127, red, green, blue); + colors[1] = lighterPrimaryColor; colors[2] = primaryColor; textColors = new int[3]; diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt index e4c3752a1..86867611b 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt @@ -65,6 +65,7 @@ class ListHabitsScreen private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory, private val colorPickerFactory: ColorPickerDialogFactory, private val numberPickerFactory: NumberPickerFactory, + private val checkmarkOptionPickerFactory: CheckmarkOptionPickerFactory, private val behavior: Lazy, private val menu: Lazy, private val selectionMenu: Lazy @@ -204,6 +205,13 @@ class ListHabitsScreen numberPickerFactory.create(value, unit, callback).show() } + override fun showCheckmarkOptions(habit: Habit, + timestamp: Timestamp, + value: Int, + callback: ListHabitsBehavior.CheckmarkOptionsCallback) { + checkmarkOptionPickerFactory.create(habit, timestamp.toString(), value, callback).show() + } + @StringRes private fun getExecuteString(command: Command): Int? { when (command) { diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt index aebdae734..4ee352d9e 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.kt @@ -26,6 +26,7 @@ import android.view.* import android.view.View.MeasureSpec.* import com.google.auto.factory.* import org.isoron.androidbase.activities.* +import org.isoron.androidbase.utils.* import org.isoron.uhabits.* import org.isoron.uhabits.core.models.* import org.isoron.uhabits.core.models.Checkmark.* @@ -53,6 +54,7 @@ class CheckmarkButtonView( } var onToggle: (Int) -> Unit = {} + var onToggleWithOptions: () -> Unit = {} private var drawer = Drawer() init { @@ -68,13 +70,22 @@ class CheckmarkButtonView( invalidate() } + fun performToggleWithOptions() { + onToggleWithOptions() + performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS) + } + override fun onClick(v: View) { if (preferences.isShortToggleEnabled) performToggle() + else if (preferences.isAdvancedCheckmarksEnabled) performToggleWithOptions() else showMessage(R.string.long_press_to_toggle) } override fun onLongClick(v: View): Boolean { - performToggle() + if (preferences.isShortToggleEnabled && preferences.isAdvancedCheckmarksEnabled) { + performToggleWithOptions() + } + else performToggle() return true } @@ -102,14 +113,22 @@ class CheckmarkButtonView( } fun draw(canvas: Canvas) { + val lighterColor = ColorUtils.setAlpha(color, 0.5f) + paint.color = when (value) { CHECKED_EXPLICITLY -> color - SKIPPED -> color - else -> lowContrastColor + UNCHECKED_EXPLICITLY -> lowContrastColor + UNCHECKED -> lowContrastColor + else -> lighterColor + } + var unchecked_symbol = R.string.fa_times + if (preferences.isAdvancedCheckmarksEnabled) { + unchecked_symbol = R.string.fa_question } val id = when (value) { SKIPPED -> R.string.fa_skipped - UNCHECKED -> R.string.fa_times + UNCHECKED -> unchecked_symbol + UNCHECKED_EXPLICITLY -> R.string.fa_times else -> R.string.fa_check } val label = resources.getString(id) @@ -121,3 +140,4 @@ class CheckmarkButtonView( } } } + diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt index 81ee667c8..ecbd88c4e 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.kt @@ -52,6 +52,12 @@ class CheckmarkPanelView( setupButtons() } + var onToggleWithOptions: (Timestamp) -> Unit = {} + set(value) { + field = value + setupButtons() + } + override fun createButton(): CheckmarkButtonView = buttonFactory.create() @Synchronized @@ -66,6 +72,7 @@ class CheckmarkPanelView( } button.color = color button.onToggle = { value -> onToggle(timestamp, value) } + button.onToggleWithOptions = { onToggleWithOptions (timestamp) } } } } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt index e7891253d..bd084f1b9 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.kt @@ -125,6 +125,10 @@ class HabitCardView( triggerRipple(timestamp) habit?.let { behavior.onToggle(it, timestamp, value) } } + onToggleWithOptions = { timestamp -> + triggerRipple(timestamp) + habit?.let { behavior.onToggleWithOptions(it, timestamp) } + } } numberPanel = numberPanelFactory.create().apply { diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java index 3e85f80b9..8d98617e7 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java @@ -26,8 +26,8 @@ import androidx.annotation.NonNull; import org.isoron.androidbase.activities.*; import org.isoron.uhabits.*; import org.isoron.uhabits.activities.common.dialogs.*; -import org.isoron.uhabits.activities.habits.edit.*; import org.isoron.uhabits.core.models.*; +import org.isoron.uhabits.core.preferences.*; import org.isoron.uhabits.core.ui.callbacks.*; import org.isoron.uhabits.core.ui.screens.habits.show.*; import org.isoron.uhabits.intents.*; @@ -49,19 +49,27 @@ public class ShowHabitScreen extends BaseScreen @NonNull private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory; + @NonNull + private final CheckmarkOptionPickerFactory checkmarkOptionPickerFactory; + private final Lazy behavior; @NonNull private final IntentFactory intentFactory; + @NonNull + private final Preferences prefs; + @Inject public ShowHabitScreen(@NonNull BaseActivity activity, @NonNull Habit habit, @NonNull ShowHabitRootView view, @NonNull ShowHabitsMenu menu, @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, + @NonNull CheckmarkOptionPickerFactory checkmarkOptionPickerFactory, @NonNull IntentFactory intentFactory, - @NonNull Lazy behavior) + @NonNull Lazy behavior, + @NonNull Preferences prefs) { super(activity); this.intentFactory = intentFactory; @@ -71,6 +79,8 @@ public class ShowHabitScreen extends BaseScreen this.habit = habit; this.behavior = behavior; this.confirmDeleteDialogFactory = confirmDeleteDialogFactory; + this.checkmarkOptionPickerFactory = checkmarkOptionPickerFactory; + this.prefs = prefs; view.setController(this); } @@ -83,7 +93,17 @@ public class ShowHabitScreen extends BaseScreen @Override public void onToggleCheckmark(Timestamp timestamp, int value) { - behavior.get().onToggleCheckmark(timestamp, value); + if (prefs.isAdvancedCheckmarksEnabled()) + { + CheckmarkList checkmarks = habit.getCheckmarks(); + int oldValue = checkmarks.getValues(timestamp, timestamp)[0]; + checkmarkOptionPickerFactory.create(habit, timestamp.toString(), oldValue, + newValue -> + { + behavior.get().onToggleCheckmark(timestamp, newValue); + }).show(); + } + else behavior.get().onToggleCheckmark(timestamp, value);; } @Override 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 cb2f84532..047d72f45 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 @@ -118,4 +118,14 @@ class PendingIntentFactory if (timestamp != null) putExtra("timestamp", timestamp) }, FLAG_UPDATE_CURRENT) + + fun setYesNoValue(habit: Habit, timestamp: Timestamp?): PendingIntent = + PendingIntent.getBroadcast( + context, 0, + Intent(context, WidgetReceiver::class.java).apply { + data = Uri.parse(habit.uriString) + action = WidgetReceiver.ACTION_SET_YESNO_VALUE + if (timestamp != null) putExtra("timestamp", timestamp.unixTime) + }, + FLAG_UPDATE_CURRENT) } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt index 1f6ab6ad0..c146dfbf9 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/notifications/AndroidNotificationTray.kt @@ -44,7 +44,7 @@ class AndroidNotificationTray private val pendingIntents: PendingIntentFactory, private val preferences: Preferences, private val ringtoneManager: RingtoneManager - ) : NotificationTray.SystemTray { +) : NotificationTray.SystemTray { private var active = HashSet() override fun log(msg: String) { @@ -69,11 +69,11 @@ class AndroidNotificationTray } catch (e: RuntimeException) { // Some Xiaomi phones produce a RuntimeException if custom notification sounds are used. Log.i("AndroidNotificationTray", - "Failed to show notification. Retrying without sound.") + "Failed to show notification. Retrying without sound.") val n = buildNotification(habit, - reminderTime, - timestamp, - disableSound = true) + reminderTime, + timestamp, + disableSound = true) notificationManager.notify(notificationId, n) } @@ -95,6 +95,11 @@ class AndroidNotificationTray context.getString(R.string.no), pendingIntents.removeRepetition(habit)) + val enterRepetitionAction = Action( + R.drawable.ic_action_check, + context.getString(R.string.enter), + pendingIntents.setYesNoValue(habit, timestamp)) + val wearableBg = decodeResource(context.resources, R.drawable.stripe) // Even though the set of actions is the same on the phone and @@ -102,8 +107,13 @@ class AndroidNotificationTray // WearableExtender. val wearableExtender = WearableExtender() .setBackground(wearableBg) - .addAction(addRepetitionAction) - .addAction(removeRepetitionAction) + + if (preferences.isAdvancedCheckmarksEnabled) { + wearableExtender.addAction(enterRepetitionAction) + } else { + wearableExtender.addAction(addRepetitionAction) + .addAction(removeRepetitionAction) + } val defaultText = context.getString(R.string.default_reminder_question) val builder = Builder(context, REMINDERS_CHANNEL_ID) @@ -112,9 +122,14 @@ class AndroidNotificationTray .setContentText(if(habit.question.isBlank()) defaultText else habit.question) .setContentIntent(pendingIntents.showHabit(habit)) .setDeleteIntent(pendingIntents.dismissNotification(habit)) - .addAction(addRepetitionAction) - .addAction(removeRepetitionAction) - .setSound(null) + + if (preferences.isAdvancedCheckmarksEnabled) { + builder.addAction(enterRepetitionAction) + } else { + builder.addAction(addRepetitionAction) + .addAction(removeRepetitionAction) + } + builder.setSound(null) .setWhen(reminderTime) .setShowWhen(true) .setOngoing(preferences.shouldMakeNotificationsSticky()) @@ -126,8 +141,8 @@ class AndroidNotificationTray builder.setLights(Color.RED, 1000, 1000) val snoozeAction = Action(R.drawable.ic_action_snooze, - context.getString(R.string.snooze), - pendingIntents.snoozeNotification(habit)) + context.getString(R.string.snooze), + pendingIntents.snoozeNotification(habit)) wearableExtender.addAction(snoozeAction) builder.addAction(snoozeAction) @@ -142,11 +157,11 @@ class AndroidNotificationTray as NotificationManager if (SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel(REMINDERS_CHANNEL_ID, - context.resources.getString(R.string.reminder), - NotificationManager.IMPORTANCE_DEFAULT) + context.resources.getString(R.string.reminder), + NotificationManager.IMPORTANCE_DEFAULT) notificationManager.createNotificationChannel(channel) } } } -} +} \ No newline at end of file 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 2942f8141..8d091bd04 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 @@ -52,6 +52,9 @@ public class WidgetReceiver extends BroadcastReceiver public static final String ACTION_SET_NUMERICAL_VALUE = "org.isoron.uhabits.ACTION_SET_NUMERICAL_VALUE"; + public static final String ACTION_SET_YESNO_VALUE = + "org.isoron.uhabits.ACTION_SET_YESNO_VALUE"; + private static final String TAG = "WidgetReceiver"; @Override @@ -112,6 +115,19 @@ public class WidgetReceiver extends BroadcastReceiver parser.copyIntentData(intent,numberSelectorIntent); context.startActivity(numberSelectorIntent); break; + case ACTION_SET_YESNO_VALUE: + Log.d(TAG, String.format( + "onSetYesNoValue habit=%d timestamp=%d", + data.getHabit().getId(), + data.getTimestamp().getUnixTime())); + context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); + Intent checkmarkOptionsSelector = new Intent(context, YesNoCheckmarkWidgetActivity.class); + checkmarkOptionsSelector.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + checkmarkOptionsSelector.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + checkmarkOptionsSelector.setAction(YesNoCheckmarkWidgetActivity.ACTION_SHOW_YESNO_VALUE_ACTIVITY); + parser.copyIntentData(intent, checkmarkOptionsSelector); + context.startActivity(checkmarkOptionsSelector); + 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 588a7fa25..cd86b1ca4 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 @@ -22,6 +22,7 @@ package org.isoron.uhabits.widgets import android.app.* import android.content.* import android.view.* +import org.isoron.uhabits.* import org.isoron.uhabits.core.models.* import org.isoron.uhabits.utils.* import org.isoron.uhabits.widgets.views.* @@ -36,7 +37,12 @@ open class CheckmarkWidget( return if (habit.isNumerical) { pendingIntentFactory.setNumericalValue(context, habit, 10, null) } else { - pendingIntentFactory.toggleCheckmark(habit, null) + val prefs = (context.applicationContext as HabitsApplication).component.preferences + if (prefs.isAdvancedCheckmarksEnabled) { + pendingIntentFactory.setYesNoValue(habit, null) + } else { + pendingIntentFactory.toggleCheckmark(habit, null) + } } } 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 index fcba1cce8..7187cb5e9 100644 --- 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 @@ -23,7 +23,7 @@ import android.app.* import android.content.* import android.os.* import android.view.* -import android.widget.FrameLayout +import android.widget.* import org.isoron.uhabits.* import org.isoron.uhabits.activities.* import org.isoron.uhabits.activities.common.dialogs.* diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/YesNoCheckmarkWidgetActivity.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/YesNoCheckmarkWidgetActivity.kt new file mode 100644 index 000000000..d59c466bd --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/activities/YesNoCheckmarkWidgetActivity.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.* +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 YesNoCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.CheckmarkOptionsCallback { + + 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 + showCheckmarkOptions(this) + } + + override fun onCheckmarkOptionPicked(newValue: Int) { + behavior.setYesNoValue(data.habit, data.timestamp, newValue) + widgetUpdater.updateWidgets() + finish() + } + + override fun onCheckmarkOptionDismissed() { + finish() + } + + private fun showCheckmarkOptions(context: Context) { + val app = this.applicationContext as HabitsApplication + AndroidThemeSwitcher(this, app.component.preferences).apply() + val oldValue = data.habit.checkmarks.getValues(data.timestamp, data.timestamp)[0] + val checkmarkOptionsPickerFactory = CheckmarkOptionPickerFactory(context) + checkmarkOptionsPickerFactory.create( + data.habit, data.timestamp.toString(), oldValue, this).show() + } + + companion object { + const val ACTION_SHOW_YESNO_VALUE_ACTIVITY = "org.isoron.uhabits.ACTION_SHOW_YESNO_VALUE_ACTIVITY" + } +} 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 c278a6635..04a11136c 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 @@ -73,27 +73,35 @@ public class CheckmarkWidgetView extends HabitWidgetView { int bgColor; int fgColor; + int lighterActiveColor = ColorUtils.setAlpha(activeColor, 0.5f); switch (checkmarkState) { case Checkmark.CHECKED_EXPLICITLY: - case Checkmark.SKIPPED: bgColor = activeColor; fgColor = res.getColor(R.attr.highContrastReverseTextColor); setShadowAlpha(0x4f); - backgroundPaint.setColor(bgColor); frame.setBackgroundDrawable(background); break; - + case Checkmark.UNCHECKED_EXPLICITLY: + bgColor = res.getColor(R.attr.highlightedBackgroundColor); + fgColor = res.getColor(R.attr.highContrastReverseTextColor); + setShadowAlpha(0x4f); + break; + case Checkmark.SKIPPED: case Checkmark.CHECKED_IMPLICITLY: + bgColor = lighterActiveColor; + fgColor = res.getColor(R.attr.highContrastReverseTextColor); + setShadowAlpha(0x4f); + break; case Checkmark.UNCHECKED: default: - getResources().getString(R.string.fa_times); bgColor = res.getColor(R.attr.cardBgColor); fgColor = res.getColor(R.attr.mediumContrastTextColor); setShadowAlpha(0x00); break; } + backgroundPaint.setColor(bgColor); ring.setPercentage(percentage); ring.setColor(fgColor); ring.setBackgroundColor(bgColor); @@ -113,6 +121,13 @@ public class CheckmarkWidgetView extends HabitWidgetView { protected String getText() { + HabitsApplication app = + (HabitsApplication) getContext().getApplicationContext(); + int uncheckedSymbol = R.string.fa_times; + if (app.getComponent().getPreferences().isAdvancedCheckmarksEnabled()) + { + uncheckedSymbol = R.string.fa_question; + } if (isNumerical) return NumberButtonViewKt.toShortString(checkmarkValue / 1000.0); switch (checkmarkState) { case Checkmark.CHECKED_EXPLICITLY: @@ -120,9 +135,11 @@ public class CheckmarkWidgetView extends HabitWidgetView { return getResources().getString(R.string.fa_check); case Checkmark.SKIPPED: return getResources().getString(R.string.fa_skipped); + case Checkmark.UNCHECKED_EXPLICITLY: + return getResources().getString(R.string.fa_times); case Checkmark.UNCHECKED: default: - return getResources().getString(R.string.fa_times); + return getResources().getString(uncheckedSymbol); } } diff --git a/android/uhabits-android/src/main/res/layout/checkmark_option_picker_dialog.xml b/android/uhabits-android/src/main/res/layout/checkmark_option_picker_dialog.xml new file mode 100644 index 000000000..036d74ab3 --- /dev/null +++ b/android/uhabits-android/src/main/res/layout/checkmark_option_picker_dialog.xml @@ -0,0 +1,85 @@ + + + + + + + + + +