From 7bf9f88ee37fe5516a6a6e2bb23bd7761426cc92 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 23 Mar 2017 18:26:58 -0400 Subject: [PATCH 01/22] Implement NumberButtonView and related classes --- .../list/views/CheckmarkButtonViewTest.java | 93 ------- .../list/views/CheckmarkPanelViewTest.java | 2 +- .../habits/list/ListHabitsComponent.java | 10 +- .../controllers/NumberButtonController.java | 102 +++++++ .../list/views/CheckmarkButtonView.java | 24 +- .../habits/list/views/CheckmarkPanelView.java | 121 +++++---- .../habits/list/views/HabitCardView.java | 4 +- .../habits/list/views/NumberButtonView.java | 131 +++++++++ .../habits/list/views/NumberPanelView.java | 255 ++++++++++++++++++ .../uhabits/utils/AttributeSetUtils.java | 10 + .../res/layout/list_habits_button_preview.xml | 67 +++++ .../res/layout/list_habits_panel_preview.xml | 80 ++++++ 12 files changed, 748 insertions(+), 151 deletions(-) create mode 100644 app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/NumberButtonController.java create mode 100644 app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java create mode 100644 app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java create mode 100644 app/src/main/res/layout/list_habits_button_preview.xml create mode 100644 app/src/main/res/layout/list_habits_panel_preview.xml diff --git a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java index b8bbc0bac..e30669414 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonViewTest.java @@ -91,97 +91,4 @@ public class CheckmarkButtonViewTest extends BaseViewTest { assertRenders(view, PATH + "render_unchecked.png"); } - -// @Test -// public void testLongClick() throws Exception -// { -// setOnToggleListener(); -// view.performLongClick(); -// waitForLatch(); -// assertRendersCheckedExplicitly(); -// } -// -// @Test -// public void testClick_withShortToggle_fromUnchecked() throws Exception -// { -// Preferences.getInstance().setShortToggleEnabled(true); -// view.setValue(Checkmark.UNCHECKED); -// setOnToggleListenerAndPerformClick(); -// assertRendersCheckedExplicitly(); -// } -// -// @Test -// public void testClick_withShortToggle_fromChecked() throws Exception -// { -// Preferences.getInstance().setShortToggleEnabled(true); -// view.setValue(Checkmark.CHECKED_EXPLICITLY); -// setOnToggleListenerAndPerformClick(); -// assertRendersUnchecked(); -// } -// -// @Test -// public void testClick_withShortToggle_withoutListener() throws Exception -// { -// Preferences.getInstance().setShortToggleEnabled(true); -// view.setValue(Checkmark.CHECKED_EXPLICITLY); -// view.setController(null); -// view.performClick(); -// assertRendersUnchecked(); -// } -// -// protected void setOnToggleListenerAndPerformClick() throws InterruptedException -// { -// setOnToggleListener(); -// view.performClick(); -// waitForLatch(); -// } -// -// @Test -// public void testClick_withoutShortToggle() throws Exception -// { -// Preferences.getInstance().setShortToggleEnabled(false); -// setOnInvalidToggleListener(); -// view.performClick(); -// waitForLatch(); -// assertRendersUnchecked(); -// } - -// protected void setOnInvalidToggleListener() -// { -// view.setController(new CheckmarkButtonView.Controller() -// { -// @Override -// public void onToggleCheckmark(CheckmarkButtonView view, long timestamp) -// { -// fail(); -// } -// -// @Override -// public void onInvalidToggle(CheckmarkButtonView v) -// { -// assertThat(v, equalTo(view)); -// latch.countDown(); -// } -// }); -// } - -// protected void setOnToggleListener() -// { -// view.setController(new CheckmarkButtonView.Controller() -// { -// @Override -// public void onToggleCheckmark(CheckmarkButtonView v, long t) -// { -// assertThat(v, equalTo(view)); -// assertThat(t, equalTo(DateUtils.getStartOfToday())); -// latch.countDown(); -// } -// -// @Override -// public void onInvalidToggle(CheckmarkButtonView view) -// { -// fail(); -// } -// }); -// } } \ No newline at end of file diff --git a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java index 30e225a7a..8be616ef9 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelViewTest.java @@ -59,7 +59,7 @@ public class CheckmarkPanelViewTest extends BaseViewTest view = new CheckmarkPanelView(targetContext); view.setHabit(habit); - view.setCheckmarkValues(checkmarks); + view.setValues(checkmarks); view.setButtonCount(4); view.setColor(ColorUtils.getAndroidTestColor(7)); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java index 554c22a3b..4f8161d40 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsComponent.java @@ -32,19 +32,21 @@ import dagger.*; dependencies = { AppComponent.class }) public interface ListHabitsComponent { - CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory(); - HabitCardListAdapter getAdapter(); + CheckmarkButtonControllerFactory getCheckmarkButtonControllerFactory(); + ListHabitsController getController(); ListHabitsMenu getMenu(); + MidnightTimer getMidnightTimer(); + + NumberButtonControllerFactory getNumberButtonControllerFactory(); + ListHabitsRootView getRootView(); ListHabitsScreen getScreen(); ListHabitsSelectionMenu getSelectionMenu(); - - MidnightTimer getMidnightTimer(); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/NumberButtonController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/NumberButtonController.java new file mode 100644 index 000000000..0cd31fe20 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/NumberButtonController.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2016 Á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.habits.list.controllers; + +import android.support.annotation.*; + +import com.google.auto.factory.*; + +import org.isoron.uhabits.activities.habits.list.views.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.preferences.*; + +@AutoFactory +public class NumberButtonController +{ + @Nullable + private NumberButtonView view; + + @Nullable + private Listener listener; + + @NonNull + private final Preferences prefs; + + @NonNull + private Habit habit; + + private long timestamp; + + public NumberButtonController(@Provided @NonNull Preferences prefs, + @NonNull Habit habit, + long timestamp) + { + this.habit = habit; + this.timestamp = timestamp; + this.prefs = prefs; + } + + public void onClick() + { + if (prefs.isShortToggleEnabled()) performEdit(); + else performInvalidToggle(); + } + + public boolean onLongClick() + { + performEdit(); + return true; + } + + public void performInvalidToggle() + { + if (listener != null) listener.onInvalidEdit(); + } + + public void performEdit() + { + if (listener != null) listener.onEdit(habit, timestamp); + } + + public void setListener(@Nullable Listener listener) + { + this.listener = listener; + } + + public void setView(@Nullable NumberButtonView view) + { + this.view = view; + } + + public interface Listener + { + /** + * Called when the user's attempt to edit the value is rejected. + */ + void onInvalidEdit(); + + /** + * Called when a the user's attempt to edit the value has been accepted. + * @param habit the habit being edited + * @param timestamp the timestamp being edited + */ + void onEdit(@NonNull Habit habit, long timestamp); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java index 97d0c9fca..acc0e8b7e 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkButtonView.java @@ -20,6 +20,8 @@ package org.isoron.uhabits.activities.habits.list.views; import android.content.*; +import android.support.annotation.*; +import android.util.*; import android.view.*; import android.widget.*; @@ -28,6 +30,9 @@ import org.isoron.uhabits.activities.habits.list.controllers.*; import org.isoron.uhabits.models.*; import org.isoron.uhabits.utils.*; +import static org.isoron.uhabits.utils.AttributeSetUtils.*; +import static org.isoron.uhabits.utils.ColorUtils.*; + public class CheckmarkButtonView extends TextView { private int color; @@ -36,16 +41,31 @@ public class CheckmarkButtonView extends TextView private StyledResources res; - public CheckmarkButtonView(Context context) + public CheckmarkButtonView(@Nullable Context context) { super(context); init(); } + public CheckmarkButtonView(@Nullable Context context, + @Nullable AttributeSet attrs) + { + super(context, attrs); + init(); + + if (context != null && attrs != null) + { + int color = getIntAttribute(context, attrs, "color", 0); + int value = getIntAttribute(context, attrs, "value", 0); + setColor(getAndroidTestColor(color)); + setValue(value); + } + } + public void setColor(int color) { this.color = color; - postInvalidate(); + updateText(); } public void setController(final CheckmarkButtonController controller) diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java index b569f9208..1f206ad4b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/CheckmarkPanelView.java @@ -31,18 +31,23 @@ import org.isoron.uhabits.models.*; import org.isoron.uhabits.preferences.*; import org.isoron.uhabits.utils.*; +import java.util.*; + import static android.view.View.MeasureSpec.*; +import static org.isoron.uhabits.utils.AttributeSetUtils.*; +import static org.isoron.uhabits.utils.ColorUtils.*; -public class CheckmarkPanelView extends LinearLayout implements Preferences.Listener +public class CheckmarkPanelView extends LinearLayout + implements Preferences.Listener { - private static final int CHECKMARK_LEFT_TO_RIGHT = 0; + private static final int LEFT_TO_RIGHT = 0; - private static final int CHECKMARK_RIGHT_TO_LEFT = 1; + private static final int RIGHT_TO_LEFT = 1; @Nullable private Preferences prefs; - private int checkmarkValues[]; + private int values[]; private int nButtons; @@ -61,61 +66,89 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List init(); } - public CheckmarkPanelView(Context context, AttributeSet attrs) + public CheckmarkPanelView(Context ctx, AttributeSet attrs) { - super(context, attrs); + super(ctx, attrs); init(); + + if (ctx != null && attrs != null) + { + int paletteColor = getIntAttribute(ctx, attrs, "color", 0); + setColor(getAndroidTestColor(paletteColor)); + setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5)); + } + + if (isInEditMode()) initEditMode(); } public CheckmarkButtonView indexToButton(int i) { int position = i; - if (getCheckmarkOrder() == CHECKMARK_RIGHT_TO_LEFT) - position = nButtons - i - 1; + if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1; return (CheckmarkButtonView) getChildAt(position); } + @Override + public void onCheckmarkOrderChanged() + { + setupButtons(); + } + public void setButtonCount(int newButtonCount) { - if(nButtons != newButtonCount) + if (nButtons != newButtonCount) { nButtons = newButtonCount; - addCheckmarkButtons(); + addButtons(); } - setupCheckmarkButtons(); - } - - public void setCheckmarkValues(int[] checkmarkValues) - { - this.checkmarkValues = checkmarkValues; - setupCheckmarkButtons(); + setupButtons(); } public void setColor(int color) { this.color = color; - setupCheckmarkButtons(); + setupButtons(); } public void setController(Controller controller) { this.controller = controller; - setupCheckmarkButtons(); + setupButtons(); } public void setDataOffset(int dataOffset) { this.dataOffset = dataOffset; - setupCheckmarkButtons(); + setupButtons(); } public void setHabit(@NonNull Habit habit) { this.habit = habit; - setupCheckmarkButtons(); + setupButtons(); + } + + public void setValues(int[] values) + { + this.values = values; + setupButtons(); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (prefs != null) prefs.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if (prefs != null) prefs.removeListener(this); + super.onDetachedFromWindow(); } @Override @@ -133,7 +166,7 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List super.onMeasure(widthSpec, heightSpec); } - private void addCheckmarkButtons() + private void addButtons() { removeAllViews(); @@ -143,21 +176,31 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List private int getCheckmarkOrder() { - if (prefs == null) return CHECKMARK_LEFT_TO_RIGHT; - return prefs.shouldReverseCheckmarks() ? CHECKMARK_RIGHT_TO_LEFT : - CHECKMARK_LEFT_TO_RIGHT; + if (prefs == null) return LEFT_TO_RIGHT; + return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; } private void init() { Context appContext = getContext().getApplicationContext(); - if(appContext instanceof HabitsApplication) + if (appContext instanceof HabitsApplication) { HabitsApplication app = (HabitsApplication) appContext; prefs = app.getComponent().getPreferences(); } setWillNotDraw(false); + values = new int[0]; + } + + private void initEditMode() + { + int values[] = new int[nButtons]; + + for (int i = 0; i < nButtons; i++) + values[i] = new Random().nextInt(3); + + setValues(values); } private void setupButtonControllers(long timestamp, @@ -178,7 +221,7 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List buttonView.setController(buttonController); } - private void setupCheckmarkButtons() + private void setupButtons() { long timestamp = DateUtils.getStartOfToday(); long day = DateUtils.millisecondsInOneDay; @@ -187,34 +230,14 @@ public class CheckmarkPanelView extends LinearLayout implements Preferences.List for (int i = 0; i < nButtons; i++) { CheckmarkButtonView buttonView = indexToButton(i); - if(i + dataOffset >= checkmarkValues.length) break; - buttonView.setValue(checkmarkValues[i + dataOffset]); + if (i + dataOffset >= values.length) break; + buttonView.setValue(values[i + dataOffset]); buttonView.setColor(color); setupButtonControllers(timestamp, buttonView); timestamp -= day; } } - @Override - protected void onAttachedToWindow() - { - super.onAttachedToWindow(); - if(prefs != null) prefs.addListener(this); - } - - @Override - protected void onDetachedFromWindow() - { - if(prefs != null) prefs.removeListener(this); - super.onDetachedFromWindow(); - } - - @Override - public void onCheckmarkOrderChanged() - { - setupCheckmarkButtons(); - } - public interface Controller extends CheckmarkButtonController.Listener { diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index d57e0a71c..e703ba18c 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -99,7 +99,7 @@ public class HabitCardView extends FrameLayout public void setCheckmarkValues(int checkmarks[]) { - checkmarkPanel.setCheckmarkValues(checkmarks); + checkmarkPanel.setValues(checkmarks); postInvalidate(); } @@ -213,7 +213,7 @@ public class HabitCardView extends FrameLayout scoreRing.setColor(color); scoreRing.setPercentage(rand.nextFloat()); checkmarkPanel.setColor(color); - checkmarkPanel.setCheckmarkValues(values); + checkmarkPanel.setValues(values); } private void refresh() diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java new file mode 100644 index 000000000..1d4d4f103 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2016 Á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.habits.list.views; + +import android.content.*; +import android.graphics.*; +import android.support.annotation.*; +import android.util.*; +import android.view.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.activities.habits.list.controllers.*; +import org.isoron.uhabits.utils.*; + +import static org.isoron.uhabits.utils.AttributeSetUtils.*; +import static org.isoron.uhabits.utils.ColorUtils.*; + +public class NumberButtonView extends TextView +{ + private static Typeface TYPEFACE = + Typeface.create("sans-serif-condensed", Typeface.BOLD); + + private int color; + + private int value; + + private int threshold; + + private StyledResources res; + + public NumberButtonView(@Nullable Context context) + { + super(context); + init(); + } + + public NumberButtonView(@Nullable Context context, + @Nullable AttributeSet attrs) + { + super(context, attrs); + init(); + + if (context != null && attrs != null) + { + int color = getIntAttribute(context, attrs, "color", 0); + int value = getIntAttribute(context, attrs, "value", 0); + int threshold = getIntAttribute(context, attrs, "threshold", 1); + setColor(getAndroidTestColor(color)); + setThreshold(threshold); + setValue(value); + } + } + + private static String formatValue(int v) + { + double fv = (double) v; + if(v >= 1e9) return String.format("%.2fG", fv / 1e9); + if(v >= 1e8) return String.format("%.0fM", fv / 1e6); + if(v >= 1e7) return String.format("%.1fM", fv / 1e6); + if(v >= 1e6) return String.format("%.2fM", fv / 1e6); + if(v >= 1e5) return String.format("%.0fk", fv / 1e3); + if(v >= 1e4) return String.format("%.1fk", fv / 1e3); + if(v >= 1e3) return String.format("%.2fk", fv / 1e3); + return String.format("%d", v); + } + + public void setColor(int color) + { + this.color = color; + postInvalidate(); + } + + public void setController(final NumberButtonController controller) + { + setOnClickListener(v -> controller.onClick()); + setOnLongClickListener(v -> controller.onLongClick()); + } + + public void setThreshold(int threshold) + { + this.threshold = threshold; + updateText(); + } + + public void setValue(int value) + { + this.value = value; + updateText(); + } + + private void init() + { + res = new StyledResources(getContext()); + + setWillNotDraw(false); + + setMinHeight( + getResources().getDimensionPixelSize(R.dimen.checkmarkHeight)); + setMinWidth( + getResources().getDimensionPixelSize(R.dimen.checkmarkWidth)); + + setFocusable(false); + setGravity(Gravity.CENTER); + setTypeface(TYPEFACE); + } + + private void updateText() + { + int lowColor = res.getColor(R.attr.lowContrastTextColor); + setTextColor(value >= threshold ? color : lowColor); + setText(formatValue(value)); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java new file mode 100644 index 000000000..16ac11e9a --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2016 Á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.habits.list.views; + +import android.content.*; +import android.support.annotation.*; +import android.util.*; +import android.widget.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.activities.habits.list.*; +import org.isoron.uhabits.activities.habits.list.controllers.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.preferences.*; +import org.isoron.uhabits.utils.*; + +import java.util.*; + +import static android.view.View.MeasureSpec.*; +import static org.isoron.uhabits.utils.AttributeSetUtils.*; +import static org.isoron.uhabits.utils.ColorUtils.*; + +public class NumberPanelView extends LinearLayout + implements Preferences.Listener +{ + private static final int LEFT_TO_RIGHT = 0; + + private static final int RIGHT_TO_LEFT = 1; + + @Nullable + private Preferences prefs; + + private int values[]; + + private int threshold; + + private int nButtons; + + private int color; + + private Controller controller; + + @NonNull + private Habit habit; + + private int dataOffset; + + public NumberPanelView(Context context) + { + super(context); + init(); + } + + public NumberPanelView(Context ctx, AttributeSet attrs) + { + super(ctx, attrs); + init(); + + if (ctx != null && attrs != null) + { + int paletteColor = getIntAttribute(ctx, attrs, "color", 0); + setColor(getAndroidTestColor(paletteColor)); + setButtonCount(getIntAttribute(ctx, attrs, "button_count", 5)); + setThreshold(getIntAttribute(ctx, attrs, "threshold", 1)); + } + + if(isInEditMode()) initEditMode(); + } + + private void initEditMode() + { + int values[] = new int[nButtons]; + + for(int i = 0; i < nButtons; i++) + values[i] = new Random().nextInt(threshold * 3); + + setValues(values); + } + + public NumberButtonView indexToButton(int i) + { + int position = i; + + if (getCheckmarkOrder() == RIGHT_TO_LEFT) position = nButtons - i - 1; + + return (NumberButtonView) getChildAt(position); + } + + @Override + public void onCheckmarkOrderChanged() + { + setupButtons(); + } + + public void setButtonCount(int newButtonCount) + { + if (nButtons != newButtonCount) + { + nButtons = newButtonCount; + addButtons(); + } + + setupButtons(); + } + + public void setColor(int color) + { + this.color = color; + setupButtons(); + } + + public void setController(Controller controller) + { + this.controller = controller; + setupButtons(); + } + + public void setDataOffset(int dataOffset) + { + this.dataOffset = dataOffset; + setupButtons(); + } + + public void setHabit(@NonNull Habit habit) + { + this.habit = habit; + setupButtons(); + } + + public void setThreshold(int threshold) + { + this.threshold = threshold; + setupButtons(); + } + + public void setValues(int[] values) + { + this.values = values; + setupButtons(); + } + + @Override + protected void onAttachedToWindow() + { + super.onAttachedToWindow(); + if (prefs != null) prefs.addListener(this); + } + + @Override + protected void onDetachedFromWindow() + { + if (prefs != null) prefs.removeListener(this); + super.onDetachedFromWindow(); + } + + @Override + protected void onMeasure(int widthSpec, int heightSpec) + { + float buttonWidth = getResources().getDimension(R.dimen.checkmarkWidth); + float buttonHeight = + getResources().getDimension(R.dimen.checkmarkHeight); + + float width = buttonWidth * nButtons; + + widthSpec = makeMeasureSpec((int) width, EXACTLY); + heightSpec = makeMeasureSpec((int) buttonHeight, EXACTLY); + + super.onMeasure(widthSpec, heightSpec); + } + + private void addButtons() + { + removeAllViews(); + + for (int i = 0; i < nButtons; i++) + addView(new NumberButtonView(getContext())); + } + + private int getCheckmarkOrder() + { + if (prefs == null) return LEFT_TO_RIGHT; + return prefs.shouldReverseCheckmarks() ? RIGHT_TO_LEFT : LEFT_TO_RIGHT; + } + + private void init() + { + Context appContext = getContext().getApplicationContext(); + if (appContext instanceof HabitsApplication) + { + HabitsApplication app = (HabitsApplication) appContext; + prefs = app.getComponent().getPreferences(); + } + + setWillNotDraw(false); + values = new int[0]; + } + + private void setupButtonControllers(long timestamp, + NumberButtonView buttonView) + { + if (controller == null) return; + if (!(getContext() instanceof ListHabitsActivity)) return; + + ListHabitsActivity activity = (ListHabitsActivity) getContext(); + NumberButtonControllerFactory buttonControllerFactory = activity + .getListHabitsComponent() + .getNumberButtonControllerFactory(); + + NumberButtonController buttonController = + buttonControllerFactory.create(habit, timestamp); + buttonController.setListener(controller); + buttonController.setView(buttonView); + buttonView.setController(buttonController); + } + + private void setupButtons() + { + long timestamp = DateUtils.getStartOfToday(); + long day = DateUtils.millisecondsInOneDay; + timestamp -= day * dataOffset; + + for (int i = 0; i < nButtons; i++) + { + NumberButtonView buttonView = indexToButton(i); + if (i + dataOffset >= values.length) break; + buttonView.setValue(values[i + dataOffset]); + buttonView.setColor(color); + buttonView.setThreshold(threshold); + setupButtonControllers(timestamp, buttonView); + timestamp -= day; + } + } + + public interface Controller extends NumberButtonController.Listener + { + + } +} diff --git a/app/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java b/app/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java index c633a9b72..67c9de531 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/AttributeSetUtils.java @@ -74,4 +74,14 @@ public class AttributeSetUtils if (number != null) return Float.parseFloat(number); else return defaultValue; } + + public static int getIntAttribute(@NonNull Context context, + @NonNull AttributeSet attrs, + @NonNull String name, + int defaultValue) + { + String number = getAttribute(context, attrs, name, null); + if (number != null) return Integer.parseInt(number); + else return defaultValue; + } } diff --git a/app/src/main/res/layout/list_habits_button_preview.xml b/app/src/main/res/layout/list_habits_button_preview.xml new file mode 100644 index 000000000..5f2724a07 --- /dev/null +++ b/app/src/main/res/layout/list_habits_button_preview.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_habits_panel_preview.xml b/app/src/main/res/layout/list_habits_panel_preview.xml new file mode 100644 index 000000000..54393cad1 --- /dev/null +++ b/app/src/main/res/layout/list_habits_panel_preview.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e2a8de3acfac8e3bb6d4199fd8a0269389436152 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Thu, 23 Mar 2017 19:09:44 -0400 Subject: [PATCH 02/22] Update controllers and HabitCardView --- .../habits/list/views/HabitCardViewTest.java | 2 +- .../habits/list/ListHabitsController.java | 12 ++++++ .../list/controllers/HabitCardController.java | 20 +++++++-- .../controllers/HabitCardListController.java | 17 +++++++- .../habits/list/views/HabitCardListView.java | 4 +- .../habits/list/views/HabitCardView.java | 42 ++++++++++++++----- .../habits/list/views/NumberButtonView.java | 8 ++-- .../habits/list/views/NumberPanelView.java | 2 +- app/src/main/res/layout/list_habits_card.xml | 5 +++ app/src/main/res/values/strings.xml | 2 + 10 files changed, 91 insertions(+), 23 deletions(-) diff --git a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java index 2ca386e18..162f56b9b 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/activities/habits/list/views/HabitCardViewTest.java @@ -59,7 +59,7 @@ public class HabitCardViewTest extends BaseViewTest view = new HabitCardView(targetContext); view.setHabit(habit); - view.setCheckmarkValues(values); + view.setValues(values); view.setSelected(false); view.setScore(habit.getScores().getTodayValue()); view.setController(controller); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index a52c4b4d6..d0b584d05 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -157,6 +157,18 @@ public class ListHabitsController })); } + @Override + public void onInvalidEdit() + { + screen.showMessage(R.string.long_press_to_edit); + } + + @Override + public void onEdit(@NonNull Habit habit, long timestamp) + { + + } + @Override public void onInvalidToggle() diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardController.java index 01e2b4643..aa29f37c3 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardController.java @@ -21,8 +21,8 @@ package org.isoron.uhabits.activities.habits.list.controllers; import android.support.annotation.*; -import org.isoron.uhabits.models.Habit; -import org.isoron.uhabits.activities.habits.list.views.HabitCardView; +import org.isoron.uhabits.activities.habits.list.views.*; +import org.isoron.uhabits.models.*; public class HabitCardController implements HabitCardView.Controller { @@ -32,6 +32,18 @@ public class HabitCardController implements HabitCardView.Controller @Nullable private Listener listener; + @Override + public void onEdit(@NonNull Habit habit, long timestamp) + { + if(listener != null) listener.onEdit(habit, timestamp); + } + + @Override + public void onInvalidEdit() + { + if(listener != null) listener.onInvalidEdit(); + } + @Override public void onInvalidToggle() { @@ -55,7 +67,9 @@ public class HabitCardController implements HabitCardView.Controller this.view = view; } - public interface Listener extends CheckmarkButtonController.Listener + public interface Listener extends CheckmarkButtonController.Listener, + NumberButtonController.Listener { + } } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListController.java index d710c3572..0d62048bc 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/controllers/HabitCardListController.java @@ -21,9 +21,9 @@ package org.isoron.uhabits.activities.habits.list.controllers; import android.support.annotation.*; -import org.isoron.uhabits.models.*; import org.isoron.uhabits.activities.habits.list.model.*; import org.isoron.uhabits.activities.habits.list.views.*; +import org.isoron.uhabits.models.*; /** * Controller responsible for receiving and processing the events generated by a @@ -75,6 +75,18 @@ public class HabitCardListController implements HabitCardListView.Controller habitListener.onHabitReorder(habitFrom, habitTo); } + @Override + public void onEdit(@NonNull Habit habit, long timestamp) + { + if (habitListener != null) habitListener.onEdit(habit, timestamp); + } + + @Override + public void onInvalidEdit() + { + if (habitListener != null) habitListener.onInvalidEdit(); + } + /** * Called when the user attempts to perform a toggle, but attempt is * rejected. @@ -172,7 +184,8 @@ public class HabitCardListController implements HabitCardListView.Controller if (selectionListener != null) selectionListener.onSelectionFinish(); } - public interface HabitListener extends CheckmarkButtonController.Listener + public interface HabitListener extends CheckmarkButtonController.Listener, + NumberButtonController.Listener { /** * Called when the user clicks a habit. diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java index bc784465e..51e22c65d 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListView.java @@ -91,8 +91,8 @@ public class HabitCardListView extends RecyclerView HabitCardView cardView = (HabitCardView) holder.itemView; cardView.setHabit(habit); cardView.setSelected(selected); - cardView.setCheckmarkValues(checkmarks); - cardView.setCheckmarkCount(checkmarkCount); + cardView.setValues(checkmarks); + cardView.setButtonCount(checkmarkCount); cardView.setDataOffset(dataOffset); cardView.setScore(score); if (controller != null) setupCardViewController(holder); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index e703ba18c..028da530c 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -56,6 +56,9 @@ public class HabitCardView extends FrameLayout @BindView(R.id.checkmarkPanel) CheckmarkPanelView checkmarkPanel; + @BindView(R.id.numberPanel) + NumberPanelView numberPanel; + @BindView(R.id.innerFrame) LinearLayout innerFrame; @@ -92,28 +95,38 @@ public class HabitCardView extends FrameLayout new Handler(Looper.getMainLooper()).post(() -> refresh()); } - public void setCheckmarkCount(int checkmarkCount) + public void setButtonCount(int buttonCount) { - checkmarkPanel.setButtonCount(checkmarkCount); + checkmarkPanel.setButtonCount(buttonCount); + numberPanel.setButtonCount(buttonCount); } - public void setCheckmarkValues(int checkmarks[]) + public void setValues(int values[]) { - checkmarkPanel.setValues(checkmarks); + checkmarkPanel.setValues(values); + + int[] magnitudes = new int[]{10, 100, 1000, 10000}; + int threshold = magnitudes[new Random().nextInt(4)]; + numberPanel.setThreshold(threshold); + numberPanel.initEditMode(); + postInvalidate(); } public void setController(Controller controller) { checkmarkPanel.setController(null); + numberPanel.setController(null); if (controller == null) return; checkmarkPanel.setController(controller); + numberPanel.setController(controller); } public void setDataOffset(int dataOffset) { this.dataOffset = dataOffset; checkmarkPanel.setDataOffset(dataOffset); + numberPanel.setDataOffset(dataOffset); } public void setHabit(@NonNull Habit habit) @@ -122,6 +135,7 @@ public class HabitCardView extends FrameLayout this.habit = habit; checkmarkPanel.setHabit(habit); + numberPanel.setHabit(habit); refresh(); attachToHabit(); @@ -191,7 +205,8 @@ public class HabitCardView extends FrameLayout inflate(context, R.layout.list_habits_card, this); ButterKnife.bind(this); - innerFrame.setOnTouchListener((v, event) -> { + innerFrame.setOnTouchListener((v, event) -> + { if (SDK_INT >= LOLLIPOP) v.getBackground().setHotspot(event.getX(), event.getY()); return false; @@ -205,15 +220,12 @@ public class HabitCardView extends FrameLayout { Random rand = new Random(); int color = ColorUtils.getAndroidTestColor(rand.nextInt(10)); - int[] values = new int[5]; - for (int i = 0; i < 5; i++) values[i] = rand.nextInt(3); - label.setText(EDIT_MODE_HABITS[rand.nextInt(EDIT_MODE_HABITS.length)]); label.setTextColor(color); scoreRing.setColor(color); scoreRing.setPercentage(rand.nextFloat()); checkmarkPanel.setColor(color); - checkmarkPanel.setValues(values); + numberPanel.setColor(color); } private void refresh() @@ -223,6 +235,12 @@ public class HabitCardView extends FrameLayout label.setTextColor(color); scoreRing.setColor(color); checkmarkPanel.setColor(color); + numberPanel.setColor(color); + + boolean isNumberHabit = false; //(new Random().nextInt(3) == 0); + checkmarkPanel.setVisibility(isNumberHabit ? GONE : VISIBLE); + numberPanel.setVisibility(isNumberHabit ? VISIBLE : GONE); + postInvalidate(); } @@ -256,5 +274,9 @@ public class HabitCardView extends FrameLayout } } - public interface Controller extends CheckmarkPanelView.Controller {} + public interface Controller + extends CheckmarkPanelView.Controller, NumberPanelView.Controller + { + + } } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java index 1d4d4f103..ff4e622d6 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberButtonView.java @@ -36,7 +36,7 @@ import static org.isoron.uhabits.utils.ColorUtils.*; public class NumberButtonView extends TextView { private static Typeface TYPEFACE = - Typeface.create("sans-serif-condensed", Typeface.BOLD); + Typeface.create("sans-serif-condensed", Typeface.NORMAL); private int color; @@ -72,13 +72,13 @@ public class NumberButtonView extends TextView private static String formatValue(int v) { double fv = (double) v; - if(v >= 1e9) return String.format("%.2fG", fv / 1e9); + if(v >= 1e9) return String.format("%.1fG", fv / 1e9); if(v >= 1e8) return String.format("%.0fM", fv / 1e6); if(v >= 1e7) return String.format("%.1fM", fv / 1e6); - if(v >= 1e6) return String.format("%.2fM", fv / 1e6); + if(v >= 1e6) return String.format("%.1fM", fv / 1e6); if(v >= 1e5) return String.format("%.0fk", fv / 1e3); if(v >= 1e4) return String.format("%.1fk", fv / 1e3); - if(v >= 1e3) return String.format("%.2fk", fv / 1e3); + if(v >= 1e3) return String.format("%.1fk", fv / 1e3); return String.format("%d", v); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java index 16ac11e9a..cfbf3de15 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/NumberPanelView.java @@ -84,7 +84,7 @@ public class NumberPanelView extends LinearLayout if(isInEditMode()) initEditMode(); } - private void initEditMode() + public void initEditMode() { int values[] = new int[nButtons]; diff --git a/app/src/main/res/layout/list_habits_card.xml b/app/src/main/res/layout/list_habits_card.xml index 7b8d0f54d..6109c7472 100644 --- a/app/src/main/res/layout/list_habits_card.xml +++ b/app/src/main/res/layout/list_habits_card.xml @@ -46,6 +46,11 @@ android:id="@+id/checkmarkPanel" style="@style/ListHabits.CheckmarkPanel"/> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1e94bea9a..a28e7e576 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -204,4 +204,6 @@ By score Download Export + Press-and-hold to change the + value \ No newline at end of file From ac32460859b57beecbd8482c4232a7cd7c1aba97 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 24 Mar 2017 08:20:59 -0400 Subject: [PATCH 03/22] Create number picker dialog --- .../habits/list/ListHabitsController.java | 8 +- .../habits/list/ListHabitsScreen.java | 177 +++++++++++------- .../main/res/layout/number_picker_dialog.xml | 33 ++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 152 insertions(+), 67 deletions(-) create mode 100644 app/src/main/res/layout/number_picker_dialog.xml diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index d0b584d05..08be54b39 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.list; import android.support.annotation.*; +import android.util.*; import org.isoron.uhabits.*; import org.isoron.uhabits.activities.*; @@ -166,7 +167,12 @@ public class ListHabitsController @Override public void onEdit(@NonNull Habit habit, long timestamp) { - + int oldValue = habit.getCheckmarks().getTodayValue(); + screen.showNumberPicker(oldValue, newValue -> { + Log.d("ListHabitsController", + String.format("%s %d %d", habit.getName(), timestamp, + newValue)); + }); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 045505a90..822964d7b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -23,6 +23,9 @@ import android.app.*; import android.content.*; import android.net.*; import android.support.annotation.*; +import android.support.v7.app.AlertDialog; +import android.view.*; +import android.widget.*; import org.isoron.uhabits.*; import org.isoron.uhabits.activities.*; @@ -41,25 +44,26 @@ import javax.inject.*; import static android.os.Build.VERSION.*; import static android.os.Build.VERSION_CODES.*; +import static org.isoron.uhabits.utils.InterfaceUtils.*; @ActivityScope public class ListHabitsScreen extends BaseScreen implements CommandRunner.Listener { - public static final int RESULT_IMPORT_DATA = 1; + public static final int REQUEST_OPEN_DOCUMENT = 6; + + public static final int REQUEST_SETTINGS = 7; + + public static final int RESULT_BUG_REPORT = 4; public static final int RESULT_EXPORT_CSV = 2; public static final int RESULT_EXPORT_DB = 3; - public static final int RESULT_BUG_REPORT = 4; + public static final int RESULT_IMPORT_DATA = 1; public static final int RESULT_REPAIR_DB = 5; - public static final int REQUEST_OPEN_DOCUMENT = 6; - - public static final int REQUEST_SETTINGS = 7; - @Nullable private ListHabitsController controller; @@ -97,11 +101,16 @@ public class ListHabitsScreen extends BaseScreen @NonNull ListHabitsRootView rootView, @NonNull IntentFactory intentFactory, @NonNull ThemeSwitcher themeSwitcher, - @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, - @NonNull CreateHabitDialogFactory createHabitDialogFactory, - @NonNull FilePickerDialogFactory filePickerDialogFactory, - @NonNull ColorPickerDialogFactory colorPickerFactory, - @NonNull EditHabitDialogFactory editHabitDialogFactory) + @NonNull + ConfirmDeleteDialogFactory confirmDeleteDialogFactory, + @NonNull + CreateHabitDialogFactory createHabitDialogFactory, + @NonNull + FilePickerDialogFactory filePickerDialogFactory, + @NonNull + ColorPickerDialogFactory colorPickerFactory, + @NonNull + EditHabitDialogFactory editHabitDialogFactory) { super(activity); setRootView(rootView); @@ -139,60 +148,7 @@ public class ListHabitsScreen extends BaseScreen if (requestCode == REQUEST_OPEN_DOCUMENT) onOpenDocumentResult(resultCode, data); - if (requestCode == REQUEST_SETTINGS) - onSettingsResult(resultCode); - } - - private void onSettingsResult(int resultCode) - { - if (controller == null) return; - - switch (resultCode) - { - case RESULT_IMPORT_DATA: - showImportScreen(); - break; - - case RESULT_EXPORT_CSV: - controller.onExportCSV(); - break; - - case RESULT_EXPORT_DB: - controller.onExportDB(); - break; - - case RESULT_BUG_REPORT: - controller.onSendBugReport(); - break; - - case RESULT_REPAIR_DB: - controller.onRepairDB(); - break; - } - } - - private void onOpenDocumentResult(int resultCode, Intent data) - { - if (controller == null) return; - if (resultCode != Activity.RESULT_OK) return; - - try - { - Uri uri = data.getData(); - ContentResolver cr = activity.getContentResolver(); - InputStream is = cr.openInputStream(uri); - - File cacheDir = activity.getExternalCacheDir(); - File tempFile = File.createTempFile("import", "", cacheDir); - - FileUtils.copy(is, tempFile); - controller.onImportData(tempFile, () -> tempFile.delete()); - } - catch (IOException e) - { - showMessage(R.string.could_not_import); - e.printStackTrace(); - } + if (requestCode == REQUEST_SETTINGS) onSettingsResult(resultCode); } public void setController(@Nullable ListHabitsController controller) @@ -238,6 +194,36 @@ public class ListHabitsScreen extends BaseScreen activity.showDialog(dialog, "editHabit"); } + public void showNumberPicker(int initialValue, + @NonNull NumberPickerCallback callback) + { + LayoutInflater inflater = activity.getLayoutInflater(); + View view = inflater.inflate(R.layout.number_picker_dialog, null); + + final NumberPicker picker = + (NumberPicker) view.findViewById(R.id.picker); + + picker.setMinValue(0); + picker.setMaxValue(Integer.MAX_VALUE); + picker.setValue(initialValue); + picker.setWrapSelectorWheel(false); + + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder + .setView(view) + .setTitle(R.string.change_value) + .setPositiveButton(android.R.string.ok, (dialog, which) -> + { + callback.onNumberPicked(picker.getValue()); + }); + + AlertDialog dialog = builder.create(); + dialog.show(); + int width = (int) dpToPixels(activity, 200); + int height = (int) dpToPixels(activity, 275); + dialog.getWindow().setLayout(width, height); + } + public void showFAQScreen() { Intent intent = intentFactory.viewFAQ(activity); @@ -278,7 +264,9 @@ public class ListHabitsScreen extends BaseScreen FilePickerDialog picker = filePickerDialogFactory.create(dir); if (controller != null) - picker.setListener(file -> controller.onImportData(file, () -> {})); + picker.setListener(file -> controller.onImportData(file, () -> + { + })); activity.showDialog(picker.getDialog()); } @@ -300,4 +288,61 @@ public class ListHabitsScreen extends BaseScreen themeSwitcher.toggleNightMode(); activity.restartWithFade(); } + + private void onOpenDocumentResult(int resultCode, Intent data) + { + if (controller == null) return; + if (resultCode != Activity.RESULT_OK) return; + + try + { + Uri uri = data.getData(); + ContentResolver cr = activity.getContentResolver(); + InputStream is = cr.openInputStream(uri); + + File cacheDir = activity.getExternalCacheDir(); + File tempFile = File.createTempFile("import", "", cacheDir); + + FileUtils.copy(is, tempFile); + controller.onImportData(tempFile, () -> tempFile.delete()); + } + catch (IOException e) + { + showMessage(R.string.could_not_import); + e.printStackTrace(); + } + } + + private void onSettingsResult(int resultCode) + { + if (controller == null) return; + + switch (resultCode) + { + case RESULT_IMPORT_DATA: + showImportScreen(); + break; + + case RESULT_EXPORT_CSV: + controller.onExportCSV(); + break; + + case RESULT_EXPORT_DB: + controller.onExportDB(); + break; + + case RESULT_BUG_REPORT: + controller.onSendBugReport(); + break; + + case RESULT_REPAIR_DB: + controller.onRepairDB(); + break; + } + } + + public interface NumberPickerCallback + { + void onNumberPicked(int newValue); + } } diff --git a/app/src/main/res/layout/number_picker_dialog.xml b/app/src/main/res/layout/number_picker_dialog.xml new file mode 100644 index 000000000..396f9c819 --- /dev/null +++ b/app/src/main/res/layout/number_picker_dialog.xml @@ -0,0 +1,33 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a28e7e576..0933fdf6f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -206,4 +206,5 @@ Export Press-and-hold to change the value + Change value \ No newline at end of file From 5b9e90fe7ac92f884ce90a31b3da9b4abccef171 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 24 Mar 2017 09:41:52 -0400 Subject: [PATCH 04/22] Persist repetition values --- app/build.gradle | 2 +- .../sqlite/SQLiteRepetitionListTest.java | 3 +- app/src/main/assets/migrations/16.sql | 2 + .../activities/common/views/HistoryChart.java | 6 +- .../habits/list/ListHabitsController.java | 7 +- .../habits/list/views/HabitCardView.java | 12 +-- .../commands/CreateRepetitionCommand.java | 78 +++++++++++++++++++ .../isoron/uhabits/models/CheckmarkList.java | 11 ++- .../java/org/isoron/uhabits/models/Habit.java | 7 ++ .../org/isoron/uhabits/models/Repetition.java | 34 +++++++- .../isoron/uhabits/models/RepetitionList.java | 6 +- .../models/memory/MemoryRepetitionList.java | 3 - .../models/sqlite/SQLiteRepetitionList.java | 9 +-- .../sqlite/records/RepetitionRecord.java | 7 +- .../commands/CreateRepetitionCommandTest.java | 70 +++++++++++++++++ 15 files changed, 227 insertions(+), 30 deletions(-) create mode 100644 app/src/main/assets/migrations/16.sql create mode 100644 app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java create mode 100644 app/src/test/java/org/isoron/uhabits/commands/CreateRepetitionCommandTest.java diff --git a/app/build.gradle b/app/build.gradle index 93d60fe16..649ab0f05 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdkVersion 15 targetSdkVersion 25 - buildConfigField "Integer", "databaseVersion", "15" + buildConfigField "Integer", "databaseVersion", "16" buildConfigField "String", "databaseFilename", "\"uhabits.db\"" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java index 992c1e673..766271ada 100644 --- a/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java +++ b/app/src/androidTest/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionListTest.java @@ -37,6 +37,7 @@ import java.util.*; import static org.hamcrest.MatcherAssert.*; import static org.hamcrest.Matchers.*; import static org.hamcrest.core.IsNot.not; +import static org.isoron.uhabits.models.Checkmark.*; @RunWith(AndroidJUnit4.class) @MediumTest @@ -67,7 +68,7 @@ public class SQLiteRepetitionListTest extends BaseAndroidTest RepetitionRecord record = getByTimestamp(today + day); assertThat(record, is(nullValue())); - Repetition rep = new Repetition(today + day); + Repetition rep = new Repetition(today + day, CHECKED_EXPLICITLY); habit.getRepetitions().add(rep); record = getByTimestamp(today + day); diff --git a/app/src/main/assets/migrations/16.sql b/app/src/main/assets/migrations/16.sql new file mode 100644 index 000000000..7a7746f63 --- /dev/null +++ b/app/src/main/assets/migrations/16.sql @@ -0,0 +1,2 @@ +alter table Habits add column type integer not null default 0; +alter table Repetitions add column value integer not null default 2; \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java b/app/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java index 620a2d59f..798ecdb88 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java +++ b/app/src/main/java/org/isoron/uhabits/activities/common/views/HistoryChart.java @@ -323,7 +323,11 @@ public class HistoryChart extends ScrollableChart int checkmarkOffset) { if (checkmarkOffset >= checkmarks.length) pSquareBg.setColor(colors[0]); - else pSquareBg.setColor(colors[checkmarks[checkmarkOffset]]); + else + { + int checkmark = checkmarks[checkmarkOffset]; + pSquareBg.setColor(colors[Integer.min(2, checkmark)]); + } pSquareFg.setColor(reverseTextColor); canvas.drawRect(location, pSquareBg); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index 08be54b39..7d02974e4 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -20,7 +20,6 @@ package org.isoron.uhabits.activities.habits.list; import android.support.annotation.*; -import android.util.*; import org.isoron.uhabits.*; import org.isoron.uhabits.activities.*; @@ -169,9 +168,9 @@ public class ListHabitsController { int oldValue = habit.getCheckmarks().getTodayValue(); screen.showNumberPicker(oldValue, newValue -> { - Log.d("ListHabitsController", - String.format("%s %d %d", habit.getName(), timestamp, - newValue)); + commandRunner.execute( + new CreateRepetitionCommand(habit, timestamp, newValue), + habit.getId()); }); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index 028da530c..febbfde84 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -104,11 +104,13 @@ public class HabitCardView extends FrameLayout public void setValues(int values[]) { checkmarkPanel.setValues(values); + numberPanel.setValues(values); + numberPanel.setThreshold(10); - int[] magnitudes = new int[]{10, 100, 1000, 10000}; - int threshold = magnitudes[new Random().nextInt(4)]; - numberPanel.setThreshold(threshold); - numberPanel.initEditMode(); +// int[] magnitudes = new int[]{10, 100, 1000, 10000}; +// int threshold = magnitudes[new Random().nextInt(4)]; +// numberPanel.setThreshold(threshold); +// numberPanel.initEditMode(); postInvalidate(); } @@ -237,7 +239,7 @@ public class HabitCardView extends FrameLayout checkmarkPanel.setColor(color); numberPanel.setColor(color); - boolean isNumberHabit = false; //(new Random().nextInt(3) == 0); + boolean isNumberHabit = true; //(new Random().nextInt(3) == 0); checkmarkPanel.setVisibility(isNumberHabit ? GONE : VISIBLE); numberPanel.setVisibility(isNumberHabit ? VISIBLE : GONE); diff --git a/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java b/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java new file mode 100644 index 000000000..5422d7f1b --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/commands/CreateRepetitionCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2016 Á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.commands; + +import android.support.annotation.*; + +import org.isoron.uhabits.models.*; + +/** + * Command to toggle a repetition. + */ +public class CreateRepetitionCommand extends Command +{ + @NonNull + private final Habit habit; + + private final long timestamp; + + private final int value; + + private Repetition previousRep; + + private Repetition newRep; + + public CreateRepetitionCommand(@NonNull Habit habit, + long timestamp, + int value) + { + this.timestamp = timestamp; + this.habit = habit; + this.value = value; + } + + @Override + public void execute() + { + RepetitionList reps = habit.getRepetitions(); + + previousRep = reps.getByTimestamp(timestamp); + if (previousRep != null) reps.remove(previousRep); + + newRep = new Repetition(timestamp, value); + reps.add(newRep); + + habit.invalidateNewerThan(timestamp); + } + + @NonNull + public Habit getHabit() + { + return habit; + } + + @Override + public void undo() + { + habit.getRepetitions().remove(newRep); + if (previousRep != null) habit.getRepetitions().add(previousRep); + habit.invalidateNewerThan(timestamp); + } +} \ No newline at end of file diff --git a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java index 86488a8d1..5e71eb9e5 100644 --- a/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java +++ b/app/src/main/java/org/isoron/uhabits/models/CheckmarkList.java @@ -27,6 +27,9 @@ import java.io.*; import java.text.*; import java.util.*; +import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY; +import static org.isoron.uhabits.models.Checkmark.CHECKED_IMPLICITLY; + /** * The collection of {@link Checkmark}s belonging to a habit. */ @@ -239,7 +242,7 @@ public abstract class CheckmarkList for (Repetition rep : reps) { int offset = (int) ((rep.getTimestamp() - fromExtended) / day); - checks[nDaysExtended - offset - 1] = Checkmark.CHECKED_EXPLICITLY; + checks[nDaysExtended - offset - 1] = rep.getValue(); } for (int i = 0; i < nDays; i++) @@ -247,11 +250,11 @@ public abstract class CheckmarkList int counter = 0; for (int j = 0; j < freq.getDenominator(); j++) - if (checks[i + j] == 2) counter++; + if (checks[i + j] == CHECKED_EXPLICITLY) counter++; if (counter >= freq.getNumerator()) - if (checks[i] != Checkmark.CHECKED_EXPLICITLY) - checks[i] = Checkmark.CHECKED_IMPLICITLY; + if (checks[i] != CHECKED_EXPLICITLY) + checks[i] = CHECKED_IMPLICITLY; } List checkmarks = new LinkedList<>(); diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 5ba711407..5d81ddd64 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -274,4 +274,11 @@ public class Habit .append("archived", archived) .toString(); } + + public void invalidateNewerThan(long timestamp) + { + getScores().invalidateNewerThan(timestamp); + getCheckmarks().invalidateNewerThan(timestamp); + getStreaks().invalidateNewerThan(timestamp); + } } diff --git a/app/src/main/java/org/isoron/uhabits/models/Repetition.java b/app/src/main/java/org/isoron/uhabits/models/Repetition.java index 72e378205..3104fda9c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Repetition.java +++ b/app/src/main/java/org/isoron/uhabits/models/Repetition.java @@ -30,6 +30,8 @@ public final class Repetition private final long timestamp; + private final int value; + /** * Creates a new repetition with given parameters. *

