Implement option for advanced checkmark states

pull/610/head
KristianTashkov 5 years ago
parent 00bc39027f
commit 9284fc76d0

@ -71,11 +71,6 @@ public class CheckmarkWidgetTest extends BaseViewTest
button.performClick();
sleep(1000);
assertThat(checkmarks.getTodayValue(), equalTo(SKIPPED_EXPLICITLY));
button.performClick();
sleep(1000);
assertThat(checkmarks.getTodayValue(), equalTo(UNCHECKED));
}

@ -0,0 +1,93 @@
/*
* 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.Context
import android.graphics.BlendMode
import android.graphics.BlendModeColorFilter
import android.graphics.PorterDuff
import android.os.Build
import android.view.LayoutInflater
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
import com.google.android.material.button.MaterialButton
import org.isoron.androidbase.activities.ActivityContext
import org.isoron.androidbase.utils.InterfaceUtils
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Checkmark
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import javax.inject.Inject
class CheckmarkOptionPickerFactory
@Inject constructor(
@ActivityContext private val context: Context
) {
fun create(habitName: String,
habitTimestamp: String,
value: Int,
callback: ListHabitsBehavior.CheckmarkOptionsCallback): AlertDialog {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.checkmark_option_picker_dialog, null)
val title = context.resources.getString(
R.string.choose_checkmark_option, habitName, habitTimestamp)
val dialog = AlertDialog.Builder(context)
.setView(view)
.setTitle(title)
.setOnDismissListener{
callback.onCheckmarkOptionDismissed()
}
.create()
val buttonValues = mapOf(
R.id.check_button to Checkmark.CHECKED_EXPLICITLY,
R.id.skip_button to Checkmark.SKIPPED_EXPLICITLY,
R.id.fail_button to Checkmark.FAILED_EXPLICITLY_NECESSARY,
R.id.clear_button to Checkmark.UNCHECKED
)
val valuesToButton = mapOf(
Checkmark.CHECKED_EXPLICITLY to R.id.check_button,
Checkmark.SKIPPED_EXPLICITLY to R.id.skip_button ,
Checkmark.FAILED_EXPLICITLY_NECESSARY to R.id.fail_button,
Checkmark.FAILED_EXPLICITLY_UNNECESSARY to R.id.fail_button
)
for ((buttonId, buttonValue) in buttonValues) {
val button = view.findViewById<MaterialButton>(buttonId)
button.setTypeface(InterfaceUtils.getFontAwesome(context))
button.setOnClickListener{
callback.onCheckmarkOptionPicked(buttonValue)
dialog.dismiss()
}
if (valuesToButton.containsKey(value) && valuesToButton[value] == buttonId) {
val color = context.resources.getColor(R.color.amber_800)
if (Build.VERSION.SDK_INT >= 29) {
button.background.colorFilter = BlendModeColorFilter(color, BlendMode.MULTIPLY)
} else {
button.background.setColorFilter(color, PorterDuff.Mode.MULTIPLY)
}
}
}
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
return dialog
}
}

@ -143,14 +143,6 @@ public class HistoryChart extends ScrollableChart
final Timestamp timestamp = positionToTimestamp(x, y);
if (timestamp == null) return false;
Timestamp today = DateUtils.getToday();
int offset = timestamp.daysUntil(today);
if (offset < checkmarks.length)
{
boolean isChecked = checkmarks[offset] == CHECKED_EXPLICITLY;
checkmarks[offset] = (isChecked ? UNCHECKED : CHECKED_EXPLICITLY);
}
controller.onToggleCheckmark(timestamp);
postInvalidate();
return true;
@ -357,26 +349,45 @@ public class HistoryChart extends ScrollableChart
headerOverflow = Math.max(0, headerOverflow - columnWidth);
}
private boolean isFailed(int checkmark)
{
return (checkmark == 0 ||
(!isNumerical && checkmark == FAILED_EXPLICITLY_NECESSARY));
}
private boolean isImplicitlySuccessful(int checkmark)
{
if (isNumerical) return checkmark < target;
return (checkmark == SKIPPED_EXPLICITLY ||
checkmark == FAILED_EXPLICITLY_UNNECESSARY ||
checkmark == CHECKED_IMPLICITLY);
}
private void drawSquare(Canvas canvas,
RectF location,
GregorianCalendar date,
int checkmarkOffset)
{
pSquareFg.setStrikeThruText(false);
boolean drawCross = false;
boolean drawDash = false;
if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]);
else
{
int checkmark = checkmarks[checkmarkOffset];
if(checkmark == 0) pSquareBg.setColor(colors[0]);
else if(checkmark < target)
if(isFailed(checkmark)) pSquareBg.setColor(colors[0]);
else if(isImplicitlySuccessful(checkmark))
{
pSquareBg.setColor(isNumerical ? textColor : colors[1]);
}
else if (!isNumerical && checkmark == 3) {
pSquareFg.setStrikeThruText(true);
pSquareBg.setColor(colors[1]);
}
else pSquareBg.setColor(colors[2]);
if (!isNumerical)
{
if (checkmark == FAILED_EXPLICITLY_UNNECESSARY ||
checkmark == FAILED_EXPLICITLY_NECESSARY) drawCross = true;
if (checkmark == SKIPPED_EXPLICITLY) drawDash = true;
}
}
pSquareFg.setColor(reverseTextColor);
@ -385,6 +396,28 @@ public class HistoryChart extends ScrollableChart
String text = Integer.toString(date.get(Calendar.DAY_OF_MONTH));
canvas.drawText(text, location.centerX(),
location.centerY() + squareTextOffset, pSquareFg);
if (drawCross)
{
for (int thickness = -1; thickness < 2; thickness ++)
{
canvas.drawLine(
location.left + thickness, location.bottom,
location.right - thickness, location.top, pSquareFg);
canvas.drawLine(
location.right - thickness, location.bottom,
location.left + thickness, location.top, pSquareFg);
}
}
if (drawDash)
{
for (int thickness = -1; thickness < 2; thickness ++)
{
canvas.drawLine(
location.left, location.centerY() + thickness + squareTextOffset / 2,
location.right,location.centerY() + thickness + squareTextOffset / 2,
pSquareFg);
}
}
}
private float getWeekdayLabelWidth()

@ -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<ListHabitsBehavior>,
private val menu: Lazy<ListHabitsMenu>,
private val selectionMenu: Lazy<ListHabitsSelectionMenu>
@ -204,6 +205,13 @@ class ListHabitsScreen
numberPickerFactory.create(value, unit, callback).show()
}
override fun showCheckmarkOptions(habitName: String,
timestamp: Timestamp,
value: Int,
callback: ListHabitsBehavior.CheckmarkOptionsCallback) {
checkmarkOptionPickerFactory.create(habitName, timestamp.toString(), value, callback).show()
}
@StringRes
private fun getExecuteString(command: Command): Int? {
when (command) {

@ -52,6 +52,7 @@ class CheckmarkButtonView(
}
var onToggle: () -> Unit = {}
var onToggleWithOptions: () -> Unit = {}
private var drawer = Drawer()
init {
@ -63,21 +64,29 @@ class CheckmarkButtonView(
fun performToggle() {
onToggle()
value = when (value) {
CHECKED_EXPLICITLY -> SKIPPED_EXPLICITLY
SKIPPED_EXPLICITLY -> UNCHECKED
else -> CHECKED_EXPLICITLY
UNCHECKED -> CHECKED_EXPLICITLY
else -> UNCHECKED
}
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
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
}
@ -110,12 +119,16 @@ class CheckmarkButtonView(
fun draw(canvas: Canvas) {
paint.color = when (value) {
CHECKED_EXPLICITLY -> color
FAILED_EXPLICITLY_UNNECESSARY -> mediumContrastTextColor
SKIPPED_EXPLICITLY -> mediumContrastTextColor
FAILED_EXPLICITLY_NECESSARY -> mediumContrastTextColor
else -> lowContrastColor
}
val id = when (value) {
SKIPPED_EXPLICITLY -> R.string.fa_skipped
UNCHECKED -> R.string.fa_times
FAILED_EXPLICITLY_NECESSARY -> R.string.fa_times
FAILED_EXPLICITLY_UNNECESSARY -> R.string.fa_check
else -> R.string.fa_check
}
val label = resources.getString(id)

@ -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 = { onToggle(timestamp) }
button.onToggleWithOptions = { onToggleWithOptions (timestamp) }
}
}
}

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

@ -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.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<ShowHabitBehavior> 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<ShowHabitBehavior> behavior)
@NonNull Lazy<ShowHabitBehavior> 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)
{
behavior.get().onToggleCheckmark(timestamp);
if (prefs.isAdvancedCheckmarksEnabled())
{
CheckmarkList checkmarks = habit.getCheckmarks();
int oldValue = checkmarks.getValues(timestamp, timestamp)[0];
checkmarkOptionPickerFactory.create(habit.getName(), timestamp.toString(), oldValue,
newValue ->
{
behavior.get().onCreateRepetition(timestamp, newValue);
}).show();
}
else behavior.get().onToggleCheckmark(timestamp);
}
@Override

@ -0,0 +1,69 @@
<?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:orientation="horizontal"
android:gravity="center"
android:paddingHorizontal="5dp"
android:paddingVertical="25dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:divider="?android:dividerVertical"
android:showDividers="middle">
<Button
android:id="@+id/check_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:drawableStart="@drawable/ic_action_check"
android:text="@string/done_button_text"
app:cornerRadius="0dp" />
<Button
android:id="@+id/fail_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/fail_button_text"
app:cornerRadius="0dp" />
<Button
android:id="@+id/skip_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/skip_button_text"
app:cornerRadius="0dp" />
<Button
android:id="@+id/clear_button"
style="@style/Widget.AppCompat.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/delete_button_text"
app:cornerRadius="0dp" />
</LinearLayout>

@ -67,6 +67,8 @@
<string name="interval_custom">Custom...</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_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_rate_this_app">Rate this app on Google Play</string>
<string name="pref_send_feedback">Send feedback to developer</string>
@ -162,6 +164,7 @@
<string name="export">Export</string>
<string name="long_press_to_edit">Press-and-hold to change the value</string>
<string name="change_value">Change value</string>
<string name="choose_checkmark_option">%1$s on %2$s</string>
<string name="calendar">Calendar</string>
<string name="unit">Unit</string>
<string name="example_question_boolean">e.g. Did you exercise today?</string>
@ -194,4 +197,8 @@
<string name="every_month">Every month</string>
<string name="validation_cannot_be_blank">Cannot be blank</string>
<string name="today">Today</string>
<string name="done_button_text">&#xf14a;\nDone</string>
<string name="fail_button_text">&#xf057;\nFail</string>
<string name="skip_button_text">&#xf146;\nSkip</string>
<string name="delete_button_text">&#xf05e;\nDelete</string>
</resources>

@ -31,6 +31,13 @@
android:title="@string/pref_toggle_title"
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
android:defaultValue="false"
android:key="pref_checkmark_reverse_order"

@ -58,8 +58,11 @@ public class CreateRepetitionCommand extends Command
previousRep = reps.getByTimestamp(timestamp);
if (previousRep != null) reps.remove(previousRep);
newRep = new Repetition(timestamp, value);
reps.add(newRep);
if (value != 0)
{
newRep = new Repetition(timestamp, value);
reps.add(newRep);
}
habit.invalidateNewerThan(timestamp);
}

@ -70,7 +70,6 @@ public class ToggleRepetitionCommand extends Command
public void undo()
{
execute();
execute();
}
public static class Record

@ -37,6 +37,18 @@ import static org.isoron.uhabits.core.utils.StringUtils.defaultToStringStyle;
@ThreadSafe
public final class Checkmark
{
/**
* Indicates that there was a failed repetition at the timestamp and a
* repetition was expected.
*/
public static final int FAILED_EXPLICITLY_NECESSARY = 5;
/**
* Indicates that there was a failed repetition at the timestamp and a
* repetition wasn't expected.
*/
public static final int FAILED_EXPLICITLY_UNNECESSARY = 4;
/**
* Indicates that there was an explicit skip at the timestamp.
*/
@ -65,7 +77,7 @@ public final class Checkmark
* The value of the checkmark.
* <p>
* For boolean habits, this equals either UNCHECKED, SKIPPED_EXPLICITLY, CHECKED_EXPLICITLY,
* or CHECKED_IMPLICITLY.
* CHECKED_IMPLICITLY, FAILED_EXPLICITLY_UNNECESSARY, FAILED_EXPLICITLY_NECESSARY.
* <p>
* 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.

@ -79,7 +79,14 @@ public abstract class CheckmarkList
{
Timestamp date = rep.getTimestamp();
int offset = date.daysUntil(today);
checkmarks.set(offset, new Checkmark(date, rep.getValue()));
int checkmarkValue = rep.getValue();
if (checkmarkValue == FAILED_EXPLICITLY_NECESSARY)
{
int oldValue = checkmarks.get(offset).getValue();
checkmarkValue = (oldValue == CHECKED_IMPLICITLY) ?
FAILED_EXPLICITLY_UNNECESSARY : FAILED_EXPLICITLY_NECESSARY;
}
checkmarks.set(offset, new Checkmark(date, checkmarkValue));
}
return checkmarks;
@ -380,10 +387,10 @@ public abstract class CheckmarkList
{
ArrayList<Interval> intervals;
List<Repetition> successful_repetitions = new ArrayList<>();
for (Repetition rep : reps) {
if (rep.getValue() != SKIPPED_EXPLICITLY) {
for (Repetition rep : reps)
{
if (rep.getValue() == CHECKED_EXPLICITLY)
successful_repetitions.add(rep);
}
}
intervals = buildIntervals(
habit.getFrequency(), successful_repetitions.toArray(new Repetition[0]));

@ -30,8 +30,8 @@ import java.util.GregorianCalendar;
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
* date.
* Represents a record that the user has performed, failed to perform or skipped a certain habit at
* a certain date.
*/
public final class Repetition
{
@ -41,8 +41,10 @@ public final class Repetition
/**
* The value of the repetition.
*
* For boolean habits, this equals Checkmark.CHECKED_EXPLICITLY if performed
* or Checkmark.SKIPPED_EXPLICITLY if skipped.
* For boolean habits, this equals:
* Checkmark.CHECKED_EXPLICITLY if performed
* Checkmark.SKIPPED_EXPLICITLY if skipped.
* Checkmark.FAILED_EXPLICITLY_NECESSARY if failed.
* 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.
*/

@ -151,10 +151,9 @@ public abstract class RepetitionList
for (Repetition r : reps)
{
if ((habit.getData().type == Habit.YES_NO_HABIT)
&& (r.getValue() == Checkmark.SKIPPED_EXPLICITLY)) {
if (habit.getData().type == Habit.YES_NO_HABIT &&
r.getValue() != Checkmark.CHECKED_EXPLICITLY)
continue;
}
Calendar date = r.getTimestamp().toCalendar();
int weekday = r.getTimestamp().getWeekday();
@ -207,13 +206,7 @@ public abstract class RepetitionList
throw new IllegalStateException("habit must NOT be numerical");
Repetition rep = getByTimestamp(timestamp);
if (rep != null) {
remove(rep);
if (rep.getValue() == Checkmark.CHECKED_EXPLICITLY) {
rep = new Repetition(timestamp, Checkmark.SKIPPED_EXPLICITLY);
add(rep);
}
}
if (rep != null) remove(rep);
else
{
rep = new Repetition(timestamp, Checkmark.CHECKED_EXPLICITLY);

@ -275,6 +275,7 @@ public abstract class ScoreList implements Iterable<Score>
for (int i = 0; i < checkmarkValues.length; i++)
{
double value = checkmarkValues[checkmarkValues.length - i - 1];
boolean skip_calculation = false;
if (habit.isNumerical())
{
@ -282,14 +283,20 @@ public abstract class ScoreList implements Iterable<Score>
value /= habit.getTargetValue();
value = Math.min(1, value);
}
if (!habit.isNumerical() && value > 0) {
value = value != Checkmark.SKIPPED_EXPLICITLY ? 1 : -1;
else
{
if (value == Checkmark.UNCHECKED ||
value == Checkmark.FAILED_EXPLICITLY_NECESSARY)
value = 0;
else if (value == Checkmark.SKIPPED_EXPLICITLY)
skip_calculation = true;
else
value = 1;
}
if (value > -1) {
if (!skip_calculation)
previousValue = Score.compute(freq, previousValue, value);
}
scores.add(new Score(from.plus(i), previousValue));
}

@ -138,17 +138,24 @@ public abstract class StreakList
{
boolean isCurrentChecked = (
checks[i] == Checkmark.CHECKED_EXPLICITLY ||
checks[i] == Checkmark.CHECKED_IMPLICITLY
checks[i] == Checkmark.CHECKED_IMPLICITLY ||
checks[i] == Checkmark.FAILED_EXPLICITLY_UNNECESSARY
);
if (habit.getData().type == Habit.NUMBER_HABIT || isCurrentChecked) {
boolean isCurrentFailed = (
checks[i] == Checkmark.UNCHECKED ||
checks[i] == Checkmark.FAILED_EXPLICITLY_NECESSARY
);
if (habit.getData().type == Habit.NUMBER_HABIT || isCurrentChecked)
lastSuccesful = current;
}
if (isInStreak && checks[i] == 0) {
if (isInStreak && isCurrentFailed)
{
list.add(lastSuccesful);
isInStreak = false;
}
if (!isInStreak && isCurrentChecked) {
if (!isInStreak && isCurrentChecked)
{
list.add(current);
isInStreak = true;
}
@ -156,7 +163,8 @@ public abstract class StreakList
current = current.plus(1);
}
if (isInStreak) list.add(lastSuccesful);
if (isInStreak)
list.add(lastSuccesful);
return list;
}

@ -101,10 +101,10 @@ public class MemoryRepetitionList extends RepetitionList
for (Repetition rep : list)
{
if (habit.getData().type == Habit.YES_NO_HABIT
&& rep.getValue() == Checkmark.SKIPPED_EXPLICITLY) {
if (habit.getData().type == Habit.YES_NO_HABIT &&
rep.getValue() != Checkmark.CHECKED_EXPLICITLY)
continue;
}
if (rep.getTimestamp().isOlderThan(oldestTimestamp))
{
oldestRep = rep;
@ -145,10 +145,10 @@ public class MemoryRepetitionList extends RepetitionList
public long getTotalSuccessfulCount()
{
int count = 0;
for (Repetition rep : list) {
if (rep.getValue() != Checkmark.SKIPPED_EXPLICITLY) {
++count;
}
for (Repetition rep : list)
{
if (rep.getValue() == Checkmark.CHECKED_EXPLICITLY)
count++;
}
return count;
}

@ -244,6 +244,16 @@ public class Preferences
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()
{
return storage.getBoolean("pref_feature_sync", false);

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

@ -62,6 +62,12 @@ public class ShowHabitBehavior
new ToggleRepetitionCommand(habitList, habit, timestamp), null);
}
public void onCreateRepetition(Timestamp timestamp, int value)
{
commandRunner.execute(
new CreateRepetitionCommand(habit, timestamp, value), null);
}
public interface Screen
{
void showEditHistoryScreen();

@ -53,19 +53,10 @@ public class ToggleRepetitionCommandTest extends BaseUnitTest
{
assertTrue(habit.getRepetitions().containsTimestamp(today));
command.execute();
assertEquals(
habit.getRepetitions().getByTimestamp(today).getValue(),
Checkmark.SKIPPED_EXPLICITLY
);
command.execute();
assertFalse(habit.getRepetitions().containsTimestamp(today));
command.undo();
assertEquals(
habit.getRepetitions().getByTimestamp(today).getValue(),
Checkmark.SKIPPED_EXPLICITLY
);
command.execute();
assertFalse(habit.getRepetitions().containsTimestamp(today));

@ -31,7 +31,6 @@ import static java.util.Calendar.*;
import static junit.framework.TestCase.assertFalse;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;
@ -158,20 +157,16 @@ public class RepetitionListTest extends BaseUnitTest
@Test
public void test_toggle()
{
assertEquals(reps.getByTimestamp(today).getValue(), Checkmark.CHECKED_EXPLICITLY);
reps.toggle(today);
assertEquals(reps.getByTimestamp(today).getValue(), Checkmark.SKIPPED_EXPLICITLY);
assertTrue(reps.containsTimestamp(today));
reps.toggle(today);
assertFalse(reps.containsTimestamp(today));
verify(listener, times(3)).onModelChange();
verify(listener).onModelChange();
reset(listener);
assertFalse(reps.containsTimestamp(today.minus(1)));
reps.toggle(today.minus(1));
assertEquals(reps.getByTimestamp(today.minus(1)).getValue(), Checkmark.CHECKED_EXPLICITLY);
reps.toggle(today.minus(1));
assertEquals(reps.getByTimestamp(today.minus(1)).getValue(), Checkmark.SKIPPED_EXPLICITLY);
verify(listener, times(3)).onModelChange();
assertTrue(reps.containsTimestamp(today.minus(1)));
verify(listener).onModelChange();
reset(listener);
habit.setType(Habit.NUMBER_HABIT);

@ -172,8 +172,6 @@ public class ListHabitsBehaviorTest extends BaseUnitTest
@Test
public void testOnToggle()
{
assertTrue(habit1.isCompletedToday());
behavior.onToggle(habit1, DateUtils.getToday());
assertTrue(habit1.isCompletedToday());
behavior.onToggle(habit1, DateUtils.getToday());
assertFalse(habit1.isCompletedToday());

Loading…
Cancel
Save