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

@ -43,7 +43,7 @@ class FrequencyCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.frequencyCard) as FrequencyCardView .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) measureView(view, 800f, 600f)
} }

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

@ -25,7 +25,7 @@ import androidx.test.filters.MediumTest
import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.equalTo
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R 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.Assert.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
@ -44,7 +44,7 @@ class NotesCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById(R.id.notesCard) .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) measureView(view, 800f, 200f)
} }
@ -55,7 +55,7 @@ class NotesCardViewTest : BaseViewTest() {
@Test @Test
fun testRenderEmptyDescription() { fun testRenderEmptyDescription() {
view.update(NotesCardViewModel(description = "")) view.setState(NotesCardState(description = ""))
assertThat(view.visibility, equalTo(GONE)) assertThat(view.visibility, equalTo(GONE))
} }
} }

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

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

@ -24,7 +24,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest import androidx.test.filters.MediumTest
import org.isoron.uhabits.BaseViewTest import org.isoron.uhabits.BaseViewTest
import org.isoron.uhabits.R 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.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -43,8 +43,8 @@ class StreakCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById<View>(R.id.streakCard) as StreakCardView .findViewById<View>(R.id.streakCard) as StreakCardView
view.update( view.setState(
StreakCardViewModel( StreakCardState(
bestStreaks = habit.streaks.getBest(10), bestStreaks = habit.streaks.getBest(10),
color = habit.color, 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.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
import org.isoron.uhabits.core.models.WeekdayList.EVERY_DAY 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.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
@ -45,8 +45,8 @@ class SubtitleCardViewTest : BaseViewTest() {
.from(targetContext) .from(targetContext)
.inflate(R.layout.show_habit, null) .inflate(R.layout.show_habit, null)
.findViewById(R.id.subtitleCard) .findViewById(R.id.subtitleCard)
view.update( view.setState(
SubtitleCardViewModel( SubtitleCardState(
color = PaletteColor(7), color = PaletteColor(7),
frequency = Frequency(3, 7), frequency = Frequency(3, 7),
isNumerical = false, isNumerical = false,

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

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

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

@ -20,6 +20,7 @@ package org.isoron.uhabits.activities.habits.show
import android.content.ContentUris import android.content.ContentUris
import android.os.Bundle import android.os.Bundle
import android.view.HapticFeedbackConstants
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -28,33 +29,39 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.isoron.uhabits.AndroidDirFinder import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.HabitsDirFinder 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.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
import org.isoron.uhabits.core.commands.Command import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitBehavior import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuBehavior 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.screens.habits.show.ShowHabitPresenter
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.intents.IntentFactory 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 { class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
val presenter = ShowHabitPresenter()
private lateinit var commandRunner: CommandRunner private lateinit var commandRunner: CommandRunner
private lateinit var menu: ShowHabitMenu private lateinit var menu: ShowHabitMenu
private lateinit var view: ShowHabitView private lateinit var view: ShowHabitView
private lateinit var habit: Habit private lateinit var habit: Habit
private lateinit var preferences: Preferences private lateinit var preferences: Preferences
private lateinit var themeSwitcher: AndroidThemeSwitcher private lateinit var themeSwitcher: AndroidThemeSwitcher
private lateinit var behavior: ShowHabitBehavior private lateinit var widgetUpdater: WidgetUpdater
private val scope = CoroutineScope(Dispatchers.Main) private val scope = CoroutineScope(Dispatchers.Main)
private lateinit var presenter: ShowHabitPresenter
private val screen = Screen()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -64,21 +71,12 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
habit = habitList.getById(ContentUris.parseId(intent.data!!))!! habit = habitList.getById(ContentUris.parseId(intent.data!!))!!
preferences = appComponent.preferences preferences = appComponent.preferences
commandRunner = appComponent.commandRunner commandRunner = appComponent.commandRunner
widgetUpdater = appComponent.widgetUpdater
themeSwitcher = AndroidThemeSwitcher(this, preferences) themeSwitcher = AndroidThemeSwitcher(this, preferences)
themeSwitcher.apply() themeSwitcher.apply()
view = ShowHabitView(this) presenter = ShowHabitPresenter(
val screen = ShowHabitScreen(
activity = this,
confirmDeleteDialogFactory = ConfirmDeleteDialogFactory { this },
habit = habit,
intentFactory = IntentFactory(),
numberPickerFactory = NumberPickerFactory(this),
widgetUpdater = appComponent.widgetUpdater,
)
behavior = ShowHabitBehavior(
commandRunner = commandRunner, commandRunner = commandRunner,
habit = habit, habit = habit,
habitList = habitList, habitList = habitList,
@ -86,7 +84,9 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
screen = screen, screen = screen,
) )
val menuBehavior = ShowHabitMenuBehavior( view = ShowHabitView(this)
val menuPresenter = ShowHabitMenuPresenter(
commandRunner = commandRunner, commandRunner = commandRunner,
habit = habit, habit = habit,
habitList = habitList, habitList = habitList,
@ -97,15 +97,11 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
menu = ShowHabitMenu( menu = ShowHabitMenu(
activity = this, activity = this,
behavior = menuBehavior, presenter = menuPresenter,
preferences = preferences, preferences = preferences,
) )
view.onScoreCardSpinnerPosition = behavior::onScoreCardSpinnerPosition view.setListener(presenter)
view.onBarCardBoolSpinnerPosition = behavior::onBarCardBoolSpinnerPosition
view.onBarCardNumericalSpinnerPosition = behavior::onBarCardNumericalSpinnerPosition
view.onClickEditHistoryButton = behavior::onClickEditHistory
setContentView(view) setContentView(view)
} }
@ -121,9 +117,9 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
super.onResume() super.onResume()
commandRunner.addListener(this) commandRunner.addListener(this)
supportFragmentManager.findFragmentByTag("historyEditor")?.let { supportFragmentManager.findFragmentByTag("historyEditor")?.let {
(it as HistoryEditorDialog).setOnDateClickedListener(behavior) (it as HistoryEditorDialog).setOnDateClickedListener(presenter.historyCardPresenter)
} }
refresh() screen.refresh()
} }
override fun onPause() { override fun onPause() {
@ -132,13 +128,18 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
} }
override fun onCommandFinished(command: Command) { 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 { scope.launch {
view.update( view.setState(
presenter.present( ShowHabitPresenter.buildState(
habit = habit, habit = habit,
preferences = preferences, preferences = preferences,
theme = themeSwitcher.currentTheme, 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 android.view.MenuItem
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.preferences.Preferences 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( class ShowHabitMenu(
val activity: ShowHabitActivity, val activity: ShowHabitActivity,
val behavior: ShowHabitMenuBehavior, val presenter: ShowHabitMenuPresenter,
val preferences: Preferences, val preferences: Preferences,
) { ) {
fun onCreateOptionsMenu(menu: Menu): Boolean { fun onCreateOptionsMenu(menu: Menu): Boolean {
@ -41,19 +41,19 @@ class ShowHabitMenu(
fun onOptionsItemSelected(item: MenuItem): Boolean { fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) { when (item.itemId) {
R.id.action_edit_habit -> { R.id.action_edit_habit -> {
behavior.onEditHabit() presenter.onEditHabit()
return true return true
} }
R.id.action_delete -> { R.id.action_delete -> {
behavior.onDeleteHabit() presenter.onDeleteHabit()
return true return true
} }
R.id.action_randomize -> { R.id.action_randomize -> {
behavior.onRandomize() presenter.onRandomize()
return true return true
} }
R.id.export -> { R.id.export -> {
behavior.onExportCSV() presenter.onExportCSV()
return true 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.content.Context
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.FrameLayout 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.databinding.ShowHabitBinding
import org.isoron.uhabits.utils.setupToolbar import org.isoron.uhabits.utils.setupToolbar
class ShowHabitView(context: Context) : FrameLayout(context) { class ShowHabitView(context: Context) : FrameLayout(context) {
private val binding = ShowHabitBinding.inflate(LayoutInflater.from(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 { init {
addView(binding.root) 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) setupToolbar(binding.toolbar, title = data.title, color = data.color)
binding.subtitleCard.update(data.subtitle) binding.subtitleCard.setState(data.subtitle)
binding.overviewCard.update(data.overview) binding.overviewCard.setState(data.overview)
binding.notesCard.update(data.notes) binding.notesCard.setState(data.notes)
binding.targetCard.update(data.target) binding.targetCard.setState(data.target)
binding.streakCard.update(data.streaks) binding.streakCard.setState(data.streaks)
binding.scoreCard.update(data.scores) binding.scoreCard.setState(data.scores)
binding.frequencyCard.update(data.frequency) binding.frequencyCard.setState(data.frequency)
binding.historyCard.update(data.history) binding.historyCard.setState(data.history)
binding.barCard.update(data.bar) binding.barCard.setState(data.bar)
if (data.isNumerical) { if (data.isNumerical) {
binding.overviewCard.visibility = GONE binding.overviewCard.visibility = GONE
binding.streakCard.visibility = GONE binding.streakCard.visibility = GONE
@ -60,4 +52,10 @@ class ShowHabitView(context: Context) : FrameLayout(context) {
binding.targetCard.visibility = GONE 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.AdapterView
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.time.JavaLocalDateFormatter 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.core.ui.views.BarChart
import org.isoron.uhabits.databinding.ShowHabitBarBinding import org.isoron.uhabits.databinding.ShowHabitBarBinding
import org.isoron.uhabits.utils.toThemedAndroidColor import org.isoron.uhabits.utils.toThemedAndroidColor
@ -34,53 +35,52 @@ import java.util.Locale
class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class BarCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this) private var binding = ShowHabitBarBinding.inflate(LayoutInflater.from(context), this)
var onNumericalSpinnerPosition: (position: Int) -> Unit = {}
var onBoolSpinnerPosition: (position: Int) -> Unit = {}
fun update(data: BarCardViewModel) { fun setState(state: BarCardState) {
val androidColor = data.color.toThemedAndroidColor(context) val androidColor = state.color.toThemedAndroidColor(context)
binding.chart.view = BarChart(data.theme, JavaLocalDateFormatter(Locale.US)).apply { binding.chart.view = BarChart(state.theme, JavaLocalDateFormatter(Locale.US)).apply {
series = mutableListOf(data.entries.map { it.value / 1000.0 }) series = mutableListOf(state.entries.map { it.value / 1000.0 })
colors = mutableListOf(theme.color(data.color.paletteIndex)) colors = mutableListOf(theme.color(state.color.paletteIndex))
axis = data.entries.map { it.timestamp.toLocalDate() } axis = state.entries.map { it.timestamp.toLocalDate() }
} }
binding.chart.resetDataOffset() binding.chart.resetDataOffset()
binding.chart.postInvalidate() binding.chart.postInvalidate()
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
if (data.isNumerical) { if (state.isNumerical) {
binding.boolSpinner.visibility = GONE binding.boolSpinner.visibility = GONE
} else { } else {
binding.numericalSpinner.visibility = GONE binding.numericalSpinner.visibility = GONE
} }
binding.numericalSpinner.onItemSelectedListener = null binding.numericalSpinner.onItemSelectedListener = null
binding.numericalSpinner.setSelection(data.numericalSpinnerPosition) binding.numericalSpinner.setSelection(state.numericalSpinnerPosition)
binding.numericalSpinner.onItemSelectedListener = binding.boolSpinner.setSelection(state.boolSpinnerPosition)
object : AdapterView.OnItemSelectedListener { }
fun setListener(presenter: BarCardPresenter) {
binding.boolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected( override fun onItemSelected(
parent: AdapterView<*>?, parent: AdapterView<*>?,
view: View?, view: View?,
position: Int, position: Int,
id: Long id: Long,
) { ) {
onNumericalSpinnerPosition(position) presenter.onBoolSpinnerPosition(position)
} }
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {
} }
} }
binding.numericalSpinner.onItemSelectedListener =
binding.boolSpinner.onItemSelectedListener = null object : AdapterView.OnItemSelectedListener {
binding.boolSpinner.setSelection(data.boolSpinnerPosition)
binding.boolSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected( override fun onItemSelected(
parent: AdapterView<*>?, parent: AdapterView<*>?,
view: View?, view: View?,
position: Int, position: Int,
id: Long id: Long,
) { ) {
onBoolSpinnerPosition(position) presenter.onNumericalSpinnerPosition(position)
} }
override fun onNothingSelected(parent: AdapterView<*>?) { override fun onNothingSelected(parent: AdapterView<*>?) {

@ -22,7 +22,7 @@ import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout 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.databinding.ShowHabitFrequencyBinding
import org.isoron.uhabits.utils.toThemedAndroidColor 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) private var binding = ShowHabitFrequencyBinding.inflate(LayoutInflater.from(context), this)
fun update(data: FrequencyCardViewModel) { fun setState(state: FrequencyCardState) {
val androidColor = data.color.toThemedAndroidColor(context) val androidColor = state.color.toThemedAndroidColor(context)
binding.frequencyChart.setFrequency(data.frequency) binding.frequencyChart.setFrequency(state.frequency)
binding.frequencyChart.setFirstWeekday(data.firstWeekday) binding.frequencyChart.setFirstWeekday(state.firstWeekday)
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
binding.frequencyChart.setColor(androidColor) binding.frequencyChart.setColor(androidColor)
} }

@ -23,7 +23,8 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import org.isoron.platform.time.JavaLocalDateFormatter 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.core.ui.views.HistoryChart
import org.isoron.uhabits.databinding.ShowHabitHistoryBinding import org.isoron.uhabits.databinding.ShowHabitHistoryBinding
import org.isoron.uhabits.utils.toThemedAndroidColor 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) private var binding = ShowHabitHistoryBinding.inflate(LayoutInflater.from(context), this)
var onClickEditButton: () -> Unit = {} fun setState(state: HistoryCardState) {
val androidColor = state.color.toThemedAndroidColor(context)
init {
binding.edit.setOnClickListener { onClickEditButton() }
}
fun update(data: HistoryCardViewModel) {
val androidColor = data.color.toThemedAndroidColor(context)
binding.title.setTextColor(androidColor) binding.title.setTextColor(androidColor)
binding.chart.view = HistoryChart( binding.chart.view = HistoryChart(
today = data.today, today = state.today,
paletteColor = data.color, paletteColor = state.color,
theme = data.theme, theme = state.theme,
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()), dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
series = data.series, series = state.series,
firstWeekday = data.firstWeekday, firstWeekday = state.firstWeekday,
) )
binding.chart.postInvalidate() 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.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout 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 import org.isoron.uhabits.databinding.ShowHabitNotesBinding
class NotesCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { class NotesCardView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private val binding = ShowHabitNotesBinding.inflate(LayoutInflater.from(context), this) private val binding = ShowHabitNotesBinding.inflate(LayoutInflater.from(context), this)
fun update(data: NotesCardViewModel) { fun setState(state: NotesCardState) {
if (data.description.isEmpty()) { if (state.description.isEmpty()) {
visibility = GONE visibility = GONE
} else { } else {
visibility = VISIBLE visibility = VISIBLE
binding.habitNotes.text = data.description binding.habitNotes.text = state.description
} }
invalidate() invalidate()
} }

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

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

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

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

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

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

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

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

@ -19,86 +19,112 @@
package org.isoron.uhabits.core.ui.screens.habits.show 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.Habit
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences 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.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.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.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.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.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.ScoreCardPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardViewModel import org.isoron.uhabits.core.ui.screens.habits.show.views.ScoreCardState
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.core.ui.screens.habits.show.views.StreakCartPresenter 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.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.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 import org.isoron.uhabits.core.ui.views.Theme
data class ShowHabitViewModel( data class ShowHabitState(
val title: String = "", val title: String = "",
val isNumerical: Boolean = false, val isNumerical: Boolean = false,
val color: PaletteColor = PaletteColor(1), val color: PaletteColor = PaletteColor(1),
val subtitle: SubtitleCardViewModel, val subtitle: SubtitleCardState,
val overview: OverviewCardViewModel, val overview: OverviewCardState,
val notes: NotesCardViewModel, val notes: NotesCardState,
val target: TargetCardViewModel, val target: TargetCardState,
val streaks: StreakCardViewModel, val streaks: StreakCardState,
val scores: ScoreCardViewModel, val scores: ScoreCardState,
val frequency: FrequencyCardViewModel, val frequency: FrequencyCardState,
val history: HistoryCardViewModel, val history: HistoryCardState,
val bar: BarCardViewModel, val bar: BarCardState,
) )
class ShowHabitPresenter { class ShowHabitPresenter(
fun present( 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, habit: Habit,
preferences: Preferences, preferences: Preferences,
theme: Theme, theme: Theme,
): ShowHabitViewModel { ): ShowHabitState {
return ShowHabitViewModel( return ShowHabitState(
title = habit.name, title = habit.name,
color = habit.color, color = habit.color,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
subtitle = SubtitleCardPresenter().present( subtitle = SubtitleCardPresenter.buildState(
habit = habit, habit = habit,
), ),
overview = OverviewCardPresenter().present( overview = OverviewCardPresenter.buildState(
habit = habit, habit = habit,
), ),
notes = NotesCardPresenter().present( notes = NotesCardPresenter.buildState(
habit = habit, habit = habit,
), ),
target = TargetCardPresenter().present( target = TargetCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
), ),
streaks = StreakCartPresenter().present( streaks = StreakCartPresenter.buildState(
habit = habit, habit = habit,
), ),
scores = ScoreCardPresenter().present( scores = ScoreCardPresenter.buildState(
spinnerPosition = preferences.scoreCardSpinnerPosition, spinnerPosition = preferences.scoreCardSpinnerPosition,
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
), ),
frequency = FrequencyCardPresenter().present( frequency = FrequencyCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
), ),
history = HistoryCardPresenter().present( history = HistoryCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekday, firstWeekday = preferences.firstWeekday,
isSkipEnabled = preferences.isSkipEnabled,
theme = theme, theme = theme,
), ),
bar = BarCardPresenter().present( bar = BarCardPresenter.buildState(
habit = habit, habit = habit,
firstWeekday = preferences.firstWeekdayInt, firstWeekday = preferences.firstWeekdayInt,
boolSpinnerPosition = preferences.barCardBoolSpinnerPosition, boolSpinnerPosition = preferences.barCardBoolSpinnerPosition,
@ -107,4 +133,10 @@ 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.io.File
import java.util.Random import java.util.Random
class ShowHabitMenuBehavior( class ShowHabitMenuPresenter(
private val commandRunner: CommandRunner, private val commandRunner: CommandRunner,
private val habit: Habit, private val habit: Habit,
private val habitList: HabitList, private val habitList: HabitList,
@ -85,9 +85,7 @@ class ShowHabitMenuBehavior(
fun showEditHabitScreen(habit: Habit) fun showEditHabitScreen(habit: Habit)
fun showMessage(m: Message?) fun showMessage(m: Message?)
fun showSendFileScreen(filename: String) fun showSendFileScreen(filename: String)
fun showDeleteConfirmationScreen( fun showDeleteConfirmationScreen(callback: OnConfirmedCallback)
callback: OnConfirmedCallback
)
fun close() fun close()
fun refresh() 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.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.groupedSum 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.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
data class BarCardViewModel( data class BarCardState(
val theme: Theme, val theme: Theme,
val boolSpinnerPosition: Int, val boolSpinnerPosition: Int,
val bucketSize: Int, val bucketSize: Int,
@ -36,17 +37,21 @@ data class BarCardViewModel(
val numericalSpinnerPosition: Int, val numericalSpinnerPosition: Int,
) )
class BarCardPresenter { class BarCardPresenter(
val preferences: Preferences,
val screen: Screen,
) {
companion object {
val numericalBucketSizes = intArrayOf(1, 7, 31, 92, 365) val numericalBucketSizes = intArrayOf(1, 7, 31, 92, 365)
val boolBucketSizes = intArrayOf(7, 31, 92, 365) val boolBucketSizes = intArrayOf(7, 31, 92, 365)
fun present( fun buildState(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
numericalSpinnerPosition: Int, numericalSpinnerPosition: Int,
boolSpinnerPosition: Int, boolSpinnerPosition: Int,
theme: Theme, theme: Theme,
): BarCardViewModel { ): BarCardState {
val bucketSize = if (habit.isNumerical) { val bucketSize = if (habit.isNumerical) {
numericalBucketSizes[numericalSpinnerPosition] numericalBucketSizes[numericalSpinnerPosition]
} else { } else {
@ -59,7 +64,7 @@ class BarCardPresenter {
firstWeekday = firstWeekday, firstWeekday = firstWeekday,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
) )
return BarCardViewModel( return BarCardState(
theme = theme, theme = theme,
entries = entries, entries = entries,
bucketSize = bucketSize, bucketSize = bucketSize,
@ -69,4 +74,22 @@ class BarCardPresenter {
boolSpinnerPosition = boolSpinnerPosition, boolSpinnerPosition = boolSpinnerPosition,
) )
} }
}
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,21 +24,23 @@ import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Timestamp import org.isoron.uhabits.core.models.Timestamp
import java.util.HashMap import java.util.HashMap
data class FrequencyCardViewModel( data class FrequencyCardState(
val color: PaletteColor, val color: PaletteColor,
val firstWeekday: Int, val firstWeekday: Int,
val frequency: HashMap<Timestamp, Array<Int>>, val frequency: HashMap<Timestamp, Array<Int>>,
) )
class FrequencyCardPresenter { class FrequencyCardPresenter {
fun present( companion object {
fun buildState(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
) = FrequencyCardViewModel( ) = FrequencyCardState(
color = habit.color, color = habit.color,
frequency = habit.originalEntries.computeWeekdayFrequency( frequency = habit.originalEntries.computeWeekdayFrequency(
isNumerical = habit.isNumerical isNumerical = habit.isNumerical
), ),
firstWeekday = firstWeekday, 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.DayOfWeek
import org.isoron.platform.time.LocalDate 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
import org.isoron.uhabits.core.models.Entry.Companion.SKIP 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_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Habit 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.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.HistoryChart
import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.core.ui.views.Theme import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt
data class HistoryCardViewModel( data class HistoryCardState(
val color: PaletteColor, val color: PaletteColor,
val firstWeekday: DayOfWeek, val firstWeekday: DayOfWeek,
val series: List<HistoryChart.Square>, val series: List<HistoryChart.Square>,
@ -40,13 +47,59 @@ data class HistoryCardViewModel(
val today: LocalDate, val today: LocalDate,
) )
class HistoryCardPresenter { class HistoryCardPresenter(
fun present( 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, habit: Habit,
firstWeekday: DayOfWeek, firstWeekday: DayOfWeek,
isSkipEnabled: Boolean,
theme: Theme, theme: Theme,
): HistoryCardViewModel { ): HistoryCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today) val entries = habit.computedEntries.getByInterval(oldest, today)
@ -70,7 +123,7 @@ class HistoryCardPresenter {
} }
} }
return HistoryCardViewModel( return HistoryCardState(
color = habit.color, color = habit.color,
firstWeekday = firstWeekday, firstWeekday = firstWeekday,
today = today.toLocalDate(), today = today.toLocalDate(),
@ -78,4 +131,15 @@ class HistoryCardPresenter {
series = series, series = series,
) )
} }
}
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 import org.isoron.uhabits.core.models.Habit
data class NotesCardViewModel( data class NotesCardState(
val description: String, val description: String,
) )
class NotesCardPresenter { class NotesCardPresenter {
fun present(habit: Habit) = NotesCardViewModel( companion object {
fun buildState(habit: Habit) = NotesCardState(
description = habit.description, 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.models.PaletteColor
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
data class OverviewCardViewModel( data class OverviewCardState(
val color: PaletteColor, val color: PaletteColor,
val scoreMonthDiff: Float, val scoreMonthDiff: Float,
val scoreYearDiff: Float, val scoreYearDiff: Float,
@ -33,7 +33,8 @@ data class OverviewCardViewModel(
) )
class OverviewCardPresenter { class OverviewCardPresenter {
fun present(habit: Habit): OverviewCardViewModel { companion object {
fun buildState(habit: Habit): OverviewCardState {
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val lastMonth = today.minus(30) val lastMonth = today.minus(30)
val lastYear = today.minus(365) val lastYear = today.minus(365)
@ -45,7 +46,7 @@ class OverviewCardPresenter {
.filter { it.value == Entry.YES_MANUAL } .filter { it.value == Entry.YES_MANUAL }
.count() .count()
.toLong() .toLong()
return OverviewCardViewModel( return OverviewCardState(
color = habit.color, color = habit.color,
scoreToday = scoreToday, scoreToday = scoreToday,
scoreMonthDiff = scoreToday - scoreLastMonth, scoreMonthDiff = scoreToday - scoreLastMonth,
@ -53,4 +54,5 @@ class OverviewCardPresenter {
totalCount = totalCount, totalCount = totalCount,
) )
} }
}
} }

@ -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.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Score import org.isoron.uhabits.core.models.Score
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
data class ScoreCardViewModel( data class ScoreCardState(
val scores: List<Score>, val scores: List<Score>,
val bucketSize: Int, val bucketSize: Int,
val spinnerPosition: Int, val spinnerPosition: Int,
val color: PaletteColor, val color: PaletteColor,
) )
class ScoreCardPresenter { class ScoreCardPresenter(
val preferences: Preferences,
val screen: Screen,
) {
companion object { companion object {
val BUCKET_SIZES = intArrayOf(1, 7, 31, 92, 365) val BUCKET_SIZES = intArrayOf(1, 7, 31, 92, 365)
fun getTruncateField(bucketSize: Int): DateUtils.TruncateField { fun getTruncateField(bucketSize: Int): DateUtils.TruncateField {
@ -44,13 +48,12 @@ class ScoreCardPresenter {
else -> return DateUtils.TruncateField.MONTH else -> return DateUtils.TruncateField.MONTH
} }
} }
}
fun present( fun buildState(
habit: Habit, habit: Habit,
firstWeekday: Int, firstWeekday: Int,
spinnerPosition: Int, spinnerPosition: Int,
): ScoreCardViewModel { ): ScoreCardState {
val bucketSize = BUCKET_SIZES[spinnerPosition] val bucketSize = BUCKET_SIZES[spinnerPosition]
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
@ -69,11 +72,23 @@ class ScoreCardPresenter {
it.timestamp it.timestamp
}.reversed() }.reversed()
return ScoreCardViewModel( return ScoreCardState(
color = habit.color, color = habit.color,
scores = scores, scores = scores,
bucketSize = bucketSize, bucketSize = bucketSize,
spinnerPosition = spinnerPosition, spinnerPosition = spinnerPosition,
) )
} }
}
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.PaletteColor
import org.isoron.uhabits.core.models.Streak import org.isoron.uhabits.core.models.Streak
data class StreakCardViewModel( data class StreakCardState(
val color: PaletteColor, val color: PaletteColor,
val bestStreaks: List<Streak> val bestStreaks: List<Streak>
) )
class StreakCartPresenter { class StreakCartPresenter {
fun present(habit: Habit): StreakCardViewModel { companion object {
return StreakCardViewModel( fun buildState(habit: Habit): StreakCardState {
return StreakCardState(
color = habit.color, color = habit.color,
bestStreaks = habit.streaks.getBest(10), 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.PaletteColor
import org.isoron.uhabits.core.models.Reminder import org.isoron.uhabits.core.models.Reminder
data class SubtitleCardViewModel( data class SubtitleCardState(
val color: PaletteColor, val color: PaletteColor,
val frequency: Frequency, val frequency: Frequency,
val isNumerical: Boolean, val isNumerical: Boolean,
@ -35,9 +35,10 @@ data class SubtitleCardViewModel(
) )
class SubtitleCardPresenter { class SubtitleCardPresenter {
fun present( companion object {
fun buildState(
habit: Habit, habit: Habit,
): SubtitleCardViewModel = SubtitleCardViewModel( ): SubtitleCardState = SubtitleCardState(
color = habit.color, color = habit.color,
frequency = habit.frequency, frequency = habit.frequency,
isNumerical = habit.isNumerical, isNumerical = habit.isNumerical,
@ -46,4 +47,5 @@ class SubtitleCardPresenter {
targetValue = habit.targetValue, targetValue = habit.targetValue,
unit = habit.unit, unit = habit.unit,
) )
}
} }

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

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