Advanced Options Dialog with explicit Fail

pull/629/head
KristianTashkov 5 years ago
parent 84523869e8
commit fdd9346d1c

Binary file not shown.

Before

Width:  |  Height:  |  Size: 543 B

After

Width:  |  Height:  |  Size: 620 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -119,6 +119,17 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".widgets.activities.YesNoCheckmarkWidgetActivity"
android:label="YesNoCheckmarkWidget"
android:noHistory="true"
android:excludeFromRecents="true"
android:theme="@style/Theme.AppCompat.Light.Dialog">
<intent-filter>
<action android:name="org.isoron.uhabits.ACTION_SHOW_YESNO_VALUE_ACTIVITY"/>
</intent-filter>
</activity>
<activity android:name=".notifications.SnoozeDelayPickerActivity" <activity android:name=".notifications.SnoozeDelayPickerActivity"
android:excludeFromRecents="true" android:excludeFromRecents="true"
android:launchMode="singleInstance" android:launchMode="singleInstance"

@ -0,0 +1,103 @@
/*
* Copyright (C) 2017 Á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.activities.common.dialogs
import android.content.*
import android.view.*
import android.widget.*
import androidx.appcompat.app.*
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.ui.screens.habits.list.*
import org.isoron.uhabits.utils.*
import javax.inject.*
class CheckmarkOptionPickerFactory
@Inject constructor(
@ActivityContext private val context: Context
) {
fun create(habit: Habit,
habitTimestamp: String,
value: Int,
callback: ListHabitsBehavior.CheckmarkOptionsCallback): AlertDialog {
var habitColor = PaletteUtils.getColor(context, habit.color)
val res = StyledResources(context)
val titleTextView = TextView(context)
titleTextView.setText(habit.name)
titleTextView.setTextSize(20F)
titleTextView.setPadding(20, 30, 20, 30);
titleTextView.setTextColor(res.getColor(R.attr.highContrastReverseTextColor))
titleTextView.setBackgroundColor(habitColor)
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.checkmark_option_picker_dialog, null)
val dialog = AlertDialog.Builder(context)
.setView(view)
.setCustomTitle(titleTextView)
.setOnDismissListener{
callback.onCheckmarkOptionDismissed()
}
.create()
val buttonValues = mapOf(
R.id.yes_button to Checkmark.CHECKED_EXPLICITLY,
R.id.skip_button to Checkmark.SKIPPED,
R.id.no_button to Checkmark.UNCHECKED_EXPLICITLY,
R.id.clear_button to Checkmark.UNCHECKED
)
val valuesToButton = mapOf(
Checkmark.CHECKED_EXPLICITLY to R.id.yes_button,
Checkmark.SKIPPED to R.id.skip_button ,
Checkmark.UNCHECKED_EXPLICITLY to R.id.no_button
)
for ((buttonId, buttonValue) in buttonValues) {
val button = view.findViewById<Button>(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<TextView>(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
}
}

@ -154,7 +154,6 @@ public class HistoryChart extends ScrollableChart
newValue = Repetition.nextToggleValue(checkmarks[offset]); newValue = Repetition.nextToggleValue(checkmarks[offset]);
checkmarks[offset] = newValue; checkmarks[offset] = newValue;
} }
controller.onToggleCheckmark(timestamp, newValue); controller.onToggleCheckmark(timestamp, newValue);
postInvalidate(); postInvalidate();
return true; return true;
@ -363,6 +362,18 @@ public class HistoryChart extends ScrollableChart
headerOverflow = Math.max(0, headerOverflow - columnWidth); 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, private void drawSquare(Canvas canvas,
RectF location, RectF location,
GregorianCalendar date, GregorianCalendar date,
@ -378,12 +389,12 @@ public class HistoryChart extends ScrollableChart
else else
{ {
checkmark = checkmarks[checkmarkOffset]; checkmark = checkmarks[checkmarkOffset];
if(checkmark == 0) if(isNotCompleted(checkmark))
{ {
pSquareBg.setColor(colors[0]); pSquareBg.setColor(colors[0]);
pSquareFg.setColor(textColors[1]); pSquareFg.setColor(textColors[1]);
} }
else if(checkmark < target) else if(isImplicitlyCompleted(checkmark))
{ {
pSquareBg.setColor(colors[1]); pSquareBg.setColor(colors[1]);
pSquareFg.setColor(textColors[2]); pSquareFg.setColor(textColors[2]);
@ -452,16 +463,13 @@ public class HistoryChart extends ScrollableChart
if (isBackgroundTransparent) if (isBackgroundTransparent)
primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f); primaryColor = ColorUtils.setMinValue(primaryColor, 0.75f);
int lighterPrimaryColor = ColorUtils.setAlpha(primaryColor, 0.5f);
int red = Color.red(primaryColor);
int green = Color.green(primaryColor);
int blue = Color.blue(primaryColor);
if (isBackgroundTransparent) if (isBackgroundTransparent)
{ {
colors = new int[3]; colors = new int[3];
colors[0] = Color.argb(16, 255, 255, 255); colors[0] = Color.argb(16, 255, 255, 255);
colors[1] = Color.argb(128, red, green, blue); colors[1] = lighterPrimaryColor;
colors[2] = primaryColor; colors[2] = primaryColor;
textColors = new int[3]; textColors = new int[3];
@ -474,7 +482,7 @@ public class HistoryChart extends ScrollableChart
{ {
colors = new int[3]; colors = new int[3];
colors[0] = res.getColor(R.attr.lowContrastTextColor); colors[0] = res.getColor(R.attr.lowContrastTextColor);
colors[1] = Color.argb(127, red, green, blue); colors[1] = lighterPrimaryColor;
colors[2] = primaryColor; colors[2] = primaryColor;
textColors = new int[3]; textColors = new int[3];

@ -65,6 +65,7 @@ class ListHabitsScreen
private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory, private val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory,
private val colorPickerFactory: ColorPickerDialogFactory, private val colorPickerFactory: ColorPickerDialogFactory,
private val numberPickerFactory: NumberPickerFactory, private val numberPickerFactory: NumberPickerFactory,
private val checkmarkOptionPickerFactory: CheckmarkOptionPickerFactory,
private val behavior: Lazy<ListHabitsBehavior>, private val behavior: Lazy<ListHabitsBehavior>,
private val menu: Lazy<ListHabitsMenu>, private val menu: Lazy<ListHabitsMenu>,
private val selectionMenu: Lazy<ListHabitsSelectionMenu> private val selectionMenu: Lazy<ListHabitsSelectionMenu>
@ -204,6 +205,13 @@ class ListHabitsScreen
numberPickerFactory.create(value, unit, callback).show() 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 @StringRes
private fun getExecuteString(command: Command): Int? { private fun getExecuteString(command: Command): Int? {
when (command) { when (command) {

@ -26,6 +26,7 @@ import android.view.*
import android.view.View.MeasureSpec.* import android.view.View.MeasureSpec.*
import com.google.auto.factory.* import com.google.auto.factory.*
import org.isoron.androidbase.activities.* import org.isoron.androidbase.activities.*
import org.isoron.androidbase.utils.*
import org.isoron.uhabits.* import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.* import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.core.models.Checkmark.* import org.isoron.uhabits.core.models.Checkmark.*
@ -53,6 +54,7 @@ class CheckmarkButtonView(
} }
var onToggle: (Int) -> Unit = {} var onToggle: (Int) -> Unit = {}
var onToggleWithOptions: () -> Unit = {}
private var drawer = Drawer() private var drawer = Drawer()
init { init {
@ -68,13 +70,22 @@ class CheckmarkButtonView(
invalidate() invalidate()
} }
fun performToggleWithOptions() {
onToggleWithOptions()
performHapticFeedback(HapticFeedbackConstants.KEYBOARD_PRESS)
}
override fun onClick(v: View) { override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) performToggle() if (preferences.isShortToggleEnabled) performToggle()
else if (preferences.isAdvancedCheckmarksEnabled) performToggleWithOptions()
else showMessage(R.string.long_press_to_toggle) else showMessage(R.string.long_press_to_toggle)
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
performToggle() if (preferences.isShortToggleEnabled && preferences.isAdvancedCheckmarksEnabled) {
performToggleWithOptions()
}
else performToggle()
return true return true
} }
@ -102,14 +113,22 @@ class CheckmarkButtonView(
} }
fun draw(canvas: Canvas) { fun draw(canvas: Canvas) {
val lighterColor = ColorUtils.setAlpha(color, 0.5f)
paint.color = when (value) { paint.color = when (value) {
CHECKED_EXPLICITLY -> color CHECKED_EXPLICITLY -> color
SKIPPED -> color UNCHECKED_EXPLICITLY -> lowContrastColor
else -> 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) { val id = when (value) {
SKIPPED -> R.string.fa_skipped SKIPPED -> R.string.fa_skipped
UNCHECKED -> R.string.fa_times UNCHECKED -> unchecked_symbol
UNCHECKED_EXPLICITLY -> R.string.fa_times
else -> R.string.fa_check else -> R.string.fa_check
} }
val label = resources.getString(id) val label = resources.getString(id)
@ -121,3 +140,4 @@ class CheckmarkButtonView(
} }
} }
} }

@ -52,6 +52,12 @@ class CheckmarkPanelView(
setupButtons() setupButtons()
} }
var onToggleWithOptions: (Timestamp) -> Unit = {}
set(value) {
field = value
setupButtons()
}
override fun createButton(): CheckmarkButtonView = buttonFactory.create() override fun createButton(): CheckmarkButtonView = buttonFactory.create()
@Synchronized @Synchronized
@ -66,6 +72,7 @@ class CheckmarkPanelView(
} }
button.color = color button.color = color
button.onToggle = { value -> onToggle(timestamp, value) } button.onToggle = { value -> onToggle(timestamp, value) }
button.onToggleWithOptions = { onToggleWithOptions (timestamp) }
} }
} }
} }

@ -125,6 +125,10 @@ class HabitCardView(
triggerRipple(timestamp) triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp, value) } habit?.let { behavior.onToggle(it, timestamp, value) }
} }
onToggleWithOptions = { timestamp ->
triggerRipple(timestamp)
habit?.let { behavior.onToggleWithOptions(it, timestamp) }
}
} }
numberPanel = numberPanelFactory.create().apply { numberPanel = numberPanelFactory.create().apply {

@ -26,8 +26,8 @@ import androidx.annotation.NonNull;
import org.isoron.androidbase.activities.*; import org.isoron.androidbase.activities.*;
import org.isoron.uhabits.*; import org.isoron.uhabits.*;
import org.isoron.uhabits.activities.common.dialogs.*; 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.models.*;
import org.isoron.uhabits.core.preferences.*;
import org.isoron.uhabits.core.ui.callbacks.*; import org.isoron.uhabits.core.ui.callbacks.*;
import org.isoron.uhabits.core.ui.screens.habits.show.*; import org.isoron.uhabits.core.ui.screens.habits.show.*;
import org.isoron.uhabits.intents.*; import org.isoron.uhabits.intents.*;
@ -49,19 +49,27 @@ public class ShowHabitScreen extends BaseScreen
@NonNull @NonNull
private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory; private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory;
@NonNull
private final CheckmarkOptionPickerFactory checkmarkOptionPickerFactory;
private final Lazy<ShowHabitBehavior> behavior; private final Lazy<ShowHabitBehavior> behavior;
@NonNull @NonNull
private final IntentFactory intentFactory; private final IntentFactory intentFactory;
@NonNull
private final Preferences prefs;
@Inject @Inject
public ShowHabitScreen(@NonNull BaseActivity activity, public ShowHabitScreen(@NonNull BaseActivity activity,
@NonNull Habit habit, @NonNull Habit habit,
@NonNull ShowHabitRootView view, @NonNull ShowHabitRootView view,
@NonNull ShowHabitsMenu menu, @NonNull ShowHabitsMenu menu,
@NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory,
@NonNull CheckmarkOptionPickerFactory checkmarkOptionPickerFactory,
@NonNull IntentFactory intentFactory, @NonNull IntentFactory intentFactory,
@NonNull Lazy<ShowHabitBehavior> behavior) @NonNull Lazy<ShowHabitBehavior> behavior,
@NonNull Preferences prefs)
{ {
super(activity); super(activity);
this.intentFactory = intentFactory; this.intentFactory = intentFactory;
@ -71,6 +79,8 @@ public class ShowHabitScreen extends BaseScreen
this.habit = habit; this.habit = habit;
this.behavior = behavior; this.behavior = behavior;
this.confirmDeleteDialogFactory = confirmDeleteDialogFactory; this.confirmDeleteDialogFactory = confirmDeleteDialogFactory;
this.checkmarkOptionPickerFactory = checkmarkOptionPickerFactory;
this.prefs = prefs;
view.setController(this); view.setController(this);
} }
@ -83,7 +93,17 @@ public class ShowHabitScreen extends BaseScreen
@Override @Override
public void onToggleCheckmark(Timestamp timestamp, int value) 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 @Override

@ -118,4 +118,14 @@ class PendingIntentFactory
if (timestamp != null) putExtra("timestamp", timestamp) if (timestamp != null) putExtra("timestamp", timestamp)
}, },
FLAG_UPDATE_CURRENT) 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)
} }

@ -44,7 +44,7 @@ class AndroidNotificationTray
private val pendingIntents: PendingIntentFactory, private val pendingIntents: PendingIntentFactory,
private val preferences: Preferences, private val preferences: Preferences,
private val ringtoneManager: RingtoneManager private val ringtoneManager: RingtoneManager
) : NotificationTray.SystemTray { ) : NotificationTray.SystemTray {
private var active = HashSet<Int>() private var active = HashSet<Int>()
override fun log(msg: String) { override fun log(msg: String) {
@ -69,11 +69,11 @@ class AndroidNotificationTray
} catch (e: RuntimeException) { } catch (e: RuntimeException) {
// Some Xiaomi phones produce a RuntimeException if custom notification sounds are used. // Some Xiaomi phones produce a RuntimeException if custom notification sounds are used.
Log.i("AndroidNotificationTray", Log.i("AndroidNotificationTray",
"Failed to show notification. Retrying without sound.") "Failed to show notification. Retrying without sound.")
val n = buildNotification(habit, val n = buildNotification(habit,
reminderTime, reminderTime,
timestamp, timestamp,
disableSound = true) disableSound = true)
notificationManager.notify(notificationId, n) notificationManager.notify(notificationId, n)
} }
@ -95,6 +95,11 @@ class AndroidNotificationTray
context.getString(R.string.no), context.getString(R.string.no),
pendingIntents.removeRepetition(habit)) 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) val wearableBg = decodeResource(context.resources, R.drawable.stripe)
// Even though the set of actions is the same on the phone and // Even though the set of actions is the same on the phone and
@ -102,8 +107,13 @@ class AndroidNotificationTray
// WearableExtender. // WearableExtender.
val wearableExtender = WearableExtender() val wearableExtender = WearableExtender()
.setBackground(wearableBg) .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 defaultText = context.getString(R.string.default_reminder_question)
val builder = Builder(context, REMINDERS_CHANNEL_ID) val builder = Builder(context, REMINDERS_CHANNEL_ID)
@ -112,9 +122,14 @@ class AndroidNotificationTray
.setContentText(if(habit.question.isBlank()) defaultText else habit.question) .setContentText(if(habit.question.isBlank()) defaultText else habit.question)
.setContentIntent(pendingIntents.showHabit(habit)) .setContentIntent(pendingIntents.showHabit(habit))
.setDeleteIntent(pendingIntents.dismissNotification(habit)) .setDeleteIntent(pendingIntents.dismissNotification(habit))
.addAction(addRepetitionAction)
.addAction(removeRepetitionAction) if (preferences.isAdvancedCheckmarksEnabled) {
.setSound(null) builder.addAction(enterRepetitionAction)
} else {
builder.addAction(addRepetitionAction)
.addAction(removeRepetitionAction)
}
builder.setSound(null)
.setWhen(reminderTime) .setWhen(reminderTime)
.setShowWhen(true) .setShowWhen(true)
.setOngoing(preferences.shouldMakeNotificationsSticky()) .setOngoing(preferences.shouldMakeNotificationsSticky())
@ -126,8 +141,8 @@ class AndroidNotificationTray
builder.setLights(Color.RED, 1000, 1000) builder.setLights(Color.RED, 1000, 1000)
val snoozeAction = Action(R.drawable.ic_action_snooze, val snoozeAction = Action(R.drawable.ic_action_snooze,
context.getString(R.string.snooze), context.getString(R.string.snooze),
pendingIntents.snoozeNotification(habit)) pendingIntents.snoozeNotification(habit))
wearableExtender.addAction(snoozeAction) wearableExtender.addAction(snoozeAction)
builder.addAction(snoozeAction) builder.addAction(snoozeAction)
@ -142,11 +157,11 @@ class AndroidNotificationTray
as NotificationManager as NotificationManager
if (SDK_INT >= Build.VERSION_CODES.O) { if (SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(REMINDERS_CHANNEL_ID, val channel = NotificationChannel(REMINDERS_CHANNEL_ID,
context.resources.getString(R.string.reminder), context.resources.getString(R.string.reminder),
NotificationManager.IMPORTANCE_DEFAULT) NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
} }
} }
} }

@ -52,6 +52,9 @@ public class WidgetReceiver extends BroadcastReceiver
public static final String ACTION_SET_NUMERICAL_VALUE = public static final String ACTION_SET_NUMERICAL_VALUE =
"org.isoron.uhabits.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"; private static final String TAG = "WidgetReceiver";
@Override @Override
@ -112,6 +115,19 @@ public class WidgetReceiver extends BroadcastReceiver
parser.copyIntentData(intent,numberSelectorIntent); parser.copyIntentData(intent,numberSelectorIntent);
context.startActivity(numberSelectorIntent); context.startActivity(numberSelectorIntent);
break; 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) catch (RuntimeException e)

@ -22,6 +22,7 @@ package org.isoron.uhabits.widgets
import android.app.* import android.app.*
import android.content.* import android.content.*
import android.view.* import android.view.*
import org.isoron.uhabits.*
import org.isoron.uhabits.core.models.* import org.isoron.uhabits.core.models.*
import org.isoron.uhabits.utils.* import org.isoron.uhabits.utils.*
import org.isoron.uhabits.widgets.views.* import org.isoron.uhabits.widgets.views.*
@ -36,7 +37,12 @@ open class CheckmarkWidget(
return if (habit.isNumerical) { return if (habit.isNumerical) {
pendingIntentFactory.setNumericalValue(context, habit, 10, null) pendingIntentFactory.setNumericalValue(context, habit, 10, null)
} else { } 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)
}
} }
} }

@ -23,7 +23,7 @@ import android.app.*
import android.content.* import android.content.*
import android.os.* import android.os.*
import android.view.* import android.view.*
import android.widget.FrameLayout import android.widget.*
import org.isoron.uhabits.* import org.isoron.uhabits.*
import org.isoron.uhabits.activities.* import org.isoron.uhabits.activities.*
import org.isoron.uhabits.activities.common.dialogs.* import org.isoron.uhabits.activities.common.dialogs.*

@ -0,0 +1,76 @@
/*
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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"
}
}

@ -73,27 +73,35 @@ public class CheckmarkWidgetView extends HabitWidgetView {
int bgColor; int bgColor;
int fgColor; int fgColor;
int lighterActiveColor = ColorUtils.setAlpha(activeColor, 0.5f);
switch (checkmarkState) { switch (checkmarkState) {
case Checkmark.CHECKED_EXPLICITLY: case Checkmark.CHECKED_EXPLICITLY:
case Checkmark.SKIPPED:
bgColor = activeColor; bgColor = activeColor;
fgColor = res.getColor(R.attr.highContrastReverseTextColor); fgColor = res.getColor(R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f); setShadowAlpha(0x4f);
backgroundPaint.setColor(bgColor);
frame.setBackgroundDrawable(background); frame.setBackgroundDrawable(background);
break; 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: case Checkmark.CHECKED_IMPLICITLY:
bgColor = lighterActiveColor;
fgColor = res.getColor(R.attr.highContrastReverseTextColor);
setShadowAlpha(0x4f);
break;
case Checkmark.UNCHECKED: case Checkmark.UNCHECKED:
default: default:
getResources().getString(R.string.fa_times);
bgColor = res.getColor(R.attr.cardBgColor); bgColor = res.getColor(R.attr.cardBgColor);
fgColor = res.getColor(R.attr.mediumContrastTextColor); fgColor = res.getColor(R.attr.mediumContrastTextColor);
setShadowAlpha(0x00); setShadowAlpha(0x00);
break; break;
} }
backgroundPaint.setColor(bgColor);
ring.setPercentage(percentage); ring.setPercentage(percentage);
ring.setColor(fgColor); ring.setColor(fgColor);
ring.setBackgroundColor(bgColor); ring.setBackgroundColor(bgColor);
@ -113,6 +121,13 @@ public class CheckmarkWidgetView extends HabitWidgetView {
protected String getText() 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); if (isNumerical) return NumberButtonViewKt.toShortString(checkmarkValue / 1000.0);
switch (checkmarkState) { switch (checkmarkState) {
case Checkmark.CHECKED_EXPLICITLY: case Checkmark.CHECKED_EXPLICITLY:
@ -120,9 +135,11 @@ public class CheckmarkWidgetView extends HabitWidgetView {
return getResources().getString(R.string.fa_check); return getResources().getString(R.string.fa_check);
case Checkmark.SKIPPED: case Checkmark.SKIPPED:
return getResources().getString(R.string.fa_skipped); return getResources().getString(R.string.fa_skipped);
case Checkmark.UNCHECKED_EXPLICITLY:
return getResources().getString(R.string.fa_times);
case Checkmark.UNCHECKED: case Checkmark.UNCHECKED:
default: default:
return getResources().getString(R.string.fa_times); return getResources().getString(uncheckedSymbol);
} }
} }

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
~
~ 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 <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="5dp"
android:paddingVertical="20dp">
<TextView
android:id="@+id/choose_checkmark_question_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="5dp"
android:text="@string/default_checkmark_option_question"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/yes_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/yes_button_text"
android:textColor="?attr/highContrastReverseTextColor"
app:cornerRadius="0dp" />
<Button
android:id="@+id/no_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/no_button_text"
android:textColor="?attr/highContrastReverseTextColor"
app:cornerRadius="0dp" />
<Button
android:id="@+id/skip_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/skip_button_text"
android:textColor="?attr/highContrastReverseTextColor"
app:cornerRadius="0dp" />
<Button
android:id="@+id/clear_button"
style="@style/Widget.AppCompat.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/delete_button_text"
android:textColor="?attr/highContrastReverseTextColor"
app:cornerRadius="0dp" />
</LinearLayout>
</LinearLayout>

@ -24,6 +24,7 @@
<string translatable="false" name="fa_check">&#xf00c;</string> <string translatable="false" name="fa_check">&#xf00c;</string>
<string translatable="false" name="fa_times">&#xf00d;</string> <string translatable="false" name="fa_times">&#xf00d;</string>
<string translatable="false" name="fa_skipped">&#xf068;</string> <string translatable="false" name="fa_skipped">&#xf068;</string>
<string translatable="false" name="fa_question">&#xf128;</string>
<string translatable="false" name="fa_bell_o">&#xf0a2;</string> <string translatable="false" name="fa_bell_o">&#xf0a2;</string>
<string translatable="false" name="fa_calendar">&#xf073;</string> <string translatable="false" name="fa_calendar">&#xf073;</string>

@ -67,6 +67,8 @@
<string name="interval_custom">Custom...</string> <string name="interval_custom">Custom...</string>
<string name="pref_toggle_title">Toggle with short press</string> <string name="pref_toggle_title">Toggle with short press</string>
<string name="pref_toggle_description">Put checkmarks with a single tap instead of press-and-hold. More convenient, but might cause accidental toggles.</string> <string name="pref_toggle_description">Put checkmarks with a single tap instead of press-and-hold. More convenient, but might cause accidental toggles.</string>
<string name="pref_advanced_checkmarks_title">Enable advanced checkmarks</string>
<string name="pref_advanced_checkmarks_description">Enables checkmark selection to have additional states like explicitly failing a habit or skipping it for a day.</string>
<string name="pref_snooze_interval_title">Snooze interval on reminders</string> <string name="pref_snooze_interval_title">Snooze interval on reminders</string>
<string name="pref_rate_this_app">Rate this app on Google Play</string> <string name="pref_rate_this_app">Rate this app on Google Play</string>
<string name="pref_send_feedback">Send feedback to developer</string> <string name="pref_send_feedback">Send feedback to developer</string>
@ -162,6 +164,7 @@
<string name="export">Export</string> <string name="export">Export</string>
<string name="long_press_to_edit">Press-and-hold to change the value</string> <string name="long_press_to_edit">Press-and-hold to change the value</string>
<string name="change_value">Change value</string> <string name="change_value">Change value</string>
<string name="choose_checkmark_question">%1$s on %2$s?</string>
<string name="calendar">Calendar</string> <string name="calendar">Calendar</string>
<string name="unit">Unit</string> <string name="unit">Unit</string>
<string name="example_question_boolean">e.g. Did you exercise today?</string> <string name="example_question_boolean">e.g. Did you exercise today?</string>
@ -169,6 +172,7 @@
<string name="target">Target</string> <string name="target">Target</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="no">No</string> <string name="no">No</string>
<string name="enter">Enter</string>
<string name="customize_notification_summary">Change sound, vibration, light and other notification settings</string> <string name="customize_notification_summary">Change sound, vibration, light and other notification settings</string>
<string name="customize_notification">Customize notifications</string> <string name="customize_notification">Customize notifications</string>
<string name="pref_view_privacy">View privacy policy</string> <string name="pref_view_privacy">View privacy policy</string>
@ -194,4 +198,9 @@
<string name="every_month">Every month</string> <string name="every_month">Every month</string>
<string name="validation_cannot_be_blank">Cannot be blank</string> <string name="validation_cannot_be_blank">Cannot be blank</string>
<string name="today">Today</string> <string name="today">Today</string>
<string name="yes_button_text">&#xf14a;\nYes</string>
<string name="no_button_text">&#xf057;\nNo</string>
<string name="skip_button_text">&#xf146;\nSkip</string>
<string name="delete_button_text">&#xf05e;\nDelete</string>
<string name="default_checkmark_option_question">Have you completed this habit</string>
</resources> </resources>

@ -31,6 +31,13 @@
android:title="@string/pref_toggle_title" android:title="@string/pref_toggle_title"
app:iconSpaceReserved="false" /> app:iconSpaceReserved="false" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pref_advanced_checkmarks"
android:summary="@string/pref_advanced_checkmarks_description"
android:title="@string/pref_advanced_checkmarks_title"
app:iconSpaceReserved="false" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="pref_checkmark_reverse_order" android:key="pref_checkmark_reverse_order"

@ -90,6 +90,7 @@ public class CreateRepetitionCommand extends Command
if(newRep != null) habit.getRepetitions().remove(newRep); if(newRep != null) habit.getRepetitions().remove(newRep);
if (previousRep != null) habit.getRepetitions().add(previousRep); if (previousRep != null) habit.getRepetitions().add(previousRep);
habit.invalidateNewerThan(timestamp); habit.invalidateNewerThan(timestamp);
habitList.update(habit);
} }
public static class Record public static class Record

@ -42,6 +42,11 @@ public final class Checkmark
*/ */
public static final int SKIPPED = 3; public static final int SKIPPED = 3;
/** Indicates that there was an explicit unchecked repetition at the timestamp and a
* repetition was expected.
*/
public static final int UNCHECKED_EXPLICITLY = 5;
/** /**
* Indicates that there was a repetition at the timestamp. * Indicates that there was a repetition at the timestamp.
*/ */
@ -64,8 +69,8 @@ public final class Checkmark
/** /**
* The value of the checkmark. * The value of the checkmark.
* <p> * <p>
* For boolean habits, this equals either UNCHECKED, CHECKED_EXPLICITLY, CHECKED_IMPLICITLY * For boolean habits, this equals either UNCHECKED, SKIPPED, CHECKED_EXPLICITLY,
* or SKIPPED. * CHECKED_IMPLICITLY, UNCHECKED_EXPLICITLY.
* <p> * <p>
* For numerical habits, this number is stored in thousandths. That * For numerical habits, this number is stored in thousandths. That
* is, if the user enters value 1.50 on the app, it is stored as 1500. * is, if the user enters value 1.50 on the app, it is stored as 1500.

@ -79,7 +79,13 @@ public abstract class CheckmarkList
{ {
Timestamp date = rep.getTimestamp(); Timestamp date = rep.getTimestamp();
int offset = date.daysUntil(today); int offset = date.daysUntil(today);
checkmarks.set(offset, new Checkmark(date, rep.getValue())); int checkmarkValue = rep.getValue();
int oldValue = checkmarks.get(offset).getValue();
if (checkmarkValue == UNCHECKED_EXPLICITLY && oldValue == CHECKED_IMPLICITLY)
{
checkmarkValue = oldValue;
}
checkmarks.set(offset, new Checkmark(date, checkmarkValue));
} }
return checkmarks; return checkmarks;
@ -379,7 +385,15 @@ public abstract class CheckmarkList
private void computeYesNo(Repetition[] reps) private void computeYesNo(Repetition[] reps)
{ {
ArrayList<Interval> intervals; ArrayList<Interval> intervals;
intervals = buildIntervals(habit.getFrequency(), reps); List<Repetition> checkedRepetitions = new ArrayList<>();
for (Repetition rep : reps)
{
int value = rep.getValue();
if (value == CHECKED_EXPLICITLY || value == SKIPPED)
checkedRepetitions.add(rep);
}
intervals = buildIntervals(
habit.getFrequency(), checkedRepetitions.toArray(new Repetition[0]));
snapIntervalsTogether(intervals); snapIntervalsTogether(intervals);
add(buildCheckmarksFromIntervals(reps, intervals)); add(buildCheckmarksFromIntervals(reps, intervals));
} }

@ -331,7 +331,7 @@ public class Habit
else else
return todayCheckmark / 1000.0 <= data.targetValue; return todayCheckmark / 1000.0 <= data.targetValue;
} }
else return (todayCheckmark != UNCHECKED); else return (todayCheckmark != UNCHECKED && todayCheckmark != UNCHECKED_EXPLICITLY);
} }
public synchronized boolean isNumerical() public synchronized boolean isNumerical()

@ -31,8 +31,8 @@ import static org.isoron.uhabits.core.models.Checkmark.*;
import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle; import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
/** /**
* Represents a record that the user has performed or skipped a certain habit at a certain * Represents a record that the user has performed, didn't perform or skipped a certain habit at
* date. * a certain date.
*/ */
public final class Repetition public final class Repetition
{ {
@ -42,9 +42,12 @@ public final class Repetition
/** /**
* The value of the repetition. * The value of the repetition.
* *
* For boolean habits, this equals CHECKED_EXPLICITLY if performed or SKIPPED if skipped. * For boolean habits, this equals:
* For numerical habits, this number is stored in thousandths. That is, if the user enters * Checkmark.CHECKED_EXPLICITLY if performed
* value 1.50 on the app, it is here stored as 1500. * Checkmark.UNCHECKED_EXPLICITLY if not performed.
* Checkmark.SKIPPED if skipped.
* For numerical habits, this number is stored in thousandths. That
* is, if the user enters value 1.50 on the app, it is here stored as 1500.
*/ */
private final int value; private final int value;
@ -66,6 +69,7 @@ public final class Repetition
{ {
switch(value) { switch(value) {
case UNCHECKED: case UNCHECKED:
case UNCHECKED_EXPLICITLY:
case CHECKED_IMPLICITLY: case CHECKED_IMPLICITLY:
return CHECKED_EXPLICITLY; return CHECKED_EXPLICITLY;
case CHECKED_EXPLICITLY: case CHECKED_EXPLICITLY:

@ -211,7 +211,7 @@ public abstract class RepetitionList
{ {
Repetition rep = getByTimestamp(timestamp); Repetition rep = getByTimestamp(timestamp);
if (rep != null) remove(rep); if (rep != null) remove(rep);
add(new Repetition(timestamp, value)); if (value > 0) add(new Repetition(timestamp, value));
habit.invalidateNewerThan(timestamp); habit.invalidateNewerThan(timestamp);
} }

@ -275,16 +275,18 @@ public abstract class ScoreList implements Iterable<Score>
for (int i = 0; i < checkmarkValues.length; i++) for (int i = 0; i < checkmarkValues.length; i++)
{ {
double value = checkmarkValues[checkmarkValues.length - i - 1]; double value = checkmarkValues[checkmarkValues.length - i - 1];
if (!habit.isNumerical() || value != Checkmark.SKIPPED) if (habit.isNumerical())
{ {
if (habit.isNumerical()) value /= 1000;
{ value /= habit.getTargetValue();
value /= 1000;
value /= habit.getTargetValue();
}
value = Math.min(1, value); value = Math.min(1, value);
previousValue = Score.compute(freq, previousValue, value);
} }
else if (value > 0)
{
if (value == Checkmark.UNCHECKED_EXPLICITLY) value = 0;
else value = 1;
}
previousValue = Score.compute(freq, previousValue, value);
scores.add(new Score(from.plus(i), previousValue)); scores.add(new Score(from.plus(i), previousValue));
} }

@ -131,18 +131,39 @@ public abstract class StreakList
{ {
ArrayList<Timestamp> list = new ArrayList<>(); ArrayList<Timestamp> list = new ArrayList<>();
Timestamp current = beginning; Timestamp current = beginning;
list.add(current); Timestamp lastChecked = beginning;
boolean isInStreak = false;
for (int i = 1; i < checks.length; i++) for (int i = checks.length - 1; i >= 0; --i)
{ {
current = current.plus(1); boolean isCurrentChecked = (
int j = checks.length - i - 1; checks[i] == Checkmark.CHECKED_EXPLICITLY ||
checks[i] == Checkmark.CHECKED_IMPLICITLY ||
checks[i] == Checkmark.SKIPPED
);
boolean isCurrentUnchecked= (
checks[i] == Checkmark.UNCHECKED ||
checks[i] == Checkmark.UNCHECKED_EXPLICITLY
);
if (isCurrentChecked)
lastChecked = current;
if (isInStreak && isCurrentUnchecked)
{
list.add(lastChecked);
isInStreak = false;
}
if (!isInStreak && isCurrentChecked)
{
list.add(current);
isInStreak = true;
}
if ((checks[j + 1] == 0 && checks[j] > 0)) list.add(current); current = current.plus(1);
if ((checks[j + 1] > 0 && checks[j] == 0)) list.add(current.minus(1));
} }
if (list.size() % 2 == 1) list.add(current); if (isInStreak)
list.add(lastChecked);
return list; return list;
} }

@ -244,6 +244,16 @@ public class Preferences
storage.putBoolean("pref_short_toggle", enabled); storage.putBoolean("pref_short_toggle", enabled);
} }
public boolean isAdvancedCheckmarksEnabled()
{
return storage.getBoolean("pref_advanced_checkmarks", false);
}
public void setAdvancedCheckmarksEnabled(boolean enabled)
{
storage.putBoolean("pref_advanced_checkmarks", enabled);
}
public boolean isSyncEnabled() public boolean isSyncEnabled()
{ {
return storage.getBoolean("pref_feature_sync", false); return storage.getBoolean("pref_feature_sync", false);

@ -156,6 +156,18 @@ public class ListHabitsBehavior
habit.getId()); habit.getId());
} }
public void onToggleWithOptions(@NonNull Habit habit, Timestamp timestamp)
{
CheckmarkList checkmarks = habit.getCheckmarks();
int oldValue = checkmarks.getValues(timestamp, timestamp)[0];
screen.showCheckmarkOptions(habit, timestamp, oldValue, newValue ->
{
commandRunner.execute(
new CreateRepetitionCommand(habitList, habit, timestamp, newValue),
habit.getId());
});
}
public enum Message public enum Message
{ {
COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, IMPORT_FAILED, DATABASE_REPAIRED, COULD_NOT_EXPORT, IMPORT_SUCCESSFUL, IMPORT_FAILED, DATABASE_REPAIRED,
@ -181,6 +193,13 @@ public class ListHabitsBehavior
default void onNumberPickerDismissed() {} default void onNumberPickerDismissed() {}
} }
public interface CheckmarkOptionsCallback
{
void onCheckmarkOptionPicked(int newValue);
default void onCheckmarkOptionDismissed() {}
}
public interface Screen public interface Screen
{ {
void showHabitScreen(@NonNull Habit h); void showHabitScreen(@NonNull Habit h);
@ -193,6 +212,11 @@ public class ListHabitsBehavior
@NonNull String unit, @NonNull String unit,
@NonNull NumberPickerCallback callback); @NonNull NumberPickerCallback callback);
void showCheckmarkOptions(Habit habit,
Timestamp timestamp,
int value,
@NonNull CheckmarkOptionsCallback callback);
void showSendBugReportToDeveloperScreen(String log); void showSendBugReportToDeveloperScreen(String log);
void showSendFileScreen(@NonNull String filename); void showSendFileScreen(@NonNull String filename);

@ -82,4 +82,7 @@ public class WidgetBehavior
habit.getId()); habit.getId());
} }
public void setYesNoValue(@NonNull Habit habit, Timestamp timestamp, int newValue) {
performToggle(habit, timestamp, newValue);
}
} }

Loading…
Cancel
Save