@@ -38,9 +40,24 @@ public final class Repetition * * @param timestamp the time this repetition occurred. */ - public Repetition(long timestamp) + public Repetition(long timestamp, int value) { this.timestamp = timestamp; + this.value = value; + } + + @Override + public boolean equals(Object o) + { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Repetition that = (Repetition) o; + + return new EqualsBuilder() + .append(timestamp, that.timestamp) + .append(value, that.value) + .isEquals(); } public long getTimestamp() @@ -48,11 +65,26 @@ public final class Repetition return timestamp; } + public int getValue() + { + return value; + } + + @Override + public int hashCode() + { + return new HashCodeBuilder(17, 37) + .append(timestamp) + .append(value) + .toHashCode(); + } + @Override public String toString() { return new ToStringBuilder(this) .append("timestamp", timestamp) + .append("value", value) .toString(); } } diff --git a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java index 07fa7b681..ec355267c 100644 --- a/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/RepetitionList.java @@ -192,13 +192,11 @@ public abstract class RepetitionList if (rep != null) remove(rep); else { - rep = new Repetition(timestamp); + rep = new Repetition(timestamp, Checkmark.CHECKED_EXPLICITLY); add(rep); } - habit.getScores().invalidateNewerThan(timestamp); - habit.getCheckmarks().invalidateNewerThan(timestamp); - habit.getStreaks().invalidateNewerThan(timestamp); + habit.invalidateNewerThan(timestamp); return rep; } diff --git a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java index dbac82b40..cf1772e0b 100644 --- a/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/memory/MemoryRepetitionList.java @@ -86,7 +86,6 @@ public class MemoryRepetitionList extends RepetitionList oldestRep = rep; oldestTime = rep.getTimestamp(); } - } return oldestRep; @@ -106,7 +105,6 @@ public class MemoryRepetitionList extends RepetitionList newestRep = rep; newestTime = rep.getTimestamp(); } - } return newestRep; @@ -119,7 +117,6 @@ public class MemoryRepetitionList extends RepetitionList observable.notifyListeners(); } - @NonNull @Override public long getTotalCount() { diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java index 6278863e9..2794950f5 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/SQLiteRepetitionList.java @@ -73,7 +73,7 @@ public class SQLiteRepetitionList extends RepetitionList public List getByInterval(long timeFrom, long timeTo) { check(habit.getId()); - String query = "select habit, timestamp " + + String query = "select habit, timestamp, value " + "from Repetitions " + "where habit = ? and timestamp >= ? and timestamp <= ? " + "order by timestamp"; @@ -93,7 +93,7 @@ public class SQLiteRepetitionList extends RepetitionList public Repetition getByTimestamp(long timestamp) { check(habit.getId()); - String query = "select habit, timestamp " + + String query = "select habit, timestamp, value " + "from Repetitions " + "where habit = ? and timestamp = ? " + "limit 1"; @@ -111,7 +111,7 @@ public class SQLiteRepetitionList extends RepetitionList public Repetition getOldest() { check(habit.getId()); - String query = "select habit, timestamp " + + String query = "select habit, timestamp, value " + "from Repetitions " + "where habit = ? " + "order by timestamp asc " + @@ -129,7 +129,7 @@ public class SQLiteRepetitionList extends RepetitionList public Repetition getNewest() { check(habit.getId()); - String query = "select habit, timestamp " + + String query = "select habit, timestamp, value " + "from Repetitions " + "where habit = ? " + "order by timestamp desc " + @@ -182,7 +182,6 @@ public class SQLiteRepetitionList extends RepetitionList return reps; } - @NonNull @Override public long getTotalCount() { diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java index 5f831495f..874fd0954 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/RepetitionRecord.java @@ -38,6 +38,9 @@ public class RepetitionRecord extends Model implements SQLiteRecord @Column(name = "timestamp") public Long timestamp; + @Column(name = "value") + public int value; + public static RepetitionRecord get(Long id) { return RepetitionRecord.load(RepetitionRecord.class, id); @@ -46,16 +49,18 @@ public class RepetitionRecord extends Model implements SQLiteRecord public void copyFrom(Repetition repetition) { timestamp = repetition.getTimestamp(); + value = repetition.getValue(); } @Override public void copyFrom(Cursor c) { timestamp = c.getLong(1); + value = c.getInt(2); } public Repetition toRepetition() { - return new Repetition(timestamp); + return new Repetition(timestamp, value); } } diff --git a/app/src/test/java/org/isoron/uhabits/commands/CreateRepetitionCommandTest.java b/app/src/test/java/org/isoron/uhabits/commands/CreateRepetitionCommandTest.java new file mode 100644 index 000000000..45816c559 --- /dev/null +++ b/app/src/test/java/org/isoron/uhabits/commands/CreateRepetitionCommandTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2016 Á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.commands; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.utils.*; +import org.junit.*; + +import static junit.framework.Assert.*; +import static org.isoron.uhabits.models.Checkmark.CHECKED_EXPLICITLY; + +public class CreateRepetitionCommandTest extends BaseUnitTest +{ + + private CreateRepetitionCommand command; + + private Habit habit; + + private long today; + + @Override + @Before + public void setUp() + { + super.setUp(); + + habit = fixtures.createShortHabit(); + + today = DateUtils.getStartOfToday(); + command = new CreateRepetitionCommand(habit, today, 100); + } + + @Test + public void testExecuteUndoRedo() + { + RepetitionList reps = habit.getRepetitions(); + + Repetition rep = reps.getByTimestamp(today); + assertNotNull(rep); + assertEquals(CHECKED_EXPLICITLY, rep.getValue()); + + command.execute(); + rep = reps.getByTimestamp(today); + assertNotNull(rep); + assertEquals(100, rep.getValue()); + + command.undo(); + rep = reps.getByTimestamp(today); + assertNotNull(rep); + assertEquals(CHECKED_EXPLICITLY, rep.getValue()); + } +} From 5c1ccfe6fe7f0f477913f1eb0df0fe64a29fa680 Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 24 Mar 2017 10:13:19 -0400 Subject: [PATCH 05/22] Fix small issues with the number picker --- .../habits/list/ListHabitsController.java | 2 +- .../habits/list/ListHabitsScreen.java | 29 +++++++++++++------ .../isoron/uhabits/utils/InterfaceUtils.java | 22 ++++++++++++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java index 7d02974e4..db0b8cbf5 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsController.java @@ -166,7 +166,7 @@ public class ListHabitsController @Override public void onEdit(@NonNull Habit habit, long timestamp) { - int oldValue = habit.getCheckmarks().getTodayValue(); + int oldValue = habit.getCheckmarks().getValues(timestamp, timestamp)[0]; screen.showNumberPicker(oldValue, newValue -> { commandRunner.execute( new CreateRepetitionCommand(habit, timestamp, newValue), diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 822964d7b..486892cc7 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -42,8 +42,10 @@ import java.io.*; import javax.inject.*; +import static android.content.DialogInterface.*; import static android.os.Build.VERSION.*; import static android.os.Build.VERSION_CODES.*; +import static android.view.inputmethod.EditorInfo.*; import static org.isoron.uhabits.utils.InterfaceUtils.*; @ActivityScope @@ -208,20 +210,29 @@ public class ListHabitsScreen extends BaseScreen picker.setValue(initialValue); picker.setWrapSelectorWheel(false); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); - builder + AlertDialog dialog = new AlertDialog.Builder(activity) .setView(view) .setTitle(R.string.change_value) - .setPositiveButton(android.R.string.ok, (dialog, which) -> - { + .setPositiveButton(android.R.string.ok, (d, which) -> { + picker.clearFocus(); callback.onNumberPicked(picker.getValue()); - }); + }).create(); + + InterfaceUtils.setupEditorAction(picker, (v, actionId, event) -> { + if (actionId == IME_ACTION_DONE) + dialog.getButton(BUTTON_POSITIVE).performClick(); + return false; + }); - AlertDialog dialog = builder.create(); dialog.show(); - int width = (int) dpToPixels(activity, 200); - int height = (int) dpToPixels(activity, 275); - dialog.getWindow().setLayout(width, height); + + Window window = dialog.getWindow(); + if (window != null) + { + int width = (int) dpToPixels(activity, 200); + int height = (int) dpToPixels(activity, 275); + window.setLayout(width, height); + } } public void showFAQScreen() diff --git a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java index 5db2a875a..2bf26c040 100644 --- a/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java +++ b/app/src/main/java/org/isoron/uhabits/utils/InterfaceUtils.java @@ -22,7 +22,10 @@ package org.isoron.uhabits.utils; import android.content.*; import android.content.res.*; import android.graphics.*; +import android.support.annotation.*; import android.util.*; +import android.view.*; +import android.widget.*; import java.util.*; @@ -39,8 +42,9 @@ public abstract class InterfaceUtils public static Typeface getFontAwesome(Context context) { - if(fontAwesome == null) - fontAwesome = Typeface.createFromAsset(context.getAssets(), "fontawesome-webfont.ttf"); + if(fontAwesome == null) fontAwesome = + Typeface.createFromAsset(context.getAssets(), + "fontawesome-webfont.ttf"); return fontAwesome; } @@ -69,4 +73,18 @@ public abstract class InterfaceUtils return false; } + public static void setupEditorAction(@NonNull ViewGroup parent, + @NonNull TextView.OnEditorActionListener listener) + { + for (int i = 0; i < parent.getChildCount(); i++) + { + View child = parent.getChildAt(i); + + if (child instanceof ViewGroup) + setupEditorAction((ViewGroup) child, listener); + + if (child instanceof TextView) + ((TextView) child).setOnEditorActionListener(listener); + } + } } From d03edf28950fb972dac8b8b29db70d24caa4ecaa Mon Sep 17 00:00:00 2001 From: Alinson Xavier Date: Fri, 24 Mar 2017 16:47:40 -0400 Subject: [PATCH 06/22] Update habit creation dialogs --- .../habits/edit/BaseDialogHelper.java | 69 +++-- ...aseDialog.java => BooleanHabitDialog.java} | 13 +- ...log.java => CreateBooleanHabitDialog.java} | 2 +- .../edit/CreateNumericalHabitDialog.java | 55 ++++ ...ialog.java => EditBooleanHabitDialog.java} | 2 +- ...ava => EditBooleanHabitDialogFactory.java} | 8 +- .../habits/edit/NumericalHabitDialog.java | 260 ++++++++++++++++++ .../habits/list/ListHabitsScreen.java | 134 +++++---- .../habits/list/views/HabitCardView.java | 12 +- .../habits/show/ShowHabitScreen.java | 9 +- .../java/org/isoron/uhabits/models/Habit.java | 41 ++- .../models/sqlite/records/HabitRecord.java | 11 +- ...{edit_habit.xml => edit_boolean_habit.xml} | 2 +- .../main/res/layout/edit_numerical_habit.xml | 110 ++++++++ app/src/main/res/values/constants.xml | 5 + app/src/main/res/values/styles.xml | 4 + .../habits/list/ListHabitsScreenTest.java | 18 +- 17 files changed, 635 insertions(+), 120 deletions(-) rename app/src/main/java/org/isoron/uhabits/activities/habits/edit/{BaseDialog.java => BooleanHabitDialog.java} (96%) rename app/src/main/java/org/isoron/uhabits/activities/habits/edit/{CreateHabitDialog.java => CreateBooleanHabitDialog.java} (96%) create mode 100644 app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateNumericalHabitDialog.java rename app/src/main/java/org/isoron/uhabits/activities/habits/edit/{EditHabitDialog.java => EditBooleanHabitDialog.java} (96%) rename app/src/main/java/org/isoron/uhabits/activities/habits/edit/{EditHabitDialogFactory.java => EditBooleanHabitDialogFactory.java} (84%) create mode 100644 app/src/main/java/org/isoron/uhabits/activities/habits/edit/NumericalHabitDialog.java rename app/src/main/res/layout/{edit_habit.xml => edit_boolean_habit.xml} (98%) create mode 100644 app/src/main/res/layout/edit_numerical_habit.xml diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialogHelper.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialogHelper.java index 225b509ff..4712c8961 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialogHelper.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialogHelper.java @@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.edit; import android.annotation.*; +import android.support.annotation.*; import android.support.v4.app.*; import android.view.*; import android.widget.*; @@ -40,9 +41,11 @@ public class BaseDialogHelper @BindView(R.id.tvDescription) TextView tvDescription; + @Nullable @BindView(R.id.tvFreqNum) TextView tvFreqNum; + @Nullable @BindView(R.id.tvFreqDen) TextView tvFreqDen; @@ -52,9 +55,11 @@ public class BaseDialogHelper @BindView(R.id.tvReminderDays) TextView tvReminderDays; + @Nullable @BindView(R.id.sFrequency) Spinner sFrequency; + @Nullable @BindView(R.id.llCustomFrequency) ViewGroup llCustomFrequency; @@ -69,9 +74,8 @@ public class BaseDialogHelper protected void populateForm(final Habit habit) { - if (habit.getName() != null) tvName.setText(habit.getName()); - if (habit.getDescription() != null) - tvDescription.setText(habit.getDescription()); + tvName.setText(habit.getName()); + tvDescription.setText(habit.getDescription()); populateColor(habit.getColor()); populateFrequencyFields(habit); @@ -82,13 +86,17 @@ public class BaseDialogHelper { habit.setName(tvName.getText().toString().trim()); habit.setDescription(tvDescription.getText().toString().trim()); - String freqNum = tvFreqNum.getText().toString(); - String freqDen = tvFreqDen.getText().toString(); - if (!freqNum.isEmpty() && !freqDen.isEmpty()) + + if (tvFreqDen != null && tvFreqNum != null) { - int numerator = Integer.parseInt(freqNum); - int denominator = Integer.parseInt(freqDen); - habit.setFrequency(new Frequency(numerator, denominator)); + String freqNum = tvFreqNum.getText().toString(); + String freqDen = tvFreqDen.getText().toString(); + if (!freqNum.isEmpty() && !freqDen.isEmpty()) + { + int numerator = Integer.parseInt(freqNum); + int denominator = Integer.parseInt(freqDen); + habit.setFrequency(new Frequency(numerator, denominator)); + } } } @@ -101,15 +109,16 @@ public class BaseDialogHelper @SuppressLint("SetTextI18n") void populateFrequencyFields(Habit habit) { + if (tvFreqNum == null) return; + if (tvFreqDen == null) return; + int quickSelectPosition = -1; Frequency freq = habit.getFrequency(); - if (freq.equals(Frequency.DAILY)) - quickSelectPosition = 0; + if (freq.equals(Frequency.DAILY)) quickSelectPosition = 0; - else if (freq.equals(Frequency.WEEKLY)) - quickSelectPosition = 1; + else if (freq.equals(Frequency.WEEKLY)) quickSelectPosition = 1; else if (freq.equals(Frequency.TWO_TIMES_PER_WEEK)) quickSelectPosition = 2; @@ -144,13 +153,16 @@ public class BaseDialogHelper tvReminderTime.setText(time); llReminderDays.setVisibility(View.VISIBLE); - boolean weekdays[] = reminder.getDays().toArray(); + boolean weekdays[] = reminder.getDays().toArray(); tvReminderDays.setText( DateUtils.formatWeekdayList(frag.getContext(), weekdays)); } private void showCustomFrequency() { + if(sFrequency == null) return; + if(llCustomFrequency == null) return; + sFrequency.setVisibility(View.GONE); llCustomFrequency.setVisibility(View.VISIBLE); } @@ -158,6 +170,9 @@ public class BaseDialogHelper @SuppressLint("SetTextI18n") private void showSimplifiedFrequency(int quickSelectPosition) { + if(sFrequency == null) return; + if(llCustomFrequency == null) return; + sFrequency.setVisibility(View.VISIBLE); sFrequency.setSelection(quickSelectPosition); llCustomFrequency.setVisibility(View.GONE); @@ -175,19 +190,21 @@ public class BaseDialogHelper } Frequency freq = habit.getFrequency(); - - if (freq.getNumerator() <= 0) + if (tvFreqNum != null && tvFreqDen != null) { - tvFreqNum.setError( - frag.getString(R.string.validation_number_should_be_positive)); - valid = false; - } - - if (freq.getNumerator() > freq.getDenominator()) - { - tvFreqNum.setError( - frag.getString(R.string.validation_at_most_one_rep_per_day)); - valid = false; + if (freq.getNumerator() <= 0) + { + tvFreqNum.setError(frag.getString( + R.string.validation_number_should_be_positive)); + valid = false; + } + + if (freq.getNumerator() > freq.getDenominator()) + { + tvFreqNum.setError(frag.getString( + R.string.validation_at_most_one_rep_per_day)); + valid = false; + } } return valid; diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialog.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BooleanHabitDialog.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialog.java rename to app/src/main/java/org/isoron/uhabits/activities/habits/edit/BooleanHabitDialog.java index b4c228876..a2751ddc2 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BaseDialog.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/BooleanHabitDialog.java @@ -39,7 +39,7 @@ import java.util.*; import butterknife.*; -public abstract class BaseDialog extends AppCompatDialogFragment +public abstract class BooleanHabitDialog extends AppCompatDialogFragment { @Nullable protected Habit originalHabit; @@ -62,6 +62,12 @@ public abstract class BaseDialog extends AppCompatDialogFragment private ColorPickerDialogFactory colorPickerDialogFactory; + @Override + public int getTheme() + { + return R.style.DialogWithTitle; + } + @Override public void onActivityCreated(Bundle savedInstanceState) { @@ -77,7 +83,7 @@ public abstract class BaseDialog extends AppCompatDialogFragment ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.edit_habit, container, false); + View view = inflater.inflate(R.layout.edit_boolean_habit, container, false); HabitsApplication app = (HabitsApplication) getContext().getApplicationContext(); @@ -201,7 +207,8 @@ public abstract class BaseDialog extends AppCompatDialogFragment int color = modifiedHabit.getColor(); ColorPickerDialog picker = colorPickerDialogFactory.create(color); - picker.setListener(c -> { + picker.setListener(c -> + { prefs.setDefaultHabitColor(c); modifiedHabit.setColor(c); helper.populateColor(c); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateHabitDialog.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateBooleanHabitDialog.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateHabitDialog.java rename to app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateBooleanHabitDialog.java index 5f34e2e40..7d94f57c5 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateHabitDialog.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateBooleanHabitDialog.java @@ -26,7 +26,7 @@ import org.isoron.uhabits.commands.*; import org.isoron.uhabits.models.*; @AutoFactory(allowSubclasses = true) -public class CreateHabitDialog extends BaseDialog +public class CreateBooleanHabitDialog extends BooleanHabitDialog { @Override protected int getTitle() diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateNumericalHabitDialog.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateNumericalHabitDialog.java new file mode 100644 index 000000000..459a94aa1 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/CreateNumericalHabitDialog.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2016 Á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.habits.edit; + +import com.google.auto.factory.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; + +@AutoFactory(allowSubclasses = true) +public class CreateNumericalHabitDialog extends NumericalHabitDialog +{ + @Override + protected int getTitle() + { + return R.string.create_habit; + } + + @Override + protected void initializeHabits() + { + modifiedHabit = modelFactory.buildHabit(); + modifiedHabit.setFrequency(Frequency.DAILY); + modifiedHabit.setColor( + prefs.getDefaultHabitColor(modifiedHabit.getColor())); + modifiedHabit.setType(Habit.NUMBER_HABIT); + } + + @Override + protected void saveHabit() + { + Command command = appComponent + .getCreateHabitCommandFactory() + .create(habitList, modifiedHabit); + commandRunner.execute(command, null); + } +} diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialog.java similarity index 96% rename from app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java rename to app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialog.java index e9c1aca78..ff5c33775 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialog.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialog.java @@ -22,7 +22,7 @@ package org.isoron.uhabits.activities.habits.edit; import org.isoron.uhabits.*; import org.isoron.uhabits.commands.*; -public class EditHabitDialog extends BaseDialog +public class EditBooleanHabitDialog extends BooleanHabitDialog { @Override protected int getTitle() diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialogFactory.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialogFactory.java similarity index 84% rename from app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialogFactory.java rename to app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialogFactory.java index 481658ebf..43cc27348 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditHabitDialogFactory.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/EditBooleanHabitDialogFactory.java @@ -26,19 +26,19 @@ import org.isoron.uhabits.models.*; import javax.inject.*; -public class EditHabitDialogFactory +public class EditBooleanHabitDialogFactory { @Inject - public EditHabitDialogFactory() + public EditBooleanHabitDialogFactory() { } - public EditHabitDialog create(@NonNull Habit habit) + public EditBooleanHabitDialog create(@NonNull Habit habit) { if (habit.getId() == null) throw new IllegalArgumentException("habit not saved"); - EditHabitDialog dialog = new EditHabitDialog(); + EditBooleanHabitDialog dialog = new EditBooleanHabitDialog(); Bundle args = new Bundle(); args.putLong("habitId", habit.getId()); dialog.setArguments(args); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/edit/NumericalHabitDialog.java b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/NumericalHabitDialog.java new file mode 100644 index 000000000..3425f6663 --- /dev/null +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/edit/NumericalHabitDialog.java @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2016 Á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.habits.edit; + +import android.os.*; +import android.support.annotation.*; +import android.support.v7.app.*; +import android.text.format.*; +import android.view.*; + +import com.android.datetimepicker.time.*; + +import org.isoron.uhabits.*; +import org.isoron.uhabits.R; +import org.isoron.uhabits.activities.*; +import org.isoron.uhabits.activities.common.dialogs.*; +import org.isoron.uhabits.commands.*; +import org.isoron.uhabits.models.*; +import org.isoron.uhabits.preferences.*; + +import java.util.*; + +import butterknife.*; + +public abstract class NumericalHabitDialog extends AppCompatDialogFragment +{ + @Nullable + protected Habit originalHabit; + + @Nullable + protected Habit modifiedHabit; + + @Nullable + protected BaseDialogHelper helper; + + protected Preferences prefs; + + protected CommandRunner commandRunner; + + protected HabitList habitList; + + protected AppComponent appComponent; + + protected ModelFactory modelFactory; + + private ColorPickerDialogFactory colorPickerDialogFactory; + + @Override + public int getTheme() + { + return R.style.DialogWithTitle; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) + { + super.onActivityCreated(savedInstanceState); + + BaseActivity activity = (BaseActivity) getActivity(); + colorPickerDialogFactory = + activity.getComponent().getColorPickerDialogFactory(); + } + + @Override + public View onCreateView(LayoutInflater inflater, + ViewGroup container, + Bundle savedInstanceState) + { + View view = + inflater.inflate(R.layout.edit_numerical_habit, container, false); + + HabitsApplication app = + (HabitsApplication) getContext().getApplicationContext(); + + appComponent = app.getComponent(); + prefs = appComponent.getPreferences(); + habitList = appComponent.getHabitList(); + commandRunner = appComponent.getCommandRunner(); + modelFactory = appComponent.getModelFactory(); + + ButterKnife.bind(this, view); + + helper = new BaseDialogHelper(this, view); + getDialog().setTitle(getTitle()); + initializeHabits(); + restoreSavedInstance(savedInstanceState); + helper.populateForm(modifiedHabit); + return view; + } + + @Override + @SuppressWarnings("ConstantConditions") + public void onSaveInstanceState(Bundle outState) + { + super.onSaveInstanceState(outState); + outState.putInt("color", modifiedHabit.getColor()); + if (modifiedHabit.hasReminder()) + { + Reminder reminder = modifiedHabit.getReminder(); + outState.putInt("reminderMin", reminder.getMinute()); + outState.putInt("reminderHour", reminder.getHour()); + outState.putInt("reminderDays", reminder.getDays().toInteger()); + } + } + + protected abstract int getTitle(); + + protected abstract void initializeHabits(); + + protected void restoreSavedInstance(@Nullable Bundle bundle) + { + if (bundle == null) return; + modifiedHabit.setColor( + bundle.getInt("color", modifiedHabit.getColor())); + + modifiedHabit.setReminder(null); + + int hour = (bundle.getInt("reminderHour", -1)); + int minute = (bundle.getInt("reminderMin", -1)); + int days = (bundle.getInt("reminderDays", -1)); + + if (hour >= 0 && minute >= 0) + { + Reminder reminder = + new Reminder(hour, minute, new WeekdayList(days)); + modifiedHabit.setReminder(reminder); + } + } + + protected abstract void saveHabit(); + + @OnClick(R.id.buttonDiscard) + void onButtonDiscardClick() + { + dismiss(); + } + + @OnClick(R.id.tvReminderTime) + @SuppressWarnings("ConstantConditions") + void onDateSpinnerClick() + { + int defaultHour = 8; + int defaultMin = 0; + + if (modifiedHabit.hasReminder()) + { + Reminder reminder = modifiedHabit.getReminder(); + defaultHour = reminder.getHour(); + defaultMin = reminder.getMinute(); + } + + showTimePicker(defaultHour, defaultMin); + } + + @OnClick(R.id.buttonSave) + void onSaveButtonClick() + { + helper.parseFormIntoHabit(modifiedHabit); + if (!helper.validate(modifiedHabit)) return; + saveHabit(); + dismiss(); + } + + @OnClick(R.id.tvReminderDays) + @SuppressWarnings("ConstantConditions") + void onWeekdayClick() + { + if (!modifiedHabit.hasReminder()) return; + Reminder reminder = modifiedHabit.getReminder(); + + WeekdayPickerDialog dialog = new WeekdayPickerDialog(); + dialog.setListener(new OnWeekdaysPickedListener()); + dialog.setSelectedDays(reminder.getDays().toArray()); + dialog.show(getFragmentManager(), "weekdayPicker"); + } + + @OnClick(R.id.buttonPickColor) + void showColorPicker() + { + int color = modifiedHabit.getColor(); + ColorPickerDialog picker = colorPickerDialogFactory.create(color); + + picker.setListener(c -> + { + prefs.setDefaultHabitColor(c); + modifiedHabit.setColor(c); + helper.populateColor(c); + }); + + picker.show(getFragmentManager(), "picker"); + } + + private void showTimePicker(int defaultHour, int defaultMin) + { + boolean is24HourMode = DateFormat.is24HourFormat(getContext()); + TimePickerDialog timePicker = + TimePickerDialog.newInstance(new OnTimeSetListener(), defaultHour, + defaultMin, is24HourMode); + timePicker.show(getFragmentManager(), "timePicker"); + } + + private class OnTimeSetListener + implements TimePickerDialog.OnTimeSetListener + { + @Override + public void onTimeCleared(RadialPickerLayout view) + { + modifiedHabit.clearReminder(); + helper.populateReminderFields(modifiedHabit); + } + + @Override + public void onTimeSet(RadialPickerLayout view, int hour, int minute) + { + Reminder reminder = + new Reminder(hour, minute, WeekdayList.EVERY_DAY); + modifiedHabit.setReminder(reminder); + helper.populateReminderFields(modifiedHabit); + } + } + + private class OnWeekdaysPickedListener + implements WeekdayPickerDialog.OnWeekdaysPickedListener + { + @Override + public void onWeekdaysPicked(boolean[] selectedDays) + { + if (isSelectionEmpty(selectedDays)) Arrays.fill(selectedDays, true); + + Reminder oldReminder = modifiedHabit.getReminder(); + modifiedHabit.setReminder( + new Reminder(oldReminder.getHour(), oldReminder.getMinute(), + new WeekdayList(selectedDays))); + helper.populateReminderFields(modifiedHabit); + } + + private boolean isSelectionEmpty(boolean[] selectedDays) + { + for (boolean d : selectedDays) if (d) return false; + return true; + } + } +} diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java index 486892cc7..afc3cc94b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.java @@ -82,7 +82,8 @@ public class ListHabitsScreen extends BaseScreen private final ConfirmDeleteDialogFactory confirmDeleteDialogFactory; @NonNull - private final CreateHabitDialogFactory createHabitDialogFactory; + private final CreateBooleanHabitDialogFactory + createBooleanHabitDialogFactory; @NonNull private final FilePickerDialogFactory filePickerDialogFactory; @@ -91,11 +92,13 @@ public class ListHabitsScreen extends BaseScreen private final ColorPickerDialogFactory colorPickerFactory; @NonNull - private final EditHabitDialogFactory editHabitDialogFactory; + private final EditBooleanHabitDialogFactory editBooleanHabitDialogFactory; @NonNull private final ThemeSwitcher themeSwitcher; + private CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory; + @Inject public ListHabitsScreen(@NonNull BaseActivity activity, @NonNull CommandRunner commandRunner, @@ -103,24 +106,21 @@ public class ListHabitsScreen extends BaseScreen @NonNull ListHabitsRootView rootView, @NonNull IntentFactory intentFactory, @NonNull ThemeSwitcher themeSwitcher, - @NonNull - ConfirmDeleteDialogFactory confirmDeleteDialogFactory, - @NonNull - CreateHabitDialogFactory createHabitDialogFactory, - @NonNull - FilePickerDialogFactory filePickerDialogFactory, - @NonNull - ColorPickerDialogFactory colorPickerFactory, - @NonNull - EditHabitDialogFactory editHabitDialogFactory) + @NonNull ConfirmDeleteDialogFactory confirmDeleteDialogFactory, + @NonNull CreateBooleanHabitDialogFactory createBooleanHabitDialogFactory, + @NonNull FilePickerDialogFactory filePickerDialogFactory, + @NonNull ColorPickerDialogFactory colorPickerFactory, + @NonNull EditBooleanHabitDialogFactory editBooleanHabitDialogFactory, + @NonNull CreateNumericalHabitDialogFactory createNumericalHabitDialogFactory) { super(activity); setRootView(rootView); - this.editHabitDialogFactory = editHabitDialogFactory; this.colorPickerFactory = colorPickerFactory; this.commandRunner = commandRunner; this.confirmDeleteDialogFactory = confirmDeleteDialogFactory; - this.createHabitDialogFactory = createHabitDialogFactory; + this.createNumericalHabitDialogFactory = createNumericalHabitDialogFactory; + this.createBooleanHabitDialogFactory = createBooleanHabitDialogFactory; + this.editBooleanHabitDialogFactory = editBooleanHabitDialogFactory; this.dirFinder = dirFinder; this.filePickerDialogFactory = filePickerDialogFactory; this.intentFactory = intentFactory; @@ -182,57 +182,41 @@ public class ListHabitsScreen extends BaseScreen public void showCreateHabitScreen() { - activity.showDialog(createHabitDialogFactory.create(), "editHabit"); + Dialog dialog = new AlertDialog.Builder(activity) + .setTitle("Type of habit") + .setItems(R.array.habitTypes, (d, which) -> { + if(which == 0) showCreateBooleanHabitScreen(); + else showCreateNumericalHabitScreen(); + }) + .create(); + + dialog.show(); } - public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback) + private void showCreateNumericalHabitScreen() { - activity.showDialog(confirmDeleteDialogFactory.create(callback)); + CreateNumericalHabitDialog dialog; + dialog = createNumericalHabitDialogFactory.create(); + activity.showDialog(dialog, "editHabit"); } - public void showEditHabitScreen(Habit habit) + public void showCreateBooleanHabitScreen() { - EditHabitDialog dialog = editHabitDialogFactory.create(habit); + CreateBooleanHabitDialog dialog; + dialog = createBooleanHabitDialogFactory.create(); activity.showDialog(dialog, "editHabit"); } - public void showNumberPicker(int initialValue, - @NonNull NumberPickerCallback callback) + public void showDeleteConfirmationScreen(ConfirmDeleteDialog.Callback callback) { - LayoutInflater inflater = activity.getLayoutInflater(); - View view = inflater.inflate(R.layout.number_picker_dialog, null); - - final NumberPicker picker = - (NumberPicker) view.findViewById(R.id.picker); - - picker.setMinValue(0); - picker.setMaxValue(Integer.MAX_VALUE); - picker.setValue(initialValue); - picker.setWrapSelectorWheel(false); - - AlertDialog dialog = new AlertDialog.Builder(activity) - .setView(view) - .setTitle(R.string.change_value) - .setPositiveButton(android.R.string.ok, (d, which) -> { - picker.clearFocus(); - callback.onNumberPicked(picker.getValue()); - }).create(); - - InterfaceUtils.setupEditorAction(picker, (v, actionId, event) -> { - if (actionId == IME_ACTION_DONE) - dialog.getButton(BUTTON_POSITIVE).performClick(); - return false; - }); - - dialog.show(); + activity.showDialog(confirmDeleteDialogFactory.create(callback)); + } - Window window = dialog.getWindow(); - if (window != null) - { - int width = (int) dpToPixels(activity, 200); - int height = (int) dpToPixels(activity, 275); - window.setLayout(width, height); - } + public void showEditHabitScreen(Habit habit) + { + EditBooleanHabitDialog dialog; + dialog = editBooleanHabitDialogFactory.create(habit); + activity.showDialog(dialog, "editHabit"); } public void showFAQScreen() @@ -288,6 +272,48 @@ public class ListHabitsScreen extends BaseScreen activity.startActivity(intent); } + public void showNumberPicker(int initialValue, + @NonNull NumberPickerCallback callback) + { + LayoutInflater inflater = activity.getLayoutInflater(); + View view = inflater.inflate(R.layout.number_picker_dialog, null); + + final NumberPicker picker = + (NumberPicker) view.findViewById(R.id.picker); + + picker.setMinValue(0); + picker.setMaxValue(Integer.MAX_VALUE); + picker.setValue(initialValue); + picker.setWrapSelectorWheel(false); + + AlertDialog dialog = new AlertDialog.Builder(activity) + .setView(view) + .setTitle(R.string.change_value) + .setPositiveButton(android.R.string.ok, (d, which) -> + { + picker.clearFocus(); + callback.onNumberPicked(picker.getValue()); + }) + .create(); + + InterfaceUtils.setupEditorAction(picker, (v, actionId, event) -> + { + if (actionId == IME_ACTION_DONE) + dialog.getButton(BUTTON_POSITIVE).performClick(); + return false; + }); + + dialog.show(); + + Window window = dialog.getWindow(); + if (window != null) + { + int width = (int) dpToPixels(activity, 200); + int height = (int) dpToPixels(activity, 275); + window.setLayout(width, height); + } + } + public void showSettingsScreen() { Intent intent = intentFactory.startSettingsActivity(activity); diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java index febbfde84..30a3fa10b 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardView.java @@ -106,12 +106,6 @@ public class HabitCardView extends FrameLayout checkmarkPanel.setValues(values); numberPanel.setValues(values); numberPanel.setThreshold(10); - -// int[] magnitudes = new int[]{10, 100, 1000, 10000}; -// int threshold = magnitudes[new Random().nextInt(4)]; -// numberPanel.setThreshold(threshold); -// numberPanel.initEditMode(); - postInvalidate(); } @@ -239,9 +233,9 @@ public class HabitCardView extends FrameLayout checkmarkPanel.setColor(color); numberPanel.setColor(color); - boolean isNumberHabit = true; //(new Random().nextInt(3) == 0); - checkmarkPanel.setVisibility(isNumberHabit ? GONE : VISIBLE); - numberPanel.setVisibility(isNumberHabit ? VISIBLE : GONE); + boolean isNumerical = habit.isNumerical(); + checkmarkPanel.setVisibility(isNumerical ? GONE : VISIBLE); + numberPanel.setVisibility(isNumerical ? VISIBLE : GONE); postInvalidate(); } diff --git a/app/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java b/app/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java index 1c238c02e..9a8d7d27d 100644 --- a/app/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java +++ b/app/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitScreen.java @@ -38,17 +38,18 @@ public class ShowHabitScreen extends BaseScreen private ShowHabitController controller; @NonNull - private final EditHabitDialogFactory editHabitDialogFactory; + private final EditBooleanHabitDialogFactory editBooleanHabitDialogFactory; @Inject public ShowHabitScreen(@NonNull BaseActivity activity, @NonNull Habit habit, @NonNull ShowHabitRootView view, - @NonNull EditHabitDialogFactory editHabitDialogFactory) + @NonNull + EditBooleanHabitDialogFactory editBooleanHabitDialogFactory) { super(activity); setRootView(view); - this.editHabitDialogFactory = editHabitDialogFactory; + this.editBooleanHabitDialogFactory = editBooleanHabitDialogFactory; this.habit = habit; } @@ -71,7 +72,7 @@ public class ShowHabitScreen extends BaseScreen public void showEditHabitDialog() { - EditHabitDialog dialog = editHabitDialogFactory.create(habit); + EditBooleanHabitDialog dialog = editBooleanHabitDialogFactory.create(habit); activity.showDialog(dialog, "editHabit"); } diff --git a/app/src/main/java/org/isoron/uhabits/models/Habit.java b/app/src/main/java/org/isoron/uhabits/models/Habit.java index 5d81ddd64..256e05add 100644 --- a/app/src/main/java/org/isoron/uhabits/models/Habit.java +++ b/app/src/main/java/org/isoron/uhabits/models/Habit.java @@ -36,6 +36,10 @@ public class Habit public static final String HABIT_URI_FORMAT = "content://org.isoron.uhabits/habit/%d"; + public static final int NUMBER_HABIT = 1; + + public static final int YES_NO_HABIT = 0; + @Nullable private Long id; @@ -69,6 +73,8 @@ public class Habit @Nullable private Reminder reminder; + private int type; + private ModelObservable observable = new ModelObservable(); /** @@ -83,6 +89,7 @@ public class Habit this.color = 5; this.archived = false; this.frequency = new Frequency(3, 7); + this.type = YES_NO_HABIT; checkmarks = factory.buildCheckmarkList(this); streaks = factory.buildStreakList(this); @@ -112,6 +119,7 @@ public class Habit this.archived = model.isArchived(); this.frequency = model.frequency; this.reminder = model.reminder; + this.type = model.type; observable.notifyListeners(); } @@ -232,6 +240,19 @@ public class Habit return streaks; } + public int getType() + { + return type; + } + + public void setType(int type) + { + if (type != YES_NO_HABIT && type != NUMBER_HABIT) + throw new IllegalArgumentException(); + + this.type = type; + } + /** * Returns the public URI that identifies this habit * @@ -253,6 +274,13 @@ public class Habit return reminder != null; } + public void invalidateNewerThan(long timestamp) + { + getScores().invalidateNewerThan(timestamp); + getCheckmarks().invalidateNewerThan(timestamp); + getStreaks().invalidateNewerThan(timestamp); + } + public boolean isArchived() { return archived; @@ -263,6 +291,11 @@ public class Habit this.archived = archived; } + public boolean isNumerical() + { + return type == NUMBER_HABIT; + } + @Override public String toString() { @@ -272,13 +305,7 @@ public class Habit .append("description", description) .append("color", color) .append("archived", archived) + .append("type", type) .toString(); } - - public void invalidateNewerThan(long timestamp) - { - getScores().invalidateNewerThan(timestamp); - getCheckmarks().invalidateNewerThan(timestamp); - getStreaks().invalidateNewerThan(timestamp); - } } diff --git a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java index b4120a386..7b2c672b7 100644 --- a/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java +++ b/app/src/main/java/org/isoron/uhabits/models/sqlite/records/HabitRecord.java @@ -22,8 +22,6 @@ package org.isoron.uhabits.models.sqlite.records; import android.annotation.*; import android.database.*; import android.support.annotation.*; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import com.activeandroid.*; import com.activeandroid.annotation.*; @@ -44,7 +42,7 @@ public class HabitRecord extends Model implements SQLiteRecord public static String SELECT = "select id, color, description, freq_den, freq_num, " + "name, position, reminder_hour, reminder_min, " + - "highlight, archived, reminder_days from habits "; + "highlight, archived, reminder_days, type from habits "; @Column(name = "name") public String name; @@ -82,6 +80,9 @@ public class HabitRecord extends Model implements SQLiteRecord @Column(name = "archived") public Integer archived; + @Column(name = "type") + public Integer type; + public HabitRecord() { } @@ -146,6 +147,8 @@ public class HabitRecord extends Model implements SQLiteRecord this.highlight = 0; this.color = model.getColor(); this.archived = model.isArchived() ? 1 : 0; + this.type = model.getType(); + Frequency freq = model.getFrequency(); this.freqNum = freq.getNumerator(); this.freqDen = freq.getDenominator(); @@ -177,6 +180,7 @@ public class HabitRecord extends Model implements SQLiteRecord highlight = c.getInt(9); archived = c.getInt(10); reminderDays = c.getInt(11); + type = c.getInt(12); } public void copyTo(Habit habit) @@ -187,6 +191,7 @@ public class HabitRecord extends Model implements SQLiteRecord habit.setColor(this.color); habit.setArchived(this.archived != 0); habit.setId(this.getId()); + habit.setType(this.type); if (reminderHour != null && reminderMin != null) { diff --git a/app/src/main/res/layout/edit_habit.xml b/app/src/main/res/layout/edit_boolean_habit.xml similarity index 98% rename from app/src/main/res/layout/edit_habit.xml rename to app/src/main/res/layout/edit_boolean_habit.xml index 007fe9500..eda86d33d 100644 --- a/app/src/main/res/layout/edit_habit.xml +++ b/app/src/main/res/layout/edit_boolean_habit.xml @@ -22,7 +22,7 @@ style="@style/dialogForm" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - tools:context=".activities.habits.edit.BaseDialog" + tools:context=".activities.habits.edit.BooleanHabitDialog" tools:ignore="MergeRootFrame"> + ~ + ~ 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 . + --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +