diff --git a/uhabits-android/src/main/AndroidManifest.xml b/uhabits-android/src/main/AndroidManifest.xml
index 56529c4bd..36f3928c0 100644
--- a/uhabits-android/src/main/AndroidManifest.xml
+++ b/uhabits-android/src/main/AndroidManifest.xml
@@ -80,6 +80,14 @@
android:value=".activities.habits.list.ListHabitsActivity" />
+
+
+
+
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt
index 953ee554c..4692799c4 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/HabitsDirFinder.kt
@@ -20,6 +20,7 @@ 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.ShowHabitGroupMenuPresenter
import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitMenuPresenter
import java.io.File
import javax.inject.Inject
@@ -33,3 +34,13 @@ constructor(
return androidDirFinder.getFilesDir("CSV")!!
}
}
+
+class HabitGroupsDirFinder @Inject
+constructor(
+ private val androidDirFinder: AndroidDirFinder
+) : ShowHabitGroupMenuPresenter.System, ListHabitsBehavior.DirFinder {
+
+ override fun getCSVOutputDir(): File {
+ return androidDirFinder.getFilesDir("CSV")!!
+ }
+}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt
index c6ad6fae8..4441cdbdc 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/ListHabitsScreen.kt
@@ -45,6 +45,7 @@ import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.tasks.TaskRunner
@@ -188,6 +189,11 @@ class ListHabitsScreen
activity.startActivity(intent)
}
+ override fun showHabitGroupScreen(hgr: HabitGroup) {
+ val intent = intentFactory.startShowHabitGroupActivity(activity, hgr)
+ activity.startActivity(intent)
+ }
+
fun showImportScreen() {
val intent = intentFactory.openDocument()
activity.startActivityForResult(intent, REQUEST_OPEN_DOCUMENT)
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt
index 6f266a207..264978c66 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListAdapter.kt
@@ -102,6 +102,14 @@ class HabitCardListAdapter @Inject constructor(
return cache.getHabitByPosition(position)
}
+ fun getHabit(position: Int): Habit? {
+ return cache.getHabitByPosition(position)
+ }
+
+ fun getHabitGroup(position: Int): HabitGroup? {
+ return cache.getHabitGroupByPosition(position)
+ }
+
override fun getItemCount(): Int {
return cache.itemCount
}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt
index a70805139..9ae4243c6 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/list/views/HabitCardListController.kt
@@ -114,8 +114,15 @@ class HabitCardListController @Inject constructor(
*/
internal inner class NormalMode : Mode {
override fun onItemClick(position: Int) {
- val habit = adapter.getItem(position) ?: return
- behavior.onClickHabit(habit)
+ val habit = adapter.getHabit(position)
+ if (habit != null) {
+ behavior.onClickHabit(habit)
+ } else {
+ val hgr = adapter.getHabitGroup(position)
+ if (hgr != null) {
+ behavior.onClickHabitGroup(hgr)
+ }
+ }
}
override fun onItemLongClick(position: Int): Boolean {
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupActivity.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupActivity.kt
new file mode 100644
index 000000000..97bab67c2
--- /dev/null
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupActivity.kt
@@ -0,0 +1,152 @@
+package org.isoron.uhabits.activities.habits.show
+
+import android.content.ContentUris
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import androidx.appcompat.app.AppCompatActivity
+import kotlinx.coroutines.CoroutineScope
+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.HabitGroupsDirFinder
+import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
+import org.isoron.uhabits.core.commands.Command
+import org.isoron.uhabits.core.commands.CommandRunner
+import org.isoron.uhabits.core.models.HabitGroup
+import org.isoron.uhabits.core.preferences.Preferences
+import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
+import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitGroupMenuPresenter
+import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitGroupPresenter
+import org.isoron.uhabits.intents.IntentFactory
+import org.isoron.uhabits.utils.dismissCurrentAndShow
+import org.isoron.uhabits.utils.dismissCurrentDialog
+import org.isoron.uhabits.utils.showMessage
+import org.isoron.uhabits.utils.showSendFileScreen
+import org.isoron.uhabits.widgets.WidgetUpdater
+
+class ShowHabitGroupActivity : AppCompatActivity(), CommandRunner.Listener {
+
+ private lateinit var commandRunner: CommandRunner
+ private lateinit var menu: ShowHabitGroupMenu
+ private lateinit var view: ShowHabitGroupView
+ private lateinit var habitGroup: HabitGroup
+ private lateinit var preferences: Preferences
+ private lateinit var themeSwitcher: AndroidThemeSwitcher
+ private lateinit var widgetUpdater: WidgetUpdater
+
+ private val scope = CoroutineScope(Dispatchers.Main)
+ private lateinit var presenter: ShowHabitGroupPresenter
+ private val screen = Screen()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val appComponent = (applicationContext as HabitsApplication).component
+ val habitGroupList = appComponent.habitGroupList
+ habitGroup = habitGroupList.getById(ContentUris.parseId(intent.data!!))!!
+ preferences = appComponent.preferences
+ commandRunner = appComponent.commandRunner
+ widgetUpdater = appComponent.widgetUpdater
+
+ themeSwitcher = AndroidThemeSwitcher(this, preferences)
+ themeSwitcher.apply()
+
+ presenter = ShowHabitGroupPresenter(
+ commandRunner = commandRunner,
+ habitGroup = habitGroup,
+ preferences = preferences,
+ screen = screen
+ )
+
+ view = ShowHabitGroupView(this)
+
+ val menuPresenter = ShowHabitGroupMenuPresenter(
+ commandRunner = commandRunner,
+ habitGroup = habitGroup,
+ habitGroupList = habitGroupList,
+ screen = screen,
+ system = HabitGroupsDirFinder(AndroidDirFinder(this)),
+ taskRunner = appComponent.taskRunner
+ )
+
+ menu = ShowHabitGroupMenu(
+ activity = this,
+ presenter = menuPresenter,
+ preferences = preferences
+ )
+
+ view.setListener(presenter)
+ setContentView(view)
+ }
+
+ override fun onCreateOptionsMenu(m: Menu): Boolean {
+ return menu.onCreateOptionsMenu(m)
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ return menu.onOptionsItemSelected(item)
+ }
+
+ override fun onResume() {
+ super.onResume()
+ commandRunner.addListener(this)
+ screen.refresh()
+ }
+
+ override fun onPause() {
+ dismissCurrentDialog()
+ commandRunner.removeListener(this)
+ super.onPause()
+ }
+
+ override fun onCommandFinished(command: Command) {
+ screen.refresh()
+ }
+
+ inner class Screen : ShowHabitGroupMenuPresenter.Screen, ShowHabitGroupPresenter.Screen {
+ override fun updateWidgets() {
+ widgetUpdater.updateWidgets()
+ }
+
+ override fun refresh() {
+ scope.launch {
+ view.setState(
+ ShowHabitGroupPresenter.buildState(
+ habitGroup = habitGroup,
+ preferences = preferences,
+ theme = themeSwitcher.currentTheme
+ )
+ )
+ }
+ }
+
+ override fun showEditHabitGroupScreen(habitGroup: HabitGroup) {
+ startActivity(IntentFactory().startEditGroupActivity(this@ShowHabitGroupActivity, habitGroup))
+ }
+
+ override fun showMessage(m: ShowHabitGroupMenuPresenter.Message?) {
+ when (m) {
+ ShowHabitGroupMenuPresenter.Message.COULD_NOT_EXPORT -> {
+ showMessage(resources.getString(R.string.could_not_export))
+ }
+ else -> {}
+ }
+ }
+
+ override fun showSendFileScreen(filename: String) {
+ this@ShowHabitGroupActivity.showSendFileScreen(filename)
+ }
+
+ override fun showDeleteConfirmationScreen(callback: OnConfirmedCallback) {
+ ConfirmDeleteDialog(this@ShowHabitGroupActivity, callback, 1).dismissCurrentAndShow()
+ }
+
+ override fun close() {
+ this@ShowHabitGroupActivity.finish()
+ }
+ }
+}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupMenu.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupMenu.kt
new file mode 100644
index 000000000..4ee4eedb5
--- /dev/null
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupMenu.kt
@@ -0,0 +1,32 @@
+package org.isoron.uhabits.activities.habits.show
+
+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.ShowHabitGroupMenuPresenter
+
+class ShowHabitGroupMenu(
+ val activity: ShowHabitGroupActivity,
+ val presenter: ShowHabitGroupMenuPresenter,
+ val preferences: Preferences
+) {
+ fun onCreateOptionsMenu(menu: Menu): Boolean {
+ activity.menuInflater.inflate(R.menu.show_habit_group, menu)
+ return true
+ }
+
+ fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.action_edit_habit_group -> {
+ presenter.onEditHabit()
+ return true
+ }
+ R.id.action_delete -> {
+ presenter.onDeleteHabit()
+ return true
+ }
+ }
+ return false
+ }
+}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupView.kt b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupView.kt
new file mode 100644
index 000000000..8575a38f5
--- /dev/null
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/activities/habits/show/ShowHabitGroupView.kt
@@ -0,0 +1,35 @@
+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.ShowHabitGroupPresenter
+import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitGroupState
+import org.isoron.uhabits.databinding.ShowHabitGroupBinding
+import org.isoron.uhabits.utils.setupToolbar
+
+class ShowHabitGroupView(context: Context) : FrameLayout(context) {
+ private val binding = ShowHabitGroupBinding.inflate(LayoutInflater.from(context))
+
+ init {
+ addView(binding.root)
+ }
+
+ fun setState(data: ShowHabitGroupState) {
+ setupToolbar(
+ binding.toolbar,
+ title = data.title,
+ color = data.color,
+ theme = data.theme
+ )
+ binding.subtitleCard.setState(data.subtitle)
+ binding.overviewCard.setState(data.overview)
+ binding.notesCard.setState(data.notes)
+ binding.streakCard.setState(data.streaks)
+ binding.scoreCard.setState(data.scores)
+ }
+
+ fun setListener(presenter: ShowHabitGroupPresenter) {
+ binding.scoreCard.setListener(presenter.scoreCardPresenter)
+ }
+}
diff --git a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt
index b2beb3d46..866bfe11c 100644
--- a/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt
+++ b/uhabits-android/src/main/java/org/isoron/uhabits/intents/IntentFactory.kt
@@ -27,6 +27,7 @@ import org.isoron.uhabits.activities.about.AboutActivity
import org.isoron.uhabits.activities.habits.edit.EditHabitActivity
import org.isoron.uhabits.activities.habits.edit.EditHabitGroupActivity
import org.isoron.uhabits.activities.habits.show.ShowHabitActivity
+import org.isoron.uhabits.activities.habits.show.ShowHabitGroupActivity
import org.isoron.uhabits.activities.intro.IntroActivity
import org.isoron.uhabits.activities.settings.SettingsActivity
import org.isoron.uhabits.core.models.Habit
@@ -67,6 +68,11 @@ class IntentFactory
data = Uri.parse(habit.uriString)
}
+ fun startShowHabitGroupActivity(context: Context, habitGroup: HabitGroup) =
+ Intent(context, ShowHabitGroupActivity::class.java).apply {
+ data = Uri.parse(habitGroup.uriString)
+ }
+
fun viewFAQ(context: Context) =
buildViewIntent(context.getString(R.string.helpURL))
diff --git a/uhabits-android/src/main/res/menu/show_habit_group.xml b/uhabits-android/src/main/res/menu/show_habit_group.xml
new file mode 100644
index 000000000..6f1e43a82
--- /dev/null
+++ b/uhabits-android/src/main/res/menu/show_habit_group.xml
@@ -0,0 +1,35 @@
+
+
+
+
\ No newline at end of file
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/StreakList.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/StreakList.kt
index 494209fce..d5df21f1a 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/StreakList.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/models/StreakList.kt
@@ -74,12 +74,12 @@ class StreakList {
from: Timestamp,
to: Timestamp
) {
+ if (habitList.isEmpty) return
var current = from
var streakRunning = false
var streakStart = from
while (current <= to) {
- if (habitList.all { it.streaks.isInStreaks(current) } && !streakRunning
- ) {
+ if (habitList.all { it.streaks.isInStreaks(current) } && !streakRunning) {
streakStart = current
streakRunning = true
} else if (streakRunning) {
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt
index b66b08be6..73ac30d23 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/list/ListHabitsBehavior.kt
@@ -22,6 +22,7 @@ import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.HabitList
import org.isoron.uhabits.core.models.HabitType
import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
@@ -51,6 +52,10 @@ open class ListHabitsBehavior @Inject constructor(
screen.showHabitScreen(h)
}
+ fun onClickHabitGroup(hgr: HabitGroup) {
+ screen.showHabitGroupScreen(hgr)
+ }
+
fun onEdit(habit: Habit, timestamp: Timestamp?) {
val entry = habit.computedEntries.get(timestamp!!)
if (habit.type == HabitType.NUMERICAL) {
@@ -178,6 +183,7 @@ open class ListHabitsBehavior @Inject constructor(
interface Screen {
fun showHabitScreen(h: Habit)
+ fun showHabitGroupScreen(hgr: HabitGroup)
fun showIntroScreen()
fun showMessage(m: Message)
fun showNumberPopup(
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroup.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroup.kt
new file mode 100644
index 000000000..56359832e
--- /dev/null
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroup.kt
@@ -0,0 +1,78 @@
+package org.isoron.uhabits.core.ui.screens.habits.show
+
+import org.isoron.uhabits.core.commands.CommandRunner
+import org.isoron.uhabits.core.models.HabitGroup
+import org.isoron.uhabits.core.models.PaletteColor
+import org.isoron.uhabits.core.preferences.Preferences
+import org.isoron.uhabits.core.ui.screens.habits.show.views.NotesCardPresenter
+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.OverviewCardState
+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.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.SubtitleCardState
+import org.isoron.uhabits.core.ui.views.Theme
+
+data class ShowHabitGroupState(
+ val title: String = "",
+ val color: PaletteColor = PaletteColor(1),
+ val subtitle: SubtitleCardState,
+ val overview: OverviewCardState,
+ val notes: NotesCardState,
+ val streaks: StreakCardState,
+ val scores: ScoreCardState,
+ val theme: Theme
+)
+
+class ShowHabitGroupPresenter(
+ val habitGroup: HabitGroup,
+ val preferences: Preferences,
+ val screen: Screen,
+ val commandRunner: CommandRunner
+) {
+ val scoreCardPresenter = ScoreCardPresenter(
+ preferences = preferences,
+ screen = screen
+ )
+
+ companion object {
+ fun buildState(
+ habitGroup: HabitGroup,
+ preferences: Preferences,
+ theme: Theme
+ ): ShowHabitGroupState {
+ return ShowHabitGroupState(
+ title = habitGroup.name,
+ color = habitGroup.color,
+ theme = theme,
+ subtitle = SubtitleCardPresenter.buildState(
+ habitGroup = habitGroup,
+ theme = theme
+ ),
+ overview = OverviewCardPresenter.buildState(
+ habitGroup = habitGroup,
+ theme = theme
+ ),
+ notes = NotesCardPresenter.buildState(
+ habitGroup = habitGroup
+ ),
+ streaks = StreakCartPresenter.buildState(
+ habitGroup = habitGroup,
+ theme = theme
+ ),
+ scores = ScoreCardPresenter.buildState(
+ spinnerPosition = preferences.scoreCardSpinnerPosition,
+ habitGroup = habitGroup,
+ firstWeekday = preferences.firstWeekdayInt,
+ theme = theme
+ )
+ )
+ }
+ }
+
+ interface Screen :
+ ScoreCardPresenter.Screen
+}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroupMenuPresenter.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroupMenuPresenter.kt
new file mode 100644
index 000000000..196d9a8a1
--- /dev/null
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/ShowHabitGroupMenuPresenter.kt
@@ -0,0 +1,46 @@
+package org.isoron.uhabits.core.ui.screens.habits.show
+
+import org.isoron.uhabits.core.commands.CommandRunner
+import org.isoron.uhabits.core.commands.DeleteHabitGroupsCommand
+import org.isoron.uhabits.core.models.HabitGroup
+import org.isoron.uhabits.core.models.HabitGroupList
+import org.isoron.uhabits.core.tasks.TaskRunner
+import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
+import java.io.File
+
+class ShowHabitGroupMenuPresenter(
+ private val commandRunner: CommandRunner,
+ private val habitGroup: HabitGroup,
+ private val habitGroupList: HabitGroupList,
+ private val screen: Screen,
+ private val system: System,
+ private val taskRunner: TaskRunner
+) {
+ fun onEditHabit() {
+ screen.showEditHabitGroupScreen(habitGroup)
+ }
+
+ fun onDeleteHabit() {
+ screen.showDeleteConfirmationScreen {
+ commandRunner.run(DeleteHabitGroupsCommand(habitGroupList, listOf(habitGroup)))
+ screen.close()
+ }
+ }
+
+ enum class Message {
+ COULD_NOT_EXPORT
+ }
+
+ interface Screen {
+ fun showEditHabitGroupScreen(habitGroup: HabitGroup)
+ fun showMessage(m: Message?)
+ fun showSendFileScreen(filename: String)
+ fun showDeleteConfirmationScreen(callback: OnConfirmedCallback)
+ fun close()
+ fun refresh()
+ }
+
+ interface System {
+ fun getCSVOutputDir(): File
+ }
+}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/NotesCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/NotesCard.kt
index 47049bb83..8f608b98b 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/NotesCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/NotesCard.kt
@@ -20,6 +20,7 @@
package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
data class NotesCardState(
val description: String
@@ -30,5 +31,9 @@ class NotesCardPresenter {
fun buildState(habit: Habit) = NotesCardState(
description = habit.description
)
+
+ fun buildState(habitGroup: HabitGroup) = NotesCardState(
+ description = habitGroup.description
+ )
}
}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt
index e15c08403..d895b7523 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/OverviewCard.kt
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.ui.views.Theme
import org.isoron.uhabits.core.utils.DateUtils
@@ -57,5 +58,27 @@ class OverviewCardPresenter {
theme = theme
)
}
+
+ fun buildState(habitGroup: HabitGroup, theme: Theme): OverviewCardState {
+ val today = DateUtils.getTodayWithOffset()
+ val lastMonth = today.minus(30)
+ val lastYear = today.minus(365)
+ val scores = habitGroup.scores
+ val scoreToday = scores[today].value.toFloat()
+ val scoreLastMonth = scores[lastMonth].value.toFloat()
+ val scoreLastYear = scores[lastYear].value.toFloat()
+ val totalCount = habitGroup.habitList.sumOf { habit ->
+ habit.originalEntries.getKnown().count { it.value == Entry.YES_MANUAL }
+ .toLong()
+ }
+ return OverviewCardState(
+ color = habitGroup.color,
+ scoreToday = scoreToday,
+ scoreMonthDiff = scoreToday - scoreLastMonth,
+ scoreYearDiff = scoreToday - scoreLastYear,
+ totalCount = totalCount,
+ theme = theme
+ )
+ }
}
}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt
index b85d75204..511bb757a 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/ScoreCard.kt
@@ -20,6 +20,7 @@
package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Score
import org.isoron.uhabits.core.preferences.Preferences
@@ -83,6 +84,45 @@ class ScoreCardPresenter(
theme = theme
)
}
+
+ fun buildState(
+ habitGroup: HabitGroup,
+ firstWeekday: Int,
+ spinnerPosition: Int,
+ theme: Theme
+ ): ScoreCardState {
+ val bucketSize = BUCKET_SIZES[spinnerPosition]
+ val today = DateUtils.getTodayWithOffset()
+ val oldest = if (habitGroup.habitList.isEmpty) {
+ today
+ } else {
+ habitGroup.habitList.minOf {
+ it.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
+ }
+ }
+
+ val field = getTruncateField(bucketSize)
+ val scores = habitGroup.scores.getByInterval(oldest, today).groupBy {
+ DateUtils.truncate(field, it.timestamp, firstWeekday)
+ }.map { (timestamp, scores) ->
+ Score(
+ timestamp,
+ scores.map {
+ it.value
+ }.average()
+ )
+ }.sortedBy {
+ it.timestamp
+ }.reversed()
+
+ return ScoreCardState(
+ color = habitGroup.color,
+ scores = scores,
+ bucketSize = bucketSize,
+ spinnerPosition = spinnerPosition,
+ theme = theme
+ )
+ }
}
fun onSpinnerPosition(position: Int) {
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/StreakCart.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/StreakCart.kt
index a242ab4b7..cb78e19b8 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/StreakCart.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/StreakCart.kt
@@ -20,6 +20,7 @@
package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Streak
import org.isoron.uhabits.core.ui.views.Theme
@@ -39,5 +40,13 @@ class StreakCartPresenter {
theme = theme
)
}
+
+ fun buildState(habitGroup: HabitGroup, theme: Theme): StreakCardState {
+ return StreakCardState(
+ color = habitGroup.color,
+ bestStreaks = habitGroup.streaks.getBest(10),
+ theme = theme
+ )
+ }
}
}
diff --git a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt
index fb839933c..e9b569c61 100644
--- a/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt
+++ b/uhabits-core/src/jvmMain/java/org/isoron/uhabits/core/ui/screens/habits/show/views/SubtitleCard.kt
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit
+import org.isoron.uhabits.core.models.HabitGroup
import org.isoron.uhabits.core.models.NumericalHabitType
import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.models.Reminder
@@ -54,5 +55,17 @@ class SubtitleCardPresenter {
unit = habit.unit,
theme = theme
)
+
+ fun buildState(
+ habitGroup: HabitGroup,
+ theme: Theme
+ ): SubtitleCardState = SubtitleCardState(
+ color = habitGroup.color,
+ frequency = Frequency.DAILY,
+ isNumerical = false,
+ question = habitGroup.question,
+ reminder = habitGroup.reminder,
+ theme = theme
+ )
}
}