diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt index 49fb03e64..5f9b49f65 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitActivity.kt @@ -36,6 +36,8 @@ import org.isoron.uhabits.utils.* import org.isoron.uhabits.widgets.* class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener { + + private lateinit var habit: Habit private lateinit var commandRunner: CommandRunner private lateinit var preferences: Preferences private lateinit var presenter: ShowHabitPresenter @@ -48,7 +50,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener { super.onCreate(savedInstanceState) val appComponent = (applicationContext as HabitsApplication).component val habitList = appComponent.habitList - val habit = habitList.getById(ContentUris.parseId(intent.data!!))!! + habit = habitList.getById(ContentUris.parseId(intent.data!!))!! preferences = appComponent.preferences commandRunner = appComponent.commandRunner widgetUpdater = appComponent.widgetUpdater @@ -61,12 +63,24 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener { preferences = appComponent.preferences ) - view.onBucketSizeSelected = { position -> - preferences.defaultScoreSpinnerPosition = position - widgetUpdater.updateWidgets(habit.id) + view.onScoreCardSpinnerPosition = { position -> + preferences.scoreCardSpinnerPosition = position + updateWidgets() updateViews() } + view.onBarCardBoolSpinnerPosition = { position -> + preferences.barCardBoolSpinnerPosition = position + updateViews() + updateWidgets() + } + + view.onBarCardNumericalSpinnerPosition = { position -> + preferences.barCardNumericalSpinnerPosition = position + updateViews() + updateWidgets() + } + view.onClickEditHistoryButton = { val dialog = HistoryEditorDialog() dialog.setHabit(habit) @@ -78,6 +92,10 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener { setContentView(view) } + private fun updateWidgets() { + widgetUpdater.updateWidgets(habit.id) + } + override fun onResume() { super.onResume() commandRunner.addListener(this) @@ -112,18 +130,23 @@ data class ShowHabitViewModel( val scores: ScoreCardViewModel, val frequency: FrequencyCardViewModel, val history: HistoryCardViewModel, + val bar: BarCardViewModel, ) class ShowHabitView(context: Context) : FrameLayout(context) { private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context)) - var onBucketSizeSelected: (position: Int) -> Unit = {} + var onScoreCardSpinnerPosition: (position: Int) -> Unit = {} var onClickEditHistoryButton: () -> Unit = {} + var onBarCardBoolSpinnerPosition: (position: Int) -> Unit = {} + var onBarCardNumericalSpinnerPosition: (position: Int) -> Unit = {} init { addView(binding.root) - binding.scoreCard.onBucketSizeSelected = { position -> onBucketSizeSelected(position) } + binding.scoreCard.onSpinnerPosition = { onScoreCardSpinnerPosition(it) } binding.historyCard.onClickEditButton = { onClickEditHistoryButton() } + binding.barCard.onBoolSpinnerPosition = { onBarCardBoolSpinnerPosition(it) } + binding.barCard.onNumericalSpinnerPosition = { onBarCardNumericalSpinnerPosition(it) } } fun update(data: ShowHabitViewModel) { @@ -136,6 +159,7 @@ class ShowHabitView(context: Context) : FrameLayout(context) { binding.scoreCard.update(data.scores) binding.frequencyCard.update(data.frequency) binding.historyCard.update(data.history) + binding.barCard.update(data.bar) if (data.isNumerical) { binding.overviewCard.visibility = GONE binding.streakCard.visibility = GONE @@ -164,6 +188,8 @@ class ShowHabitPresenter( private val historyCardViewModel = HistoryCardPresenter(habit = habit, firstWeekday = preferences.firstWeekday, isSkipEnabled = preferences.isSkipEnabled) + private val barCardPresenter = BarCardPresenter(habit = habit, + firstWeekday = preferences.firstWeekday) suspend fun present(): ShowHabitViewModel { return ShowHabitViewModel( @@ -175,9 +201,15 @@ class ShowHabitPresenter( notes = notesCardPresenter.present(), target = targetCardPresenter.present(), streaks = streakCartPresenter.present(), - scores = scoreCardPresenter.present(preferences.defaultScoreSpinnerPosition), + scores = scoreCardPresenter.present( + spinnerPosition = preferences.scoreCardSpinnerPosition + ), frequency = frequencyCardPresenter.present(), history = historyCardViewModel.present(), + bar = barCardPresenter.present( + boolSpinnerPosition = preferences.barCardBoolSpinnerPosition, + numericalSpinnerPosition = preferences.barCardNumericalSpinnerPosition, + ), ) } } \ No newline at end of file diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.java b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.java deleted file mode 100644 index a0bc7e59f..000000000 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * 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.show.views; - -import android.content.*; -import android.util.*; -import android.widget.*; - -import androidx.annotation.Nullable; - -import org.isoron.uhabits.*; -import org.isoron.uhabits.R; -import org.isoron.uhabits.activities.common.views.*; -import org.isoron.uhabits.core.models.*; -import org.isoron.uhabits.core.preferences.*; -import org.isoron.uhabits.core.tasks.*; -import org.isoron.uhabits.utils.*; - -import java.util.*; - -import butterknife.*; - -public class BarCard extends HabitCard -{ - public static final int[] NUMERICAL_BUCKET_SIZES = {1, 7, 31, 92, 365}; - public static final int[] BOOLEAN_BUCKET_SIZES = {7, 31, 92, 365}; - - @BindView(R.id.numericalSpinner) - Spinner numericalSpinner; - - @BindView(R.id.boolSpinner) - Spinner boolSpinner; - - @BindView(R.id.barChart) - BarChart chart; - - @BindView(R.id.title) - TextView title; - - private int bucketSize; - - @Nullable - private Preferences prefs; - - public BarCard(Context context) - { - super(context); - init(); - } - - public BarCard(Context context, AttributeSet attrs) - { - super(context, attrs); - init(); - } - - @OnItemSelected(R.id.numericalSpinner) - public void onNumericalItemSelected(int position) - { - bucketSize = NUMERICAL_BUCKET_SIZES[position]; - refreshData(); - } - - @OnItemSelected(R.id.boolSpinner) - public void onBoolItemSelected(int position) - { - bucketSize = BOOLEAN_BUCKET_SIZES[position]; - refreshData(); - } - - private void init() - { - Context appContext = getContext().getApplicationContext(); - if (appContext instanceof HabitsApplication) - { - HabitsApplication app = (HabitsApplication) appContext; - prefs = app.getComponent().getPreferences(); - } - inflate(getContext(), R.layout.show_habit_bar, this); - ButterKnife.bind(this); - boolSpinner.setSelection(1); - numericalSpinner.setSelection(2); - bucketSize = 7; - if (isInEditMode()) initEditMode(); - } - - private void initEditMode() - { - int color = PaletteUtils.getAndroidTestColor(1); - title.setTextColor(color); - chart.setColor(color); - chart.populateWithRandomData(); - } - - @Override - protected Task createRefreshTask() - { - return new RefreshTask(getHabit()); - } - - private class RefreshTask extends CancelableTask - { - private final Habit habit; - - RefreshTask(Habit habit) - { - this.habit = habit; - } - - @Override - public void doInBackground() - { - if (isCanceled()) return; - List checkmarks; - int firstWeekday = Calendar.SATURDAY; - if (prefs != null) firstWeekday = prefs.getFirstWeekday(); - if (bucketSize == 1) checkmarks = habit.getCheckmarks().getAll(); - else checkmarks = habit.getCheckmarks().groupBy(ScoreCardPresenter.Companion.getTruncateField(bucketSize), - firstWeekday); - chart.setCheckmarks(checkmarks); - chart.setBucketSize(bucketSize); - } - - @Override - public void onPreExecute() - { - int color = PaletteUtilsKt.toThemedAndroidColor(habit.getColor(), getContext()); - title.setTextColor(color); - chart.setColor(color); - if (habit.isNumerical()) - { - boolSpinner.setVisibility(GONE); - chart.setTarget(habit.getTargetValue() / habit.getFrequency().getDenominator() * bucketSize); - } - else - { - numericalSpinner.setVisibility(GONE); - chart.setTarget(0); - } - } - } -} diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.kt new file mode 100644 index 000000000..3f42708f6 --- /dev/null +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/BarCard.kt @@ -0,0 +1,113 @@ +/* + * 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.show.views + +import android.content.* +import android.util.* +import android.view.* +import android.widget.* +import org.isoron.uhabits.activities.habits.show.views.ScoreCardPresenter.Companion.getTruncateField +import org.isoron.uhabits.core.models.* +import org.isoron.uhabits.databinding.* +import org.isoron.uhabits.utils.* + +data class BarCardViewModel( + val checkmarks: List, + val bucketSize: Int, + val color: PaletteColor, + val isNumerical: Boolean, + val target: Double, + val numericalSpinnerPosition: Int, + val boolSpinnerPosition: Int, +) + +class BarCard(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { + + private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this) + var onNumericalSpinnerPosition: (position: Int) -> Unit = {} + var onBoolSpinnerPosition: (position: Int) -> Unit = {} + + fun update(data: BarCardViewModel) { + binding.barChart.setCheckmarks(data.checkmarks) + binding.barChart.setBucketSize(data.bucketSize) + val androidColor = data.color.toThemedAndroidColor(context) + binding.title.setTextColor(androidColor) + binding.barChart.setColor(androidColor) + if (data.isNumerical) { + binding.boolSpinner.visibility = GONE + binding.barChart.setTarget(data.target) + } else { + binding.numericalSpinner.visibility = GONE + binding.barChart.setTarget(0.0) + } + + binding.numericalSpinner.onItemSelectedListener = null + binding.numericalSpinner.setSelection(data.numericalSpinnerPosition) + binding.numericalSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + onNumericalSpinnerPosition(position) + } + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + + binding.boolSpinner.onItemSelectedListener = null + binding.boolSpinner.setSelection(data.boolSpinnerPosition) + binding.boolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + onBoolSpinnerPosition(position) + } + override fun onNothingSelected(parent: AdapterView<*>?) { + } + } + } +} + +class BarCardPresenter( + val habit: Habit, + val firstWeekday: Int, +) { + val numericalBucketSizes = intArrayOf(1, 7, 31, 92, 365) + val boolBucketSizes = intArrayOf(7, 31, 92, 365) + + fun present( + numericalSpinnerPosition: Int, + boolSpinnerPosition: Int, + ): BarCardViewModel { + val bucketSize = if(habit.isNumerical) { + numericalBucketSizes[numericalSpinnerPosition] + } else { + boolBucketSizes[boolSpinnerPosition] + } + val checkmarks = if (bucketSize == 1) { + habit.checkmarks.all + } else { + habit.checkmarks.groupBy(getTruncateField(bucketSize), firstWeekday) + } + return BarCardViewModel( + checkmarks = checkmarks, + bucketSize = bucketSize, + color = habit.color, + isNumerical = habit.isNumerical, + target = (habit.targetValue / habit.frequency.denominator * bucketSize), + numericalSpinnerPosition = numericalSpinnerPosition, + boolSpinnerPosition = boolSpinnerPosition, + ) + } +} \ No newline at end of file diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt index 99256b565..406deb8f1 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/views/ScoreCard.kt @@ -38,7 +38,7 @@ data class ScoreCardViewModel( class ScoreCard(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this) - var onBucketSizeSelected: (position: Int) -> Unit = {} + var onSpinnerPosition: (position: Int) -> Unit = {} fun update(data: ScoreCardViewModel) { val androidColor = data.color.toThemedAndroidColor(context) @@ -51,7 +51,7 @@ class ScoreCard(context: Context, attrs: AttributeSet) : LinearLayout(context, a binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { - onBucketSizeSelected(position) + onSpinnerPosition(position) } override fun onNothingSelected(parent: AdapterView<*>?) { } diff --git a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt index 9f0f73aa8..3c73f3f13 100644 --- a/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt +++ b/android/uhabits-android/src/main/java/org/isoron/uhabits/widgets/ScoreWidget.kt @@ -37,7 +37,7 @@ class ScoreWidget( pendingIntentFactory.showHabit(habit) override fun refreshData(view: View) { - val size = ScoreCardPresenter.BUCKET_SIZES[prefs.defaultScoreSpinnerPosition] + val size = ScoreCardPresenter.BUCKET_SIZES[prefs.scoreCardSpinnerPosition] val scores = when(size) { 1 -> habit.scores.toList() else -> habit.scores.groupBy(ScoreCardPresenter.getTruncateField(size), prefs.firstWeekday) diff --git a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java index 3f3688e06..a7901871d 100644 --- a/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java +++ b/android/uhabits-core/src/main/java/org/isoron/uhabits/core/preferences/Preferences.java @@ -37,7 +37,6 @@ public class Preferences @Nullable private Boolean shouldReverseCheckmarks = null; - public Preferences(@NonNull Storage storage) { this.storage = storage; @@ -95,23 +94,34 @@ public class Preferences storage.putString("pref_default_secondary_order", order.name()); } - public int getDefaultScoreSpinnerPosition() + public int getScoreCardSpinnerPosition() { - int defaultScoreInterval = - storage.getInt("pref_score_view_interval", 1); + return Math.min(4, Math.max(0, storage.getInt("pref_score_view_interval", 1))); + } - if (defaultScoreInterval > 5 || defaultScoreInterval < 0) - { - defaultScoreInterval = 1; - storage.putInt("pref_score_view_interval", 1); - } + public void setScoreCardSpinnerPosition(int position) + { + storage.putInt("pref_score_view_interval", position); + } - return defaultScoreInterval; + public int getBarCardBoolSpinnerPosition() + { + return Math.min(3, Math.max(0, storage.getInt("pref_bar_card_bool_spinner", 0))); } - public void setDefaultScoreSpinnerPosition(int position) + public void setBarCardBoolSpinnerPosition(int position) { - storage.putInt("pref_score_view_interval", position); + storage.putInt("pref_bar_card_bool_spinner", position); + } + + public int getBarCardNumericalSpinnerPosition() + { + return Math.min(4, Math.max(0, storage.getInt("pref_bar_card_numerical_spinner", 0))); + } + + public void setBarCardNumericalSpinnerPosition(int position) + { + storage.putInt("pref_bar_card_numerical_spinner", position); } public int getLastHintNumber() diff --git a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/preferences/PreferencesTest.java b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/preferences/PreferencesTest.java index a58abd868..b571c3573 100644 --- a/android/uhabits-core/src/test/java/org/isoron/uhabits/core/preferences/PreferencesTest.java +++ b/android/uhabits-core/src/test/java/org/isoron/uhabits/core/preferences/PreferencesTest.java @@ -32,7 +32,6 @@ import java.io.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsEqual.equalTo; import static org.junit.Assert.*; -import static org.mockito.Mockito.*; public class PreferencesTest extends BaseUnitTest { @@ -86,16 +85,15 @@ public class PreferencesTest extends BaseUnitTest } @Test - public void testDefaultSpinnerPosition() throws Exception + public void testScoreCardSpinnerPosition() throws Exception { - assertThat(prefs.getDefaultScoreSpinnerPosition(), equalTo(1)); + assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(1)); - prefs.setDefaultScoreSpinnerPosition(4); - assertThat(prefs.getDefaultScoreSpinnerPosition(), equalTo(4)); + prefs.setScoreCardSpinnerPosition(4); + assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(4)); storage.putInt("pref_score_view_interval", 9000); - assertThat(prefs.getDefaultScoreSpinnerPosition(), equalTo(1)); - assertThat(storage.getInt("pref_score_view_interval", 0), equalTo(1)); + assertThat(prefs.getScoreCardSpinnerPosition(), equalTo(4)); } @Test