Remove ShowHabitScreen and ShowHabitBehavior

pull/707/head
Alinson S. Xavier 5 years ago
parent 162eac3bdf
commit cb6843e08b

@ -39,8 +39,6 @@ public class ScoreChartTest extends BaseViewTest
private ScoreChart view;
private ScoreCardPresenter presenter;
@Override
@Before
public void setUp()
@ -49,13 +47,12 @@ public class ScoreChartTest extends BaseViewTest
fixtures.purgeHabits(habitList);
habit = fixtures.createLongHabit();
presenter = new ScoreCardPresenter();
ScoreCardViewModel model = presenter.present(habit, prefs.getFirstWeekdayInt(), 0);
ScoreCardState state = ScoreCardPresenter.Companion.buildState(habit, prefs.getFirstWeekdayInt(), 0);
view = new ScoreChart(targetContext);
view.setScores(model.getScores());
view.setColor(PaletteUtilsKt.toFixedAndroidColor(model.getColor()));
view.setBucketSize(model.getBucketSize());
view.setScores(state.getScores());
view.setColor(PaletteUtilsKt.toFixedAndroidColor(state.getColor()));
view.setBucketSize(state.getBucketSize());
measureView(view, dpToPixels(300), dpToPixels(200));
}
@ -84,7 +81,7 @@ public class ScoreChartTest extends BaseViewTest
@Test
public void testRender_withMonthlyBucket() throws Throwable
{
ScoreCardViewModel model = presenter.present(habit, prefs.getFirstWeekdayInt(), 2);
ScoreCardState model = ScoreCardPresenter.Companion.buildState(habit, prefs.getFirstWeekdayInt(), 2);
view.setScores(model.getScores());
view.setBucketSize(model.getBucketSize());
view.invalidate();
@ -102,7 +99,7 @@ public class ScoreChartTest extends BaseViewTest
@Test
public void testRender_withYearlyBucket() throws Throwable
{
ScoreCardViewModel model = presenter.present(habit, prefs.getFirstWeekdayInt(), 4);
ScoreCardState model = ScoreCardPresenter.Companion.buildState(habit, prefs.getFirstWeekdayInt(), 4);
view.setScores(model.getScores());
view.setBucketSize(model.getBucketSize());
view.invalidate();

@ -43,7 +43,7 @@ class FrequencyCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.frequencyCard) as FrequencyCardView
view.update(FrequencyCardPresenter().present(habit = habit, firstWeekday = 0))
view.setState(FrequencyCardPresenter.buildState(habit = habit, firstWeekday = 0))
measureView(view, 800f, 600f)
}

@ -45,11 +45,10 @@ class HistoryCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.historyCard) as HistoryCardView
view.update(
HistoryCardPresenter().present(
view.setState(
HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = SUNDAY,
isSkipEnabled = false,
theme = LightTheme(),
)
)

@ -25,7 +25,7 @@ import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardState
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Test
@ -44,7 +44,7 @@ class NotesCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.notesCard)
view.update(NotesCardViewModel(description = "This is a test description"))
view.setState(NotesCardState(description = "This is a test description"))
measureView(view, 800f, 200f)
}
@ -55,7 +55,7 @@ class NotesCardViewTest : BaseViewTest() {
@Test
fun testRenderEmptyDescription() {
view.update(NotesCardViewModel(description = ""))
view.setState(NotesCardState(description = ""))
assertThat(view.visibility, equalTo(GONE))
}
}

