Add show habit group activity

pull/2020/head
Dharanish 1 year ago
parent cac62c0278
commit a2cf78f823

@ -80,6 +80,14 @@
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.habits.show.ShowHabitGroupActivity"
android:label="@string/title_activity_show_habit">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.habits.list.ListHabitsActivity" />
</activity>
<activity
android:name=".activities.settings.SettingsActivity"
android:label="@string/settings">

@ -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")!!
}
}

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

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

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

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

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

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

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

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2016-2021 Álinson Santos Xavier <git@axavier.org>
~
~ 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/>.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_delete"
android:title="@string/delete"
app:showAsAction="never"/>
<item
android:id="@+id/action_edit_habit_group"
android:icon="?iconEdit"
android:title="@string/edit"
app:showAsAction="ifRoom"/>
</menu>

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

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

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

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

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

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

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

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

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

Loading…
Cancel
Save