mirror of https://github.com/iSoron/uhabits.git
parent
262b49d025
commit
832d51a055
@ -0,0 +1,232 @@
|
|||||||
|
package org.isoron.uhabits.activities.habits.edit
|
||||||
|
|
||||||
|
import android.content.res.ColorStateList
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Html
|
||||||
|
import android.text.Spanned
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.view.View
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.DialogFragment
|
||||||
|
import com.android.datetimepicker.time.RadialPickerLayout
|
||||||
|
import com.android.datetimepicker.time.TimePickerDialog
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
|
import org.isoron.uhabits.HabitsApplication
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.AndroidThemeSwitcher
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.WeekdayPickerDialog
|
||||||
|
import org.isoron.uhabits.core.commands.CommandRunner
|
||||||
|
import org.isoron.uhabits.core.commands.CreateHabitGroupCommand
|
||||||
|
import org.isoron.uhabits.core.commands.EditHabitGroupCommand
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
|
import org.isoron.uhabits.core.models.Reminder
|
||||||
|
import org.isoron.uhabits.core.models.WeekdayList
|
||||||
|
import org.isoron.uhabits.databinding.ActivityEditHabitGroupBinding
|
||||||
|
import org.isoron.uhabits.utils.ColorUtils
|
||||||
|
import org.isoron.uhabits.utils.dismissCurrentAndShow
|
||||||
|
import org.isoron.uhabits.utils.formatTime
|
||||||
|
import org.isoron.uhabits.utils.toFormattedString
|
||||||
|
|
||||||
|
class EditHabitGroupActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var themeSwitcher: AndroidThemeSwitcher
|
||||||
|
private lateinit var binding: ActivityEditHabitGroupBinding
|
||||||
|
private lateinit var commandRunner: CommandRunner
|
||||||
|
|
||||||
|
var habitGroupId = -1L
|
||||||
|
var color = PaletteColor(11)
|
||||||
|
var androidColor = 0
|
||||||
|
var reminderHour = -1
|
||||||
|
var reminderMin = -1
|
||||||
|
var reminderDays: WeekdayList = WeekdayList.EVERY_DAY
|
||||||
|
|
||||||
|
override fun onCreate(state: Bundle?) {
|
||||||
|
super.onCreate(state)
|
||||||
|
|
||||||
|
val component = (application as HabitsApplication).component
|
||||||
|
themeSwitcher = AndroidThemeSwitcher(this, component.preferences)
|
||||||
|
themeSwitcher.apply()
|
||||||
|
|
||||||
|
binding = ActivityEditHabitGroupBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
if (intent.hasExtra("habitGroupId")) {
|
||||||
|
binding.toolbar.title = getString(R.string.edit_habit_group)
|
||||||
|
habitGroupId = intent.getLongExtra("habitId", -1)
|
||||||
|
val hgr = component.habitGroupList.getById(habitGroupId)!!
|
||||||
|
color = hgr.color
|
||||||
|
hgr.reminder?.let {
|
||||||
|
reminderHour = it.hour
|
||||||
|
reminderMin = it.minute
|
||||||
|
reminderDays = it.days
|
||||||
|
}
|
||||||
|
binding.nameInput.setText(hgr.name)
|
||||||
|
binding.questionInput.setText(hgr.question)
|
||||||
|
binding.notesInput.setText(hgr.description)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != null) {
|
||||||
|
habitGroupId = state.getLong("habitGroupId")
|
||||||
|
color = PaletteColor(state.getInt("paletteColor"))
|
||||||
|
reminderHour = state.getInt("reminderHour")
|
||||||
|
reminderMin = state.getInt("reminderMin")
|
||||||
|
reminderDays = WeekdayList(state.getInt("reminderDays"))
|
||||||
|
}
|
||||||
|
|
||||||
|
updateColors()
|
||||||
|
|
||||||
|
setSupportActionBar(binding.toolbar)
|
||||||
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
|
supportActionBar?.setDisplayShowHomeEnabled(true)
|
||||||
|
supportActionBar?.elevation = 10.0f
|
||||||
|
|
||||||
|
val colorPickerDialogFactory = ColorPickerDialogFactory(this)
|
||||||
|
binding.colorButton.setOnClickListener {
|
||||||
|
val picker = colorPickerDialogFactory.create(color, themeSwitcher.currentTheme)
|
||||||
|
picker.setListener { paletteColor ->
|
||||||
|
this.color = paletteColor
|
||||||
|
updateColors()
|
||||||
|
}
|
||||||
|
picker.dismissCurrentAndShow(supportFragmentManager, "colorPicker")
|
||||||
|
}
|
||||||
|
|
||||||
|
populateReminder()
|
||||||
|
binding.reminderTimePicker.setOnClickListener {
|
||||||
|
val currentHour = if (reminderHour >= 0) reminderHour else 8
|
||||||
|
val currentMin = if (reminderMin >= 0) reminderMin else 0
|
||||||
|
val is24HourMode = DateFormat.is24HourFormat(this)
|
||||||
|
val dialog = TimePickerDialog.newInstance(
|
||||||
|
object : TimePickerDialog.OnTimeSetListener {
|
||||||
|
override fun onTimeSet(view: RadialPickerLayout?, hourOfDay: Int, minute: Int) {
|
||||||
|
reminderHour = hourOfDay
|
||||||
|
reminderMin = minute
|
||||||
|
populateReminder()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTimeCleared(view: RadialPickerLayout?) {
|
||||||
|
reminderHour = -1
|
||||||
|
reminderMin = -1
|
||||||
|
reminderDays = WeekdayList.EVERY_DAY
|
||||||
|
populateReminder()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
currentHour,
|
||||||
|
currentMin,
|
||||||
|
is24HourMode,
|
||||||
|
androidColor
|
||||||
|
)
|
||||||
|
dialog.dismissCurrentAndShow(supportFragmentManager, "timePicker")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.reminderDatePicker.setOnClickListener {
|
||||||
|
val dialog = WeekdayPickerDialog()
|
||||||
|
|
||||||
|
dialog.setListener { days: WeekdayList ->
|
||||||
|
reminderDays = days
|
||||||
|
if (reminderDays.isEmpty) reminderDays = WeekdayList.EVERY_DAY
|
||||||
|
populateReminder()
|
||||||
|
}
|
||||||
|
dialog.setSelectedDays(reminderDays)
|
||||||
|
dialog.dismissCurrentAndShow(supportFragmentManager, "dayPicker")
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.buttonSave.setOnClickListener {
|
||||||
|
if (validate()) save()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (fragment in supportFragmentManager.fragments) {
|
||||||
|
(fragment as DialogFragment).dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun save() {
|
||||||
|
val component = (application as HabitsApplication).component
|
||||||
|
val hgr = component.modelFactory.buildHabitGroup()
|
||||||
|
|
||||||
|
var original: HabitGroup? = null
|
||||||
|
if (habitGroupId >= 0) {
|
||||||
|
original = component.habitGroupList.getById(habitGroupId)!!
|
||||||
|
hgr.copyFrom(original)
|
||||||
|
}
|
||||||
|
|
||||||
|
hgr.name = binding.nameInput.text.trim().toString()
|
||||||
|
hgr.question = binding.questionInput.text.trim().toString()
|
||||||
|
hgr.description = binding.notesInput.text.trim().toString()
|
||||||
|
hgr.color = color
|
||||||
|
if (reminderHour >= 0) {
|
||||||
|
hgr.reminder = Reminder(reminderHour, reminderMin, reminderDays)
|
||||||
|
} else {
|
||||||
|
hgr.reminder = null
|
||||||
|
}
|
||||||
|
|
||||||
|
val command = if (habitGroupId >= 0) {
|
||||||
|
EditHabitGroupCommand(
|
||||||
|
component.habitGroupList,
|
||||||
|
habitGroupId,
|
||||||
|
hgr
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
CreateHabitGroupCommand(
|
||||||
|
component.modelFactory,
|
||||||
|
component.habitGroupList,
|
||||||
|
hgr
|
||||||
|
)
|
||||||
|
}
|
||||||
|
component.commandRunner.run(command)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validate(): Boolean {
|
||||||
|
var isValid = true
|
||||||
|
if (binding.nameInput.text.isEmpty()) {
|
||||||
|
binding.nameInput.error = getFormattedValidationError(R.string.validation_cannot_be_blank)
|
||||||
|
isValid = false
|
||||||
|
}
|
||||||
|
return isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun populateReminder() {
|
||||||
|
if (reminderHour < 0) {
|
||||||
|
binding.reminderTimePicker.text = getString(R.string.reminder_off)
|
||||||
|
binding.reminderDatePicker.visibility = View.GONE
|
||||||
|
binding.reminderDivider.visibility = View.GONE
|
||||||
|
} else {
|
||||||
|
val time = formatTime(this, reminderHour, reminderMin)
|
||||||
|
binding.reminderTimePicker.text = time
|
||||||
|
binding.reminderDatePicker.visibility = View.VISIBLE
|
||||||
|
binding.reminderDivider.visibility = View.VISIBLE
|
||||||
|
binding.reminderDatePicker.text = reminderDays.toFormattedString(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateColors() {
|
||||||
|
androidColor = themeSwitcher.currentTheme.color(color).toInt()
|
||||||
|
binding.colorButton.backgroundTintList = ColorStateList.valueOf(androidColor)
|
||||||
|
if (!themeSwitcher.isNightMode) {
|
||||||
|
val darkerAndroidColor = ColorUtils.mixColors(Color.BLACK, androidColor, 0.15f)
|
||||||
|
window.statusBarColor = darkerAndroidColor
|
||||||
|
binding.toolbar.setBackgroundColor(androidColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFormattedValidationError(@StringRes resId: Int): Spanned {
|
||||||
|
val html = "<font color=#FFFFFF>${getString(resId)}</font>"
|
||||||
|
return Html.fromHtml(html)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(state: Bundle) {
|
||||||
|
super.onSaveInstanceState(state)
|
||||||
|
with(state) {
|
||||||
|
putLong("habitGroupId", habitGroupId)
|
||||||
|
putInt("paletteColor", color.paletteIndex)
|
||||||
|
putInt("androidColor", androidColor)
|
||||||
|
putInt("reminderHour", reminderHour)
|
||||||
|
putInt("reminderMin", reminderMin)
|
||||||
|
putInt("reminderDays", reminderDays.toInteger())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,72 @@
|
|||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/container"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
style="@style/Toolbar"
|
||||||
|
app:popupTheme="?toolbarPopupTheme"
|
||||||
|
android:layout_alignParentTop="true"/>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/scrollView"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/toolbar"
|
||||||
|
android:background="?windowBackgroundColor"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
style="@style/CardList"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.SubtitleCardView
|
||||||
|
android:id="@+id/subtitleCard"
|
||||||
|
style="@style/ShowHabit.Subtitle"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.NotesCardView
|
||||||
|
android:id="@+id/notesCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:gravity="center" />
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.OverviewCardView
|
||||||
|
android:id="@+id/overviewCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:paddingTop="12dp"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.ScoreCardView
|
||||||
|
android:id="@+id/scoreCard"
|
||||||
|
style="@style/Card"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<org.isoron.uhabits.activities.habits.show.views.StreakCardView
|
||||||
|
android:id="@+id/streakCard"
|
||||||
|
style="@style/Card"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
@ -0,0 +1,18 @@
|
|||||||
|
package org.isoron.uhabits.core.commands
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroupList
|
||||||
|
import org.isoron.uhabits.core.models.ModelFactory
|
||||||
|
|
||||||
|
data class CreateHabitGroupCommand(
|
||||||
|
val modelFactory: ModelFactory,
|
||||||
|
val habitGroupList: HabitGroupList,
|
||||||
|
val model: HabitGroup
|
||||||
|
) : Command {
|
||||||
|
override fun run() {
|
||||||
|
val habitGroup = modelFactory.buildHabitGroup()
|
||||||
|
habitGroup.copyFrom(model)
|
||||||
|
habitGroupList.add(habitGroup)
|
||||||
|
habitGroup.recompute()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package org.isoron.uhabits.core.commands
|
||||||
|
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroup
|
||||||
|
import org.isoron.uhabits.core.models.HabitGroupList
|
||||||
|
import org.isoron.uhabits.core.models.HabitNotFoundException
|
||||||
|
|
||||||
|
data class EditHabitGroupCommand(
|
||||||
|
val habitGroupList: HabitGroupList,
|
||||||
|
val habitGroupId: Long,
|
||||||
|
val modified: HabitGroup
|
||||||
|
) : Command {
|
||||||
|
override fun run() {
|
||||||
|
val habitGroup = habitGroupList.getById(habitGroupId) ?: throw HabitNotFoundException()
|
||||||
|
habitGroup.copyFrom(modified)
|
||||||
|
habitGroupList.update(habitGroup)
|
||||||
|
habitGroup.observable.notifyListeners()
|
||||||
|
habitGroup.recompute()
|
||||||
|
habitGroupList.resort()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in new issue