Refactor BarCard

pull/699/head
Alinson S. Xavier 5 years ago
parent 816ab71d83
commit 7d5b9d0f63

@ -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,
),
)
}
}

@ -1,159 +0,0 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<Checkmark> 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);
}
}
}
}

@ -0,0 +1,113 @@
/*
* Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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<Checkmark>,
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,
)
}
}

@ -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<*>?) {
}

@ -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)

@ -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()

@ -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

Loading…
Cancel
Save