@ -25,7 +25,7 @@ import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@ -43,8 +43,8 @@ class OverviewCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.overviewCard) as OverviewCardView
view.update(
OverviewCardViewModel(
view.setState(
OverviewCardState(
scoreToday = 0.74f,
scoreMonthDiff = 0.23f,
scoreYearDiff = 0.74f,

@ -43,8 +43,8 @@ class ScoreCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.scoreCard) as ScoreCardView
view.update(
ScoreCardPresenter().present(
view.setState(
ScoreCardPresenter.buildState(
habit = habit,
firstWeekday = 0,
spinnerPosition = 0,

@ -24,7 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@ -43,8 +43,8 @@ class StreakCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.streakCard) as StreakCardView
view.update(
StreakCardViewModel(
view.setState(
StreakCardState(
bestStreaks = habit.streaks.getBest(10),
color = habit.color,
)

@ -27,7 +27,7 @@ import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList.EVERY_DAY
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@ -45,8 +45,8 @@ class SubtitleCardViewTest : BaseViewTest() {
.from(targetContext)
.inflate(R.layout.show_habit, null)
.findViewById(R.id.subtitleCard)
view.update(
SubtitleCardViewModel(
view.setState(
SubtitleCardState(
color = PaletteColor(7),
frequency = Frequency(3, 7),
isNumerical = false,

@ -20,14 +20,14 @@ package org.isoron.uhabits.activities
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
import java.io.File
import javax.inject.Inject
class HabitsDirFinder @Inject
constructor(
private val androidDirFinder: AndroidDirFinder
) : ShowHabitMenuBehavior.System, ListHabitsBehavior.DirFinder {
) : ShowHabitMenuPresenter.System, ListHabitsBehavior.DirFinder {
override fun getCSVOutputDir(): File {
return androidDirFinder.getFilesDir("CSV")!!

@ -37,7 +37,7 @@ import org.isoron.uhabits.inject.*;
@AutoFactory(allowSubclasses = true)
public class ConfirmDeleteDialog extends AlertDialog
{
protected ConfirmDeleteDialog(@Provided @ActivityContext Context context,
public ConfirmDeleteDialog(@Provided @ActivityContext Context context,
@NonNull OnConfirmedCallback callback,
int quantity)
{

@ -91,10 +91,9 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
}
private fun refreshData() {
val model = HistoryCardPresenter().present(
val model = HistoryCardPresenter.buildState(
habit,
preferences.firstWeekday,
preferences.isSkipEnabled,
theme = LightTheme()
)
chart?.series = model.series

@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.show
import android.content.ContentUris
import android.os.Bundle
import android.view.HapticFeedbackConstants
import android.view.Menu
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
@ -28,33 +29,39 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater
class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
val presenter = ShowHabitPresenter()
private lateinit var commandRunner: CommandRunner
private lateinit var menu: ShowHabitMenu
private lateinit var view: ShowHabitView
private lateinit var habit: Habit
private lateinit var preferences: Preferences
private lateinit var themeSwitcher: AndroidThemeSwitcher
private lateinit var behavior: ShowHabitBehavior
private lateinit var widgetUpdater: WidgetUpdater
private val scope = CoroutineScope(Dispatchers.Main)
private lateinit var presenter: ShowHabitPresenter
private val screen = Screen()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -64,21 +71,12 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
preferences = appComponent.preferences
commandRunner = appComponent.commandRunner
widgetUpdater = appComponent.widgetUpdater
themeSwitcher = AndroidThemeSwitcher(this, preferences)
themeSwitcher.apply()
view = ShowHabitView(this)
val screen = ShowHabitScreen(
activity = this,
confirmDeleteDialogFactory = ConfirmDeleteDialogFactory { this },
habit = habit,
intentFactory = IntentFactory(),
numberPickerFactory = NumberPickerFactory(this),
widgetUpdater = appComponent.widgetUpdater,
)
behavior = ShowHabitBehavior(
presenter = ShowHabitPresenter(
commandRunner = commandRunner,
habit = habit,
habitList = habitList,
@ -86,7 +84,9 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
screen = screen,
)
val menuBehavior = ShowHabitMenuBehavior(
view = ShowHabitView(this)
val menuPresenter = ShowHabitMenuPresenter(
commandRunner = commandRunner,
habit = habit,
habitList = habitList,
@ -97,15 +97,11 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
menu = ShowHabitMenu(
activity = this,
behavior = menuBehavior,
presenter = menuPresenter,
preferences = preferences,
)
view.onScoreCardSpinnerPosition = behavior::onScoreCardSpinnerPosition
view.onBarCardBoolSpinnerPosition = behavior::onBarCardBoolSpinnerPosition
view.onBarCardNumericalSpinnerPosition = behavior::onBarCardNumericalSpinnerPosition
view.onClickEditHistoryButton = behavior::onClickEditHistory
view.setListener(presenter)
setContentView(view)
}
@ -121,9 +117,9 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
super.onResume()
commandRunner.addListener(this)
supportFragmentManager.findFragmentByTag("historyEditor")?.let {
(it as HistoryEditorDialog).setOnDateClickedListener(behavior)
(it as HistoryEditorDialog).setOnDateClickedListener(presenter.historyCardPresenter)
}
refresh()
screen.refresh()
}
override fun onPause() {
@ -132,13 +128,18 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
}
override fun onCommandFinished(command: Command) {
refresh()
screen.refresh()
}
inner class Screen : ShowHabitMenuPresenter.Screen, ShowHabitPresenter.Screen {
override fun updateWidgets() {
widgetUpdater.updateWidgets()
}
fun refresh() {
override fun refresh() {
scope.launch {
view.update(
presenter.present(
view.setState(
ShowHabitPresenter.buildState(
habit = habit,
preferences = preferences,
theme = themeSwitcher.currentTheme,
@ -146,4 +147,50 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
)
}
}
override fun showHistoryEditorDialog(listener: OnDateClickedListener) {
val dialog = HistoryEditorDialog()
dialog.arguments = Bundle().apply {
putLong("habit", habit.id!!)
}
dialog.setOnDateClickedListener(listener)
dialog.show(supportFragmentManager, "historyEditor")
}
override fun showFeedback() {
window.decorView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
}
override fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback,
) {
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, callback).show()
}
override fun showEditHabitScreen(habit: Habit) {
startActivity(IntentFactory().startEditActivity(this@ShowHabitActivity, habit))
}
override fun showMessage(m: ShowHabitMenuPresenter.Message?) {
when (m) {
ShowHabitMenuPresenter.Message.COULD_NOT_EXPORT -> {
showMessage(resources.getString(R.string.could_not_export))
}
}
}
override fun showSendFileScreen(filename: String) {
this@ShowHabitActivity.showSendFileScreen(filename)
}
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
ConfirmDeleteDialog(this@ShowHabitActivity, callback, 1).show()
}
override fun close() {
this@ShowHabitActivity.finish()
}
}
}

@ -23,11 +23,11 @@ import android.view.Menu
import android.view.MenuItem
import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
class ShowHabitMenu(
val activity: ShowHabitActivity,
val behavior: ShowHabitMenuBehavior,
val presenter: ShowHabitMenuPresenter,
val preferences: Preferences,
) {
fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -41,19 +41,19 @@ class ShowHabitMenu(
fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.action_edit_habit -> {
behavior.onEditHabit()
presenter.onEditHabit()
return true
}
R.id.action_delete -> {
behavior.onDeleteHabit()
presenter.onDeleteHabit()
return true
}
R.id.action_randomize -> {
behavior.onRandomize()
presenter.onRandomize()
return true
}
R.id.export -> {
behavior.onExportCSV()
presenter.onExportCSV()
return true
}
}

@ -1,100 +0,0 @@
/*
* Copyright (C) 2016-2020 Álinson Santos Xavier <isoron@gmail.com>
*
* This file is part of Loop Habit Tracker.
*
* Loop Habit Tracker is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* Loop Habit Tracker is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.isoron.uhabits.activities.habits.show
import android.os.Bundle
import android.view.HapticFeedbackConstants
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialogFactory
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater
class ShowHabitScreen(
val activity: ShowHabitActivity,
val confirmDeleteDialogFactory: ConfirmDeleteDialogFactory,
val habit: Habit,
val intentFactory: IntentFactory,
val numberPickerFactory: NumberPickerFactory,
val widgetUpdater: WidgetUpdater,
) : ShowHabitBehavior.Screen, ShowHabitMenuBehavior.Screen {
override fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback,
) {
numberPickerFactory.create(value, unit, callback).show()
}
override fun updateWidgets() {
widgetUpdater.updateWidgets(habit.id)
}
override fun refresh() {
activity.refresh()
}
override fun showHistoryEditorDialog(listener: OnDateClickedListener) {
val dialog = HistoryEditorDialog()
dialog.arguments = Bundle().apply {
putLong("habit", habit.id!!)
}
dialog.setOnDateClickedListener(listener)
dialog.show(activity.supportFragmentManager, "historyEditor")
}
override fun touchFeedback() {
activity.window.decorView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
}
override fun showEditHabitScreen(habit: Habit) {
activity.startActivity(intentFactory.startEditActivity(activity, habit))
}
override fun showMessage(m: ShowHabitMenuBehavior.Message?) {
when (m) {
ShowHabitMenuBehavior.Message.COULD_NOT_EXPORT -> {
activity.showMessage(activity.resources.getString(R.string.could_not_export))
}
}
}
override fun showSendFileScreen(filename: String) {
activity.showSendFileScreen(filename)
}
override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
confirmDeleteDialogFactory.create(callback, 1).show()
}
override fun close() {
activity.finish()
}
}

@ -22,37 +22,29 @@ package org.isoron.uhabits.activities.habits.show
import android.content.Context
import android.view.LayoutInflater
import android.widget.FrameLayout
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitState
import org.isoron.uhabits.databinding.ShowHabitBinding
import org.isoron.uhabits.utils.setupToolbar
class ShowHabitView(context: Context) : FrameLayout(context) {
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(context))
var onScoreCardSpinnerPosition: (position: Int) -> Unit = {}
var onClickEditHistoryButton: () -> Unit = {}
var onBarCardBoolSpinnerPosition: (position: Int) -> Unit = {}
var onBarCardNumericalSpinnerPosition: (position: Int) -> Unit = {}
init {
addView(binding.root)
binding.scoreCard.onSpinnerPosition = { onScoreCardSpinnerPosition(it) }
binding.historyCard.onClickEditButton = { onClickEditHistoryButton() }
binding.barCard.onBoolSpinnerPosition = { onBarCardBoolSpinnerPosition(it) }
binding.barCard.onNumericalSpinnerPosition = { onBarCardNumericalSpinnerPosition(it) }
}
fun update(data: ShowHabitViewModel) {
fun setState(data: ShowHabitState) {
setupToolbar(binding.toolbar, title = data.title, color = data.color)
binding.subtitleCard.update(data.subtitle)
binding.overviewCard.update(data.overview)
binding.notesCard.update(data.notes)
binding.targetCard.update(data.target)
binding.streakCard.update(data.streaks)
binding.scoreCard.update(data.scores)
binding.frequencyCard.update(data.frequency)
binding.historyCard.update(data.history)
binding.barCard.update(data.bar)
binding.subtitleCard.setState(data.subtitle)
binding.overviewCard.setState(data.overview)
binding.notesCard.setState(data.notes)
binding.targetCard.setState(data.target)
binding.streakCard.setState(data.streaks)
binding.scoreCard.setState(data.scores)
binding.frequencyCard.setState(data.frequency)
binding.historyCard.setState(data.history)
binding.barCard.setState(data.bar)
if (data.isNumerical) {
binding.overviewCard.visibility = GONE
binding.streakCard.visibility = GONE
@ -60,4 +52,10 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
binding.targetCard.visibility = GONE
}
}
fun setListener(presenter: ShowHabitPresenter) {
binding.scoreCard.setListener(presenter.scoreCardPresenter)
binding.historyCard.setListener(presenter.historyCardPresenter)
binding.barCard.setListener(presenter.barCardPresenter)
}
}

@ -25,7 +25,8 @@ import android.view.View
import android.widget.AdapterView
import android.widget.LinearLayout
import org.isoron.platform.time.JavaLocalDateFormatter
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
import org.isoron.uhabits.core.ui.views.BarChart
import org.isoron.uhabits.databinding.ShowHabitBarBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
@ -34,53 +35,52 @@ import java.util.Locale
class BarCardView(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) {
val androidColor = data.color.toThemedAndroidColor(context)
binding.chart.view = BarChart(data.theme, JavaLocalDateFormatter(Locale.US)).apply {
series = mutableListOf(data.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(data.color.paletteIndex))
axis = data.entries.map { it.timestamp.toLocalDate() }
fun setState(state: BarCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
series = mutableListOf(state.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(state.color.paletteIndex))
axis = state.entries.map { it.timestamp.toLocalDate() }
}
binding.chart.resetDataOffset()
binding.chart.postInvalidate()
binding.title.setTextColor(androidColor)
if (data.isNumerical) {
if (state.isNumerical) {
binding.boolSpinner.visibility = GONE
} else {
binding.numericalSpinner.visibility = GONE
}
binding.numericalSpinner.onItemSelectedListener = null
binding.numericalSpinner.setSelection(data.numericalSpinnerPosition)
binding.numericalSpinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
binding.numericalSpinner.setSelection(state.numericalSpinnerPosition)
binding.boolSpinner.setSelection(state.boolSpinnerPosition)
}
fun setListener(presenter: BarCardPresenter) {
binding.boolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
onNumericalSpinnerPosition(position)
presenter.onBoolSpinnerPosition(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
binding.boolSpinner.onItemSelectedListener = null
binding.boolSpinner.setSelection(data.boolSpinnerPosition)
binding.boolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
binding.numericalSpinner.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
id: Long,
) {
onBoolSpinnerPosition(position)
presenter.onNumericalSpinnerPosition(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {

@ -22,7 +22,7 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
import org.isoron.uhabits.databinding.ShowHabitFrequencyBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
@ -30,10 +30,10 @@ class FrequencyCardView(context: Context, attrs: AttributeSet) : LinearLayout(co
private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this)
fun update(data: FrequencyCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
binding.frequencyChart.setFrequency(data.frequency)
binding.frequencyChart.setFirstWeekday(data.firstWeekday)
fun setState(state: FrequencyCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
binding.frequencyChart.setFrequency(state.frequency)
binding.frequencyChart.setFirstWeekday(state.firstWeekday)
binding.title.setTextColor(androidColor)
binding.frequencyChart.setColor(androidColor)
}

@ -23,7 +23,8 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.platform.time.JavaLocalDateFormatter
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState
import org.isoron.uhabits.core.ui.views.HistoryChart
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
@ -33,23 +34,21 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this)
var onClickEditButton: () -> Unit = {}
init {
binding.edit.setOnClickListener { onClickEditButton() }
}
fun update(data: HistoryCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
fun setState(state: HistoryCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
binding.title.setTextColor(androidColor)
binding.chart.view = HistoryChart(
today = data.today,
paletteColor = data.color,
theme = data.theme,
today = state.today,
paletteColor = state.color,
theme = state.theme,
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
series = data.series,
firstWeekday = data.firstWeekday,
series = state.series,
firstWeekday = state.firstWeekday,
)
binding.chart.postInvalidate()
}
fun setListener(presenter: HistoryCardPresenter) {
binding.edit.setOnClickListener { presenter.onClickEditButton() }
}
}

@ -23,17 +23,17 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardState
import org.isoron.uhabits.databinding.ShowHabitNotesBinding
class NotesCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitNotesBinding.inflate(LayoutInflater.from(context), this)
fun update(data: NotesCardViewModel) {
if (data.description.isEmpty()) {
fun setState(state: NotesCardState) {
if (state.description.isEmpty()) {
visibility = GONE
} else {
visibility = VISIBLE
binding.habitNotes.text = data.description
binding.habitNotes.text = state.description
}
invalidate()
}

@ -23,7 +23,7 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
import org.isoron.uhabits.databinding.ShowHabitOverviewBinding
import org.isoron.uhabits.utils.StyledResources
import org.isoron.uhabits.utils.toThemedAndroidColor
@ -40,21 +40,21 @@ class OverviewCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
)
}
fun update(data: OverviewCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
fun setState(state: OverviewCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
val res = StyledResources(context)
val inactiveColor = res.getColor(R.attr.mediumContrastTextColor)
binding.monthDiffLabel.setTextColor(if (data.scoreMonthDiff >= 0) androidColor else inactiveColor)
binding.monthDiffLabel.text = formatPercentageDiff(data.scoreMonthDiff)
binding.monthDiffLabel.setTextColor(if (state.scoreMonthDiff >= 0) androidColor else inactiveColor)
binding.monthDiffLabel.text = formatPercentageDiff(state.scoreMonthDiff)
binding.scoreLabel.setTextColor(androidColor)
binding.scoreLabel.text = String.format("%.0f%%", data.scoreToday * 100)
binding.scoreLabel.text = String.format("%.0f%%", state.scoreToday * 100)
binding.scoreRing.color = androidColor
binding.scoreRing.percentage = data.scoreToday
binding.scoreRing.percentage = state.scoreToday
binding.title.setTextColor(androidColor)
binding.totalCountLabel.setTextColor(androidColor)
binding.totalCountLabel.text = data.totalCount.toString()
binding.yearDiffLabel.setTextColor(if (data.scoreYearDiff >= 0) androidColor else inactiveColor)
binding.yearDiffLabel.text = formatPercentageDiff(data.scoreYearDiff)
binding.totalCountLabel.text = state.totalCount.toString()
binding.yearDiffLabel.setTextColor(if (state.scoreYearDiff >= 0) androidColor else inactiveColor)
binding.yearDiffLabel.text = formatPercentageDiff(state.scoreYearDiff)
postInvalidate()
}
}

@ -24,24 +24,25 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.AdapterView
import android.widget.LinearLayout
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
import org.isoron.uhabits.databinding.ShowHabitScoreBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var binding = ShowHabitScoreBinding.inflate(LayoutInflater.from(context), this)
var onSpinnerPosition: (position: Int) -> Unit = {}
fun update(data: ScoreCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
fun setState(state: ScoreCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
binding.title.setTextColor(androidColor)
binding.spinner.setSelection(data.spinnerPosition)
binding.scoreView.setScores(data.scores)
binding.spinner.setSelection(state.spinnerPosition)
binding.scoreView.setScores(state.scores)
binding.scoreView.reset()
binding.scoreView.setBucketSize(data.bucketSize)
binding.scoreView.setBucketSize(state.bucketSize)
binding.scoreView.setColor(androidColor)
}
fun setListener(presenter: ScoreCardPresenter) {
binding.spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
@ -49,7 +50,7 @@ class ScoreCardView(context: Context, attrs: AttributeSet) : LinearLayout(contex
position: Int,
id: Long
) {
onSpinnerPosition(position)
presenter.onSpinnerPosition(position)
}
override fun onNothingSelected(parent: AdapterView<*>?) {

@ -22,17 +22,17 @@ import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
import org.isoron.uhabits.databinding.ShowHabitStreakBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class StreakCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitStreakBinding.inflate(LayoutInflater.from(context), this)
fun update(data: StreakCardViewModel) {
val color = data.color.toThemedAndroidColor(context)
fun setState(state: StreakCardState) {
val color = state.color.toThemedAndroidColor(context)
binding.title.setTextColor(color)
binding.streakChart.setColor(color)
binding.streakChart.setStreaks(data.bestStreaks)
binding.streakChart.setStreaks(state.bestStreaks)
postInvalidate()
}
}

@ -28,7 +28,7 @@ import android.widget.LinearLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.habits.list.views.toShortString
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.isoron.uhabits.databinding.ShowHabitSubtitleBinding
import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.formatTime
@ -47,27 +47,27 @@ class SubtitleCardView(context: Context, attrs: AttributeSet) : LinearLayout(con
}
@SuppressLint("SetTextI18n")
fun update(data: SubtitleCardViewModel) {
val color = data.color.toThemedAndroidColor(context)
val reminder = data.reminder
binding.frequencyLabel.text = data.frequency.format(resources)
fun setState(state: SubtitleCardState) {
val color = state.color.toThemedAndroidColor(context)
val reminder = state.reminder
binding.frequencyLabel.text = state.frequency.format(resources)
binding.questionLabel.setTextColor(color)
binding.questionLabel.text = data.question
binding.questionLabel.text = state.question
binding.reminderLabel.text = if (reminder != null) {
formatTime(context, reminder.hour, reminder.minute)
} else {
resources.getString(R.string.reminder_off)
}
binding.targetText.text = "${data.targetValue.toShortString()} ${data.unit}"
binding.targetText.text = "${state.targetValue.toShortString()} ${state.unit}"
binding.questionLabel.visibility = View.VISIBLE
binding.targetIcon.visibility = View.VISIBLE
binding.targetText.visibility = View.VISIBLE
if (!data.isNumerical) {
if (!state.isNumerical) {
binding.targetIcon.visibility = View.GONE
binding.targetText.visibility = View.GONE
}
if (data.question.isEmpty()) {
if (state.question.isEmpty()) {
binding.questionLabel.visibility = View.GONE
}

@ -24,17 +24,17 @@ import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.LinearLayout
import org.isoron.uhabits.R
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
import org.isoron.uhabits.databinding.ShowHabitTargetBinding
import org.isoron.uhabits.utils.toThemedAndroidColor
class TargetCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitTargetBinding.inflate(LayoutInflater.from(context), this)
fun update(data: TargetCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
binding.targetChart.setValues(data.values)
binding.targetChart.setTargets(data.targets)
binding.targetChart.setLabels(data.intervals.map { intervalToLabel(resources, it) })
fun setState(state: TargetCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
binding.targetChart.setValues(state.values)
binding.targetChart.setTargets(state.targets)
binding.targetChart.setLabels(state.intervals.map { intervalToLabel(resources, it) })
binding.title.setTextColor(androidColor)
binding.targetChart.setColor(androidColor)
postInvalidate()

@ -46,9 +46,8 @@ class HistoryWidget(
val widgetView = view as GraphWidgetView
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
val model = HistoryCardPresenter().present(
val model = HistoryCardPresenter.buildState(
habit = habit,
isSkipEnabled = prefs.isSkipEnabled,
firstWeekday = prefs.firstWeekday,
theme = WidgetTheme(),
)

@ -37,8 +37,7 @@ class ScoreWidget(
pendingIntentFactory.showHabit(habit)
override fun refreshData(view: View) {
val presenter = ScoreCardPresenter()
val viewModel = presenter.present(
val viewModel = ScoreCardPresenter.buildState(
habit = habit,
firstWeekday = prefs.firstWeekdayInt,
spinnerPosition = prefs.scoreCardSpinnerPosition

@ -45,8 +45,7 @@ class TargetWidget(
widgetView.setBackgroundAlpha(preferedBackgroundAlpha)
if (preferedBackgroundAlpha >= 255) widgetView.setShadowAlpha(0x4f)
val chart = (widgetView.dataView as TargetChart)
val presenter = TargetCardPresenter()
val data = presenter.present(habit, prefs.firstWeekdayInt)
val data = TargetCardPresenter.buildState(habit, prefs.firstWeekdayInt)
chart.setColor(data.color.toThemedAndroidColor(context))
chart.setTargets(data.targets)
chart.setLabels(data.intervals.map { intervalToLabel(context.resources, it) })

@ -19,86 +19,112 @@
package org.isoron.uhabits.core.ui.screens.habits.show
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.BarCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.FrequencyCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.HistoryCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.OverviewCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.StreakCartPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.SubtitleCardState
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardViewModel
import org.isoron.uhabits.core.ui.screens.habits.show.views.TargetCardState
import org.isoron.uhabits.core.ui.views.Theme
data class ShowHabitViewModel(
data class ShowHabitState(
val title: String = "",
val isNumerical: Boolean = false,
val color: PaletteColor = PaletteColor(1),
val subtitle: SubtitleCardViewModel,
val overview: OverviewCardViewModel,
val notes: NotesCardViewModel,
val target: TargetCardViewModel,
val streaks: StreakCardViewModel,
val scores: ScoreCardViewModel,
val frequency: FrequencyCardViewModel,
val history: HistoryCardViewModel,
val bar: BarCardViewModel,
val subtitle: SubtitleCardState,
val overview: OverviewCardState,
val notes: NotesCardState,
val target: TargetCardState,
val streaks: StreakCardState,
val scores: ScoreCardState,
val frequency: FrequencyCardState,
val history: HistoryCardState,
val bar: BarCardState,
)
class ShowHabitPresenter {
fun present(
class ShowHabitPresenter(
val habit: Habit,
val habitList: HabitList,
val preferences: Preferences,
val screen: Screen,
val commandRunner: CommandRunner,
) {
val historyCardPresenter = HistoryCardPresenter(
commandRunner = commandRunner,
habit = habit,
habitList = habitList,
preferences = preferences,
screen = screen,
)
val barCardPresenter = BarCardPresenter(
preferences = preferences,
screen = screen,
)
val scoreCardPresenter = ScoreCardPresenter(
preferences = preferences,
screen = screen,
)
companion object {
fun buildState(
habit: Habit,
preferences: Preferences,
theme: Theme,
): ShowHabitViewModel {
return ShowHabitViewModel(
): ShowHabitState {
return ShowHabitState(
title = habit.name,
color = habit.color,
isNumerical = habit.isNumerical,
subtitle = SubtitleCardPresenter().present(
subtitle = SubtitleCardPresenter.buildState(
habit = habit,
),
overview = OverviewCardPresenter().present(
overview = OverviewCardPresenter.buildState(
habit = habit,
),
notes = NotesCardPresenter().present(
notes = NotesCardPresenter.buildState(
habit = habit,
),
target = TargetCardPresenter().present(
target = TargetCardPresenter.buildState(
habit = habit,
firstWeekday = preferences.firstWeekdayInt,
),
streaks = StreakCartPresenter().present(
streaks = StreakCartPresenter.buildState(
habit = habit,
),
scores = ScoreCardPresenter().present(
scores = ScoreCardPresenter.buildState(
spinnerPosition = preferences.scoreCardSpinnerPosition,
habit = habit,
firstWeekday = preferences.firstWeekdayInt,
),
frequency = FrequencyCardPresenter().present(
frequency = FrequencyCardPresenter.buildState(
habit = habit,
firstWeekday = preferences.firstWeekdayInt,
),
history = HistoryCardPresenter().present(
history = HistoryCardPresenter.buildState(
habit = habit,
firstWeekday = preferences.firstWeekday,
isSkipEnabled = preferences.isSkipEnabled,
theme = theme,
),
bar = BarCardPresenter().present(
bar = BarCardPresenter.buildState(
habit = habit,
firstWeekday = preferences.firstWeekdayInt,
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
@ -108,3 +134,9 @@ class ShowHabitPresenter {
)
}
}
interface Screen :
BarCardPresenter.Screen,
ScoreCardPresenter.Screen,
HistoryCardPresenter.Screen
}

@ -1,109 +0,0 @@
/*
* Copyright (C) 2017 Á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.core.ui.screens.habits.show
import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import kotlin.math.roundToInt
class ShowHabitBehavior(
private val habitList: HabitList,
private val commandRunner: CommandRunner,
private val habit: Habit,
private val screen: Screen,
private val preferences: Preferences,
) : OnDateClickedListener {
fun onScoreCardSpinnerPosition(position: Int) {
preferences.scoreCardSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
fun onBarCardBoolSpinnerPosition(position: Int) {
preferences.barCardBoolSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
fun onBarCardNumericalSpinnerPosition(position: Int) {
preferences.barCardNumericalSpinnerPosition = position
screen.refresh()
screen.updateWidgets()
}
fun onClickEditHistory() {
screen.showHistoryEditorDialog(this)
}
override fun onDateClicked(date: LocalDate) {
val timestamp = date.timestamp
screen.touchFeedback()
if (habit.isNumerical) {
val entries = habit.computedEntries
val oldValue = entries.get(timestamp).value
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
val thousands = (newValue * 1000).roundToInt()
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
thousands,
),
)
}
} else {
val currentValue = habit.computedEntries.get(timestamp).value
val nextValue = if (preferences.isSkipEnabled) {
Entry.nextToggleValueWithSkip(currentValue)
} else {
Entry.nextToggleValueWithoutSkip(currentValue)
}
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
nextValue,
),
)
}
}
interface Screen {
fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback,
)
fun updateWidgets()
fun refresh()
fun showHistoryEditorDialog(listener: OnDateClickedListener)
fun touchFeedback()
}
}

@ -30,7 +30,7 @@ import org.isoron.uhabits.core.utils.DateUtils
import java.io.File
import java.util.Random
class ShowHabitMenuBehavior(
class ShowHabitMenuPresenter(
private val commandRunner: CommandRunner,
private val habit: Habit,
private val habitList: HabitList,
@ -85,9 +85,7 @@ class ShowHabitMenuBehavior(
fun showEditHabitScreen(habit: Habit)
fun showMessage(m: Message?)
fun showSendFileScreen(filename: String)
fun showDeleteConfirmationScreen(
callback: OnConfirmedCallback
)
fun showDeleteConfirmationScreen(callback: OnConfirmedCallback)
fun close()
fun refresh()
}

@ -23,10 +23,11 @@ import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.groupedSum
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
data class BarCardViewModel(
data class BarCardState(
val theme: Theme,
val boolSpinnerPosition: Int,
val bucketSize: Int,
@ -36,17 +37,21 @@ data class BarCardViewModel(
val numericalSpinnerPosition: Int,
)
class BarCardPresenter {
class BarCardPresenter(
val preferences: Preferences,
val screen: Screen,
) {
companion object {
val numericalBucketSizes = intArrayOf(1, 7, 31, 92, 365)
val boolBucketSizes = intArrayOf(7, 31, 92, 365)
fun present(
fun buildState(
habit: Habit,
firstWeekday: Int,
numericalSpinnerPosition: Int,
boolSpinnerPosition: Int,
theme: Theme,
): BarCardViewModel {
): BarCardState {
val bucketSize = if (habit.isNumerical) {
numericalBucketSizes[numericalSpinnerPosition]
} else {
@ -59,7 +64,7 @@ class BarCardPresenter {
firstWeekday = firstWeekday,
isNumerical = habit.isNumerical,
)
return BarCardViewModel(
return BarCardState(
theme = theme,
entries = entries,
bucketSize = bucketSize,
@ -70,3 +75,21 @@ class BarCardPresenter {
)
}
}
fun onNumericalSpinnerPosition(position: Int) {
preferences.barCardNumericalSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
fun onBoolSpinnerPosition(position: Int) {
preferences.barCardBoolSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
interface Screen {
fun updateWidgets()
fun refresh()
}
}

@ -24,17 +24,18 @@ import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp
import java.util.HashMap
data class FrequencyCardViewModel(
data class FrequencyCardState(
val color: PaletteColor,
val firstWeekday: Int,
val frequency: HashMap<Timestamp, Array<Int>>,
)
class FrequencyCardPresenter {
fun present(
companion object {
fun buildState(
habit: Habit,
firstWeekday: Int,
) = FrequencyCardViewModel(
) = FrequencyCardState(
color = habit.color,
frequency = habit.originalEntries.computeWeekdayFrequency(
isNumerical = habit.isNumerical
@ -42,3 +43,4 @@ class FrequencyCardPresenter {
firstWeekday = firstWeekday,
)
}
}

@ -21,18 +21,25 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.platform.time.DayOfWeek
import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.views.HistoryChart
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
import kotlin.math.max
import kotlin.math.roundToInt
data class HistoryCardViewModel(
data class HistoryCardState(
val color: PaletteColor,
val firstWeekday: DayOfWeek,
val series: List<HistoryChart.Square>,
@ -40,13 +47,59 @@ data class HistoryCardViewModel(
val today: LocalDate,
)
class HistoryCardPresenter {
fun present(
class HistoryCardPresenter(
val commandRunner: CommandRunner,
val habit: Habit,
val habitList: HabitList,
val preferences: Preferences,
val screen: Screen,
) : OnDateClickedListener {
override fun onDateClicked(date: LocalDate) {
val timestamp = date.timestamp
screen.showFeedback()
if (habit.isNumerical) {
val entries = habit.computedEntries
val oldValue = entries.get(timestamp).value
screen.showNumberPicker(oldValue / 1000.0, habit.unit) { newValue: Double ->
val thousands = (newValue * 1000).roundToInt()
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
thousands,
),
)
}
} else {
val currentValue = habit.computedEntries.get(timestamp).value
val nextValue = if (preferences.isSkipEnabled) {
Entry.nextToggleValueWithSkip(currentValue)
} else {
Entry.nextToggleValueWithoutSkip(currentValue)
}
commandRunner.run(
CreateRepetitionCommand(
habitList,
habit,
timestamp,
nextValue,
),
)
}
}
fun onClickEditButton() {
screen.showHistoryEditorDialog(this)
}
companion object {
fun buildState(
habit: Habit,
firstWeekday: DayOfWeek,
isSkipEnabled: Boolean,
theme: Theme,
): HistoryCardViewModel {
): HistoryCardState {
val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today)
@ -70,7 +123,7 @@ class HistoryCardPresenter {
}
}
return HistoryCardViewModel(
return HistoryCardState(
color = habit.color,
firstWeekday = firstWeekday,
today = today.toLocalDate(),
@ -79,3 +132,14 @@ class HistoryCardPresenter {
)
}
}
interface Screen {
fun showHistoryEditorDialog(listener: OnDateClickedListener)
fun showFeedback()
fun showNumberPicker(
value: Double,
unit: String,
callback: ListHabitsBehavior.NumberPickerCallback,
)
}
}

@ -21,12 +21,14 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit
data class NotesCardViewModel(
data class NotesCardState(
val description: String,
)
class NotesCardPresenter {
fun present(habit: Habit) = NotesCardViewModel(
companion object {
fun buildState(habit: Habit) = NotesCardState(
description = habit.description,
)
}
}

@ -24,7 +24,7 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils
data class OverviewCardViewModel(
data class OverviewCardState(
val color: PaletteColor,
val scoreMonthDiff: Float,
val scoreYearDiff: Float,
@ -33,7 +33,8 @@ data class OverviewCardViewModel(
)
class OverviewCardPresenter {
fun present(habit: Habit): OverviewCardViewModel {
companion object {
fun buildState(habit: Habit): OverviewCardState {
val today = DateUtils.getTodayWithOffset()
val lastMonth = today.minus(30)
val lastYear = today.minus(365)
@ -45,7 +46,7 @@ class OverviewCardPresenter {
.filter { it.value == Entry.YES_MANUAL }
.count()
.toLong()
return OverviewCardViewModel(
return OverviewCardState(
color = habit.color,
scoreToday = scoreToday,
scoreMonthDiff = scoreToday - scoreLastMonth,
@ -54,3 +55,4 @@ class OverviewCardPresenter {
)
}
}
}

@ -22,16 +22,20 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Score
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils
data class ScoreCardViewModel(
data class ScoreCardState(
val scores: List<Score>,
val bucketSize: Int,
val spinnerPosition: Int,
val color: PaletteColor,
)
class ScoreCardPresenter {
class ScoreCardPresenter(
val preferences: Preferences,
val screen: Screen,
) {
companion object {
val BUCKET_SIZES = intArrayOf(1, 7, 31, 92, 365)
fun getTruncateField(bucketSize: Int): DateUtils.TruncateField {
@ -44,13 +48,12 @@ class ScoreCardPresenter {
else -> return DateUtils.TruncateField.MONTH
}
}
}
fun present(
fun buildState(
habit: Habit,
firstWeekday: Int,
spinnerPosition: Int,
): ScoreCardViewModel {
): ScoreCardState {
val bucketSize = BUCKET_SIZES[spinnerPosition]
val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
@ -69,7 +72,7 @@ class ScoreCardPresenter {
it.timestamp
}.reversed()
return ScoreCardViewModel(
return ScoreCardState(
color = habit.color,
scores = scores,
bucketSize = bucketSize,
@ -77,3 +80,15 @@ class ScoreCardPresenter {
)
}
}
fun onSpinnerPosition(position: Int) {
preferences.scoreCardSpinnerPosition = position
screen.updateWidgets()
screen.refresh()
}
interface Screen {
fun updateWidgets()
fun refresh()
}
}

@ -23,16 +23,18 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Streak
data class StreakCardViewModel(
data class StreakCardState(
val color: PaletteColor,
val bestStreaks: List<Streak>
)
class StreakCartPresenter {
fun present(habit: Habit): StreakCardViewModel {
return StreakCardViewModel(
companion object {
fun buildState(habit: Habit): StreakCardState {
return StreakCardState(
color = habit.color,
bestStreaks = habit.streaks.getBest(10),
)
}
}
}

@ -24,7 +24,7 @@ import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder
data class SubtitleCardViewModel(
data class SubtitleCardState(
val color: PaletteColor,
val frequency: Frequency,
val isNumerical: Boolean,
@ -35,9 +35,10 @@ data class SubtitleCardViewModel(
)
class SubtitleCardPresenter {
fun present(
companion object {
fun buildState(
habit: Habit,
): SubtitleCardViewModel = SubtitleCardViewModel(
): SubtitleCardState = SubtitleCardState(
color = habit.color,
frequency = habit.frequency,
isNumerical = habit.isNumerical,
@ -47,3 +48,4 @@ class SubtitleCardPresenter {
unit = habit.unit,
)
}
}

@ -26,7 +26,7 @@ import org.isoron.uhabits.core.utils.DateUtils
import java.util.ArrayList
import java.util.Calendar
data class TargetCardViewModel(
data class TargetCardState(
val color: PaletteColor,
val values: List<Double> = listOf(),
val targets: List<Double> = listOf(),
@ -34,10 +34,11 @@ data class TargetCardViewModel(
)
class TargetCardPresenter {
fun present(
companion object {
fun buildState(
habit: Habit,
firstWeekday: Int,
): TargetCardViewModel {
): TargetCardState {
val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today)
@ -100,7 +101,7 @@ class TargetCardPresenter {
intervals.add(91)
intervals.add(365)
return TargetCardViewModel(
return TargetCardState(
color = habit.color,
values = values,
targets = targets,
@ -108,3 +109,4 @@ class TargetCardPresenter {
)
}
}
}

@ -31,25 +31,25 @@ import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.*;
public class ShowHabitMenuBehaviorTest extends BaseUnitTest
public class ShowHabitMenuPresenterTest extends BaseUnitTest
{
private ShowHabitMenuBehavior.System system;
private ShowHabitMenuPresenter.System system;
private ShowHabitMenuBehavior.Screen screen;
private ShowHabitMenuPresenter.Screen screen;
private Habit habit;
private ShowHabitMenuBehavior menu;
private ShowHabitMenuPresenter menu;
@Override
public void setUp() throws Exception
{
super.setUp();
system = mock(ShowHabitMenuBehavior.System.class);
screen = mock(ShowHabitMenuBehavior.Screen.class);
system = mock(ShowHabitMenuPresenter.System.class);
screen = mock(ShowHabitMenuPresenter.Screen.class);
habit = fixtures.createShortHabit();
menu = new ShowHabitMenuBehavior(commandRunner, habit, habitList, screen, system, taskRunner);
menu = new ShowHabitMenuPresenter(commandRunner, habit, habitList, screen, system, taskRunner);
}
@Test
Loading…
Cancel
Save