mirror of
https://github.com/iSoron/uhabits.git
synced 2025-12-06 09:08:52 -06:00
Merge pull request #1103 from vbh/feat-1074
Add notes to specific dates
This commit is contained in:
@@ -116,15 +116,7 @@ class NumberButtonViewTest : BaseViewTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testClick_shortToggleDisabled() {
|
fun testClick() {
|
||||||
prefs.isShortToggleEnabled = false
|
|
||||||
view.performClick()
|
|
||||||
assertFalse(edited)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testClick_shortToggleEnabled() {
|
|
||||||
prefs.isShortToggleEnabled = true
|
|
||||||
view.performClick()
|
view.performClick()
|
||||||
assertTrue(edited)
|
assertTrue(edited)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class PerformanceTest : BaseAndroidTest() {
|
|||||||
val habit = fixtures.createEmptyHabit()
|
val habit = fixtures.createEmptyHabit()
|
||||||
for (i in 0..4999) {
|
for (i in 0..4999) {
|
||||||
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
|
val timestamp: Timestamp = Timestamp(i * DAY_LENGTH)
|
||||||
CreateRepetitionCommand(habitList, habit, timestamp, 1).run()
|
CreateRepetitionCommand(habitList, habit, timestamp, 1, "").run()
|
||||||
}
|
}
|
||||||
db.setTransactionSuccessful()
|
db.setTransactionSuccessful()
|
||||||
db.endTransaction()
|
db.endTransaction()
|
||||||
|
|||||||
@@ -49,23 +49,12 @@ class AndroidDataView(
|
|||||||
override fun onShowPress(e: MotionEvent?) = Unit
|
override fun onShowPress(e: MotionEvent?) = Unit
|
||||||
|
|
||||||
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
override fun onSingleTapUp(e: MotionEvent?): Boolean {
|
||||||
val x: Float
|
return handleClick(e, true)
|
||||||
val y: Float
|
|
||||||
try {
|
|
||||||
val pointerId = e!!.getPointerId(0)
|
|
||||||
x = e.getX(pointerId)
|
|
||||||
y = e.getY(pointerId)
|
|
||||||
} catch (ex: RuntimeException) {
|
|
||||||
// Android often throws IllegalArgumentException here. Apparently,
|
|
||||||
// the pointer id may become invalid shortly after calling
|
|
||||||
// e.getPointerId.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongPress(e: MotionEvent?) = Unit
|
override fun onLongPress(e: MotionEvent?) {
|
||||||
|
handleClick(e)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onScroll(
|
override fun onScroll(
|
||||||
e1: MotionEvent?,
|
e1: MotionEvent?,
|
||||||
@@ -137,4 +126,22 @@ class AndroidDataView(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleClick(e: MotionEvent?, isSingleTap: Boolean = false): Boolean {
|
||||||
|
val x: Float
|
||||||
|
val y: Float
|
||||||
|
try {
|
||||||
|
val pointerId = e!!.getPointerId(0)
|
||||||
|
x = e.getX(pointerId)
|
||||||
|
y = e.getY(pointerId)
|
||||||
|
} catch (ex: RuntimeException) {
|
||||||
|
// Android often throws IllegalArgumentException here. Apparently,
|
||||||
|
// the pointer id may become invalid shortly after calling
|
||||||
|
// e.getPointerId.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isSingleTap) view?.onClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
else view?.onLongClick(x / canvas.innerDensity, y / canvas.innerDensity)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
package org.isoron.uhabits.activities.common.dialogs
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Typeface
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
|
||||||
|
import android.widget.Button
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import org.isoron.platform.gui.toInt
|
||||||
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.NO
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.SKIP
|
||||||
|
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
|
||||||
|
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.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.Theme
|
||||||
|
import org.isoron.uhabits.databinding.CheckmarkDialogBinding
|
||||||
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
|
import org.isoron.uhabits.utils.InterfaceUtils
|
||||||
|
import org.isoron.uhabits.utils.StyledResources
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class CheckmarkDialog
|
||||||
|
@Inject constructor(
|
||||||
|
@ActivityContext private val context: Context,
|
||||||
|
private val preferences: Preferences,
|
||||||
|
) : View.OnClickListener {
|
||||||
|
|
||||||
|
private lateinit var binding: CheckmarkDialogBinding
|
||||||
|
private lateinit var fontAwesome: Typeface
|
||||||
|
private val allButtons = mutableListOf<Button>()
|
||||||
|
private var selectedButton: Button? = null
|
||||||
|
|
||||||
|
fun create(
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
|
paletteColor: PaletteColor,
|
||||||
|
callback: ListHabitsBehavior.CheckMarkDialogCallback,
|
||||||
|
theme: Theme,
|
||||||
|
): AlertDialog {
|
||||||
|
binding = CheckmarkDialogBinding.inflate(LayoutInflater.from(context))
|
||||||
|
fontAwesome = InterfaceUtils.getFontAwesome(context)!!
|
||||||
|
binding.etNotes.append(notes)
|
||||||
|
setUpButtons(value, theme.color(paletteColor).toInt())
|
||||||
|
|
||||||
|
val dialog = AlertDialog.Builder(context)
|
||||||
|
.setView(binding.root)
|
||||||
|
.setTitle(dateString)
|
||||||
|
.setPositiveButton(R.string.save) { _, _ ->
|
||||||
|
val newValue = when (selectedButton?.id) {
|
||||||
|
R.id.yesBtn -> YES_MANUAL
|
||||||
|
R.id.noBtn -> NO
|
||||||
|
R.id.skippedBtn -> SKIP
|
||||||
|
else -> UNKNOWN
|
||||||
|
}
|
||||||
|
callback.onNotesSaved(newValue, binding.etNotes.text.toString())
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
callback.onNotesDismissed()
|
||||||
|
}
|
||||||
|
.setOnDismissListener {
|
||||||
|
callback.onNotesDismissed()
|
||||||
|
}
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
binding.etNotes.requestFocus()
|
||||||
|
dialog.window?.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpButtons(value: Int, color: Int) {
|
||||||
|
val sres = StyledResources(context)
|
||||||
|
val mediumContrastColor = sres.getColor(R.attr.contrast60)
|
||||||
|
setButtonAttrs(binding.yesBtn, color)
|
||||||
|
setButtonAttrs(binding.noBtn, mediumContrastColor)
|
||||||
|
setButtonAttrs(binding.skippedBtn, color, visible = preferences.isSkipEnabled)
|
||||||
|
setButtonAttrs(binding.questionBtn, mediumContrastColor, visible = preferences.areQuestionMarksEnabled)
|
||||||
|
when (value) {
|
||||||
|
UNKNOWN -> if (preferences.areQuestionMarksEnabled) {
|
||||||
|
binding.questionBtn.performClick()
|
||||||
|
} else {
|
||||||
|
binding.noBtn.performClick()
|
||||||
|
}
|
||||||
|
SKIP -> binding.skippedBtn.performClick()
|
||||||
|
YES_MANUAL -> binding.yesBtn.performClick()
|
||||||
|
YES_AUTO, NO -> binding.noBtn.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setButtonAttrs(button: Button, color: Int, visible: Boolean = true) {
|
||||||
|
button.apply {
|
||||||
|
visibility = if (visible) View.VISIBLE else View.GONE
|
||||||
|
typeface = fontAwesome
|
||||||
|
setTextColor(color)
|
||||||
|
setOnClickListener(this@CheckmarkDialog)
|
||||||
|
}
|
||||||
|
allButtons.add(button)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick(v: View?) {
|
||||||
|
allButtons.forEach {
|
||||||
|
if (v?.id == it.id) {
|
||||||
|
it.isSelected = true
|
||||||
|
selectedButton = it
|
||||||
|
} else it.isSelected = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -63,9 +63,10 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
paletteColor = habit.color,
|
paletteColor = habit.color,
|
||||||
series = emptyList(),
|
series = emptyList(),
|
||||||
defaultSquare = HistoryChart.Square.OFF,
|
defaultSquare = HistoryChart.Square.OFF,
|
||||||
|
notesIndicators = emptyList(),
|
||||||
theme = themeSwitcher.currentTheme,
|
theme = themeSwitcher.currentTheme,
|
||||||
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
today = DateUtils.getTodayWithOffset().toLocalDate(),
|
||||||
onDateClickedListener = onDateClickedListener ?: OnDateClickedListener { },
|
onDateClickedListener = onDateClickedListener ?: object : OnDateClickedListener {},
|
||||||
padding = 10.0,
|
padding = 10.0,
|
||||||
)
|
)
|
||||||
dataView = AndroidDataView(context!!, null)
|
dataView = AndroidDataView(context!!, null)
|
||||||
@@ -103,6 +104,7 @@ class HistoryEditorDialog : AppCompatDialogFragment(), CommandRunner.Listener {
|
|||||||
)
|
)
|
||||||
chart?.series = model.series
|
chart?.series = model.series
|
||||||
chart?.defaultSquare = model.defaultSquare
|
chart?.defaultSquare = model.defaultSquare
|
||||||
|
chart?.notesIndicators = model.notesIndicators
|
||||||
dataView.postInvalidate()
|
dataView.postInvalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ class NumberPickerFactory
|
|||||||
fun create(
|
fun create(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
): AlertDialog {
|
): AlertDialog {
|
||||||
val inflater = LayoutInflater.from(context)
|
val inflater = LayoutInflater.from(context)
|
||||||
@@ -54,6 +56,7 @@ class NumberPickerFactory
|
|||||||
|
|
||||||
val picker = view.findViewById<NumberPicker>(R.id.picker)
|
val picker = view.findViewById<NumberPicker>(R.id.picker)
|
||||||
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
|
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
|
||||||
|
val etNotes = view.findViewById<EditText>(R.id.etNotes)
|
||||||
|
|
||||||
val watcherFilter: InputFilter = SeparatorWatcherInputFilter(picker2)
|
val watcherFilter: InputFilter = SeparatorWatcherInputFilter(picker2)
|
||||||
val numberPickerInputText = getNumberPickerInputText(picker)
|
val numberPickerInputText = getNumberPickerInputText(picker)
|
||||||
@@ -77,13 +80,18 @@ class NumberPickerFactory
|
|||||||
picker2.setFormatter { v -> String.format("%02d", v) }
|
picker2.setFormatter { v -> String.format("%02d", v) }
|
||||||
picker2.value = intValue % 100
|
picker2.value = intValue % 100
|
||||||
|
|
||||||
|
etNotes.setText(notes)
|
||||||
val dialog = AlertDialog.Builder(context)
|
val dialog = AlertDialog.Builder(context)
|
||||||
.setView(view)
|
.setView(view)
|
||||||
.setTitle(R.string.change_value)
|
.setTitle(dateString)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.save) { _, _ ->
|
||||||
picker.clearFocus()
|
picker.clearFocus()
|
||||||
val v = picker.value + 0.01 * picker2.value
|
val v = picker.value + 0.01 * picker2.value
|
||||||
callback.onNumberPicked(v)
|
val note = etNotes.text.toString()
|
||||||
|
callback.onNumberPicked(v, note)
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
callback.onNumberPickerDismissed()
|
||||||
}
|
}
|
||||||
.setOnDismissListener {
|
.setOnDismissListener {
|
||||||
callback.onNumberPickerDismissed()
|
callback.onNumberPickerDismissed()
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.content.Intent
|
|||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import dagger.Lazy
|
import dagger.Lazy
|
||||||
import org.isoron.uhabits.R
|
import org.isoron.uhabits.R
|
||||||
|
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory
|
||||||
@@ -89,6 +90,7 @@ class ListHabitsScreen
|
|||||||
private val importTaskFactory: ImportDataTaskFactory,
|
private val importTaskFactory: ImportDataTaskFactory,
|
||||||
private val colorPickerFactory: ColorPickerDialogFactory,
|
private val colorPickerFactory: ColorPickerDialogFactory,
|
||||||
private val numberPickerFactory: NumberPickerFactory,
|
private val numberPickerFactory: NumberPickerFactory,
|
||||||
|
private val checkMarkDialog: CheckmarkDialog,
|
||||||
private val behavior: Lazy<ListHabitsBehavior>
|
private val behavior: Lazy<ListHabitsBehavior>
|
||||||
) : CommandRunner.Listener,
|
) : CommandRunner.Listener,
|
||||||
ListHabitsBehavior.Screen,
|
ListHabitsBehavior.Screen,
|
||||||
@@ -225,9 +227,28 @@ class ListHabitsScreen
|
|||||||
override fun showNumberPicker(
|
override fun showNumberPicker(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback
|
callback: ListHabitsBehavior.NumberPickerCallback
|
||||||
) {
|
) {
|
||||||
numberPickerFactory.create(value, unit, callback).show()
|
numberPickerFactory.create(value, unit, notes, dateString, callback).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCheckmarkDialog(
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
|
color: PaletteColor,
|
||||||
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
|
) {
|
||||||
|
checkMarkDialog.create(
|
||||||
|
value,
|
||||||
|
notes,
|
||||||
|
dateString,
|
||||||
|
color,
|
||||||
|
callback,
|
||||||
|
themeSwitcher.currentTheme!!,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getExecuteString(command: Command): String? {
|
private fun getExecuteString(command: Command): String? {
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ 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.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
|
import org.isoron.uhabits.utils.drawNotesIndicator
|
||||||
import org.isoron.uhabits.utils.getFontAwesome
|
import org.isoron.uhabits.utils.getFontAwesome
|
||||||
import org.isoron.uhabits.utils.showMessage
|
|
||||||
import org.isoron.uhabits.utils.sp
|
import org.isoron.uhabits.utils.sp
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import org.isoron.uhabits.utils.toMeasureSpec
|
import org.isoron.uhabits.utils.toMeasureSpec
|
||||||
@@ -71,7 +71,15 @@ class CheckmarkButtonView(
|
|||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hasNotes = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
var onToggle: (Int) -> Unit = {}
|
var onToggle: (Int) -> Unit = {}
|
||||||
|
|
||||||
|
var onEdit: () -> Unit = {}
|
||||||
private var drawer = Drawer()
|
private var drawer = Drawer()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@@ -93,11 +101,12 @@ class CheckmarkButtonView(
|
|||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) performToggle()
|
if (preferences.isShortToggleEnabled) performToggle()
|
||||||
else showMessage(resources.getString(R.string.long_press_to_toggle))
|
else onEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View): Boolean {
|
override fun onLongClick(v: View): Boolean {
|
||||||
performToggle()
|
if (preferences.isShortToggleEnabled) onEdit()
|
||||||
|
else performToggle()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +179,8 @@ class CheckmarkButtonView(
|
|||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
canvas.drawText(label, rect.centerX(), rect.centerY(), paint)
|
canvas.drawText(label, rect.centerX(), rect.centerY(), paint)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawNotesIndicator(canvas, color, em, hasNotes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,24 @@ class CheckmarkPanelView(
|
|||||||
setupButtons()
|
setupButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notesIndicators = BooleanArray(0)
|
||||||
|
set(values) {
|
||||||
|
field = values
|
||||||
|
setupButtons()
|
||||||
|
}
|
||||||
|
|
||||||
var onToggle: (Timestamp, Int) -> Unit = { _, _ -> }
|
var onToggle: (Timestamp, Int) -> Unit = { _, _ -> }
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
setupButtons()
|
setupButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var onEdit: (Timestamp) -> Unit = {}
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
setupButtons()
|
||||||
|
}
|
||||||
|
|
||||||
override fun createButton(): CheckmarkButtonView = buttonFactory.create()
|
override fun createButton(): CheckmarkButtonView = buttonFactory.create()
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
@@ -72,8 +84,13 @@ class CheckmarkPanelView(
|
|||||||
index + dataOffset < values.size -> values[index + dataOffset]
|
index + dataOffset < values.size -> values[index + dataOffset]
|
||||||
else -> UNKNOWN
|
else -> UNKNOWN
|
||||||
}
|
}
|
||||||
|
button.hasNotes = when {
|
||||||
|
index + dataOffset < notesIndicators.size -> notesIndicators[index + dataOffset]
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
button.color = color
|
button.color = color
|
||||||
button.onToggle = { value -> onToggle(timestamp, value) }
|
button.onToggle = { value -> onToggle(timestamp, value) }
|
||||||
|
button.onEdit = { onEdit(timestamp) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,8 +124,9 @@ class HabitCardListAdapter @Inject constructor(
|
|||||||
val habit = cache.getHabitByPosition(position)
|
val habit = cache.getHabitByPosition(position)
|
||||||
val score = cache.getScore(habit!!.id!!)
|
val score = cache.getScore(habit!!.id!!)
|
||||||
val checkmarks = cache.getCheckmarks(habit.id!!)
|
val checkmarks = cache.getCheckmarks(habit.id!!)
|
||||||
|
val notesIndicators = cache.getNoteIndicators(habit.id!!)
|
||||||
val selected = selected.contains(habit)
|
val selected = selected.contains(habit)
|
||||||
listView!!.bindCardView(holder, habit, score, checkmarks, selected)
|
listView!!.bindCardView(holder, habit, score, checkmarks, notesIndicators, selected)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: HabitCardViewHolder) {
|
override fun onViewAttachedToWindow(holder: HabitCardViewHolder) {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ class HabitCardListView(
|
|||||||
habit: Habit,
|
habit: Habit,
|
||||||
score: Double,
|
score: Double,
|
||||||
checkmarks: IntArray,
|
checkmarks: IntArray,
|
||||||
|
notesIndicators: BooleanArray,
|
||||||
selected: Boolean
|
selected: Boolean
|
||||||
): View {
|
): View {
|
||||||
val cardView = holder.itemView as HabitCardView
|
val cardView = holder.itemView as HabitCardView
|
||||||
@@ -98,6 +99,7 @@ class HabitCardListView(
|
|||||||
cardView.score = score
|
cardView.score = score
|
||||||
cardView.unit = habit.unit
|
cardView.unit = habit.unit
|
||||||
cardView.threshold = habit.targetValue / habit.frequency.denominator
|
cardView.threshold = habit.targetValue / habit.frequency.denominator
|
||||||
|
cardView.notesIndicators = notesIndicators
|
||||||
|
|
||||||
val detector = GestureDetector(context, CardViewGestureDetector(holder))
|
val detector = GestureDetector(context, CardViewGestureDetector(holder))
|
||||||
cardView.setOnTouchListener { _, ev ->
|
cardView.setOnTouchListener { _, ev ->
|
||||||
|
|||||||
@@ -116,6 +116,13 @@ class HabitCardView(
|
|||||||
numberPanel.threshold = value
|
numberPanel.threshold = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notesIndicators
|
||||||
|
get() = checkmarkPanel.notesIndicators
|
||||||
|
set(values) {
|
||||||
|
checkmarkPanel.notesIndicators = values
|
||||||
|
numberPanel.notesIndicators = values
|
||||||
|
}
|
||||||
|
|
||||||
var checkmarkPanel: CheckmarkPanelView
|
var checkmarkPanel: CheckmarkPanelView
|
||||||
private var numberPanel: NumberPanelView
|
private var numberPanel: NumberPanelView
|
||||||
private var innerFrame: LinearLayout
|
private var innerFrame: LinearLayout
|
||||||
@@ -150,6 +157,10 @@ class HabitCardView(
|
|||||||
}.delay(TOGGLE_DELAY_MILLIS)
|
}.delay(TOGGLE_DELAY_MILLIS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onEdit = { timestamp ->
|
||||||
|
triggerRipple(timestamp)
|
||||||
|
habit?.let { behavior.onEdit(it, timestamp) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
numberPanel = numberPanelFactory.create().apply {
|
numberPanel = numberPanelFactory.create().apply {
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ import org.isoron.uhabits.core.preferences.Preferences
|
|||||||
import org.isoron.uhabits.inject.ActivityContext
|
import org.isoron.uhabits.inject.ActivityContext
|
||||||
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
|
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
|
||||||
import org.isoron.uhabits.utils.dim
|
import org.isoron.uhabits.utils.dim
|
||||||
|
import org.isoron.uhabits.utils.drawNotesIndicator
|
||||||
import org.isoron.uhabits.utils.getFontAwesome
|
import org.isoron.uhabits.utils.getFontAwesome
|
||||||
import org.isoron.uhabits.utils.showMessage
|
|
||||||
import org.isoron.uhabits.utils.sres
|
import org.isoron.uhabits.utils.sres
|
||||||
import java.lang.Double.max
|
import java.lang.Double.max
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
@@ -101,6 +101,11 @@ class NumberButtonView(
|
|||||||
field = value
|
field = value
|
||||||
invalidate()
|
invalidate()
|
||||||
}
|
}
|
||||||
|
var hasNotes = false
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
var onEdit: () -> Unit = {}
|
var onEdit: () -> Unit = {}
|
||||||
private var drawer: Drawer = Drawer(context)
|
private var drawer: Drawer = Drawer(context)
|
||||||
@@ -111,8 +116,7 @@ class NumberButtonView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
if (preferences.isShortToggleEnabled) onEdit()
|
onEdit()
|
||||||
else showMessage(resources.getString(R.string.long_press_to_edit))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLongClick(v: View): Boolean {
|
override fun onLongClick(v: View): Boolean {
|
||||||
@@ -211,6 +215,8 @@ class NumberButtonView(
|
|||||||
rect.offset(0f, 1.3f * em)
|
rect.offset(0f, 1.3f * em)
|
||||||
canvas.drawText(units, rect.centerX(), rect.centerY(), pUnit)
|
canvas.drawText(units, rect.centerX(), rect.centerY(), pUnit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drawNotesIndicator(canvas, color, em, hasNotes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,12 @@ class NumberPanelView(
|
|||||||
setupButtons()
|
setupButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var notesIndicators = BooleanArray(0)
|
||||||
|
set(values) {
|
||||||
|
field = values
|
||||||
|
setupButtons()
|
||||||
|
}
|
||||||
|
|
||||||
var onEdit: (Timestamp) -> Unit = {}
|
var onEdit: (Timestamp) -> Unit = {}
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -90,6 +96,10 @@ class NumberPanelView(
|
|||||||
index + dataOffset < values.size -> values[index + dataOffset]
|
index + dataOffset < values.size -> values[index + dataOffset]
|
||||||
else -> 0.0
|
else -> 0.0
|
||||||
}
|
}
|
||||||
|
button.hasNotes = when {
|
||||||
|
index + dataOffset < notesIndicators.size -> notesIndicators[index + dataOffset]
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
button.color = color
|
button.color = color
|
||||||
button.targetType = targetType
|
button.targetType = targetType
|
||||||
button.threshold = threshold
|
button.threshold = threshold
|
||||||
|
|||||||
@@ -32,12 +32,14 @@ import org.isoron.uhabits.HabitsApplication
|
|||||||
import org.isoron.uhabits.R
|
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.CheckmarkDialog
|
||||||
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
|
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.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.ui.callbacks.OnConfirmedCallback
|
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.list.ListHabitsBehavior
|
||||||
@@ -164,9 +166,29 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
|
|||||||
override fun showNumberPicker(
|
override fun showNumberPicker(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback,
|
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||||
) {
|
) {
|
||||||
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, callback).show()
|
NumberPickerFactory(this@ShowHabitActivity).create(value, unit, notes, dateString, callback).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun showCheckmarkDialog(
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
|
preferences: Preferences,
|
||||||
|
color: PaletteColor,
|
||||||
|
callback: ListHabitsBehavior.CheckMarkDialogCallback
|
||||||
|
) {
|
||||||
|
CheckmarkDialog(this@ShowHabitActivity, preferences).create(
|
||||||
|
value,
|
||||||
|
notes,
|
||||||
|
dateString,
|
||||||
|
color,
|
||||||
|
callback,
|
||||||
|
themeSwitcher.currentTheme!!,
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun showEditHabitScreen(habit: Habit) {
|
override fun showEditHabitScreen(habit: Habit) {
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ class HistoryCardView(context: Context, attrs: AttributeSet) : LinearLayout(cont
|
|||||||
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
|
dateFormatter = JavaLocalDateFormatter(Locale.getDefault()),
|
||||||
series = state.series,
|
series = state.series,
|
||||||
defaultSquare = state.defaultSquare,
|
defaultSquare = state.defaultSquare,
|
||||||
|
notesIndicators = state.notesIndicators,
|
||||||
firstWeekday = state.firstWeekday,
|
firstWeekday = state.firstWeekday,
|
||||||
)
|
)
|
||||||
binding.chart.postInvalidate()
|
binding.chart.postInvalidate()
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ package org.isoron.uhabits.utils
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -199,5 +201,15 @@ fun View.dim(id: Int) = InterfaceUtils.getDimension(context, id)
|
|||||||
fun View.sp(value: Float) = InterfaceUtils.spToPixels(context, value)
|
fun View.sp(value: Float) = InterfaceUtils.spToPixels(context, value)
|
||||||
fun View.dp(value: Float) = InterfaceUtils.dpToPixels(context, value)
|
fun View.dp(value: Float) = InterfaceUtils.dpToPixels(context, value)
|
||||||
fun View.str(id: Int) = resources.getString(id)
|
fun View.str(id: Int) = resources.getString(id)
|
||||||
|
|
||||||
|
fun View.drawNotesIndicator(canvas: Canvas, color: Int, size: Float, hasNotes: Boolean) {
|
||||||
|
val pNotesIndicator = Paint()
|
||||||
|
pNotesIndicator.color = color
|
||||||
|
if (hasNotes) {
|
||||||
|
val cy = 0.8f * size
|
||||||
|
canvas.drawCircle(width.toFloat() - cy, cy, 8f, pNotesIndicator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val View.sres: StyledResources
|
val View.sres: StyledResources
|
||||||
get() = StyledResources(context)
|
get() = StyledResources(context)
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ class HistoryWidget(
|
|||||||
val historyChart = (this.view as HistoryChart)
|
val historyChart = (this.view as HistoryChart)
|
||||||
historyChart.series = model.series
|
historyChart.series = model.series
|
||||||
historyChart.defaultSquare = model.defaultSquare
|
historyChart.defaultSquare = model.defaultSquare
|
||||||
|
historyChart.notesIndicators = model.notesIndicators
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ class HistoryWidget(
|
|||||||
firstWeekday = prefs.firstWeekday,
|
firstWeekday = prefs.firstWeekday,
|
||||||
series = listOf(),
|
series = listOf(),
|
||||||
defaultSquare = HistoryChart.Square.OFF,
|
defaultSquare = HistoryChart.Square.OFF,
|
||||||
|
notesIndicators = listOf(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
).apply {
|
).apply {
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
|
|||||||
SystemUtils.unlockScreen(this)
|
SystemUtils.unlockScreen(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNumberPicked(newValue: Double) {
|
override fun onNumberPicked(newValue: Double, notes: String) {
|
||||||
behavior.setValue(data.habit, data.timestamp, (newValue * 1000).toInt())
|
behavior.setValue(data.habit, data.timestamp, (newValue * 1000).toInt(), notes)
|
||||||
widgetUpdater.updateWidgets()
|
widgetUpdater.updateWidgets()
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
@@ -79,6 +79,8 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
|
|||||||
numberPickerFactory.create(
|
numberPickerFactory.create(
|
||||||
entry.value / 1000.0,
|
entry.value / 1000.0,
|
||||||
data.habit.unit,
|
data.habit.unit,
|
||||||
|
entry.notes,
|
||||||
|
today.toDialogDateString(),
|
||||||
this
|
this
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|||||||
14
uhabits-android/src/main/res/drawable/bg_select_button.xml
Normal file
14
uhabits-android/src/main/res/drawable/bg_select_button.xml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_selected="true">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="?attr/contrast40" />
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
<padding
|
||||||
|
android:bottom="0dp"
|
||||||
|
android:left="8dp"
|
||||||
|
android:right="8dp"
|
||||||
|
android:top="0dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<stroke android:width="1dp" android:color="?attr/contrast40" />
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
<padding
|
||||||
|
android:left="0dp"
|
||||||
|
android:top="8dp"
|
||||||
|
android:right="0dp"
|
||||||
|
android:bottom="0dp" />
|
||||||
|
</shape>
|
||||||
95
uhabits-android/src/main/res/layout/checkmark_dialog.xml
Normal file
95
uhabits-android/src/main/res/layout/checkmark_dialog.xml
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
style="@style/FormOuterBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<LinearLayout style="@style/DialogFormInnerBox">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/DialogFormLabel"
|
||||||
|
android:text="@string/value" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/yesBtn"
|
||||||
|
android:text="@string/fa_check"
|
||||||
|
style="@style/CheckmarkDialogBtn"/>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/skippedBtn"
|
||||||
|
android:text="@string/fa_skipped"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/CheckmarkDialogBtn"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/noBtn"
|
||||||
|
android:text="@string/fa_times"
|
||||||
|
style="@style/CheckmarkDialogBtn"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/questionBtn"
|
||||||
|
android:text="@string/fa_question"
|
||||||
|
android:visibility="gone"
|
||||||
|
style="@style/CheckmarkDialogBtn"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
style="@style/FormOuterBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/DialogFormInnerBox">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/DialogFormLabel"
|
||||||
|
android:text="@string/notes" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etNotes"
|
||||||
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
style="@style/FormInput"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:hint="@string/example_notes"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -19,11 +19,35 @@
|
|||||||
-->
|
-->
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:gravity="center"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingEnd="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
style="@style/FormOuterBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<LinearLayout style="@style/DialogFormInnerBox">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/DialogFormLabel"
|
||||||
|
android:text="@string/value" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
<NumberPicker
|
<NumberPicker
|
||||||
android:id="@+id/picker"
|
android:id="@+id/picker"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
@@ -32,6 +56,7 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvSeparator"
|
android:id="@+id/tvSeparator"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
/>
|
/>
|
||||||
@@ -45,7 +70,47 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvUnit"
|
android:id="@+id/tvUnit"
|
||||||
|
android:layout_gravity="center"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="5dp"
|
||||||
|
android:baselineAligned="false">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
style="@style/FormOuterBox"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<LinearLayout style="@style/DialogFormInnerBox">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/DialogFormLabel"
|
||||||
|
android:text="@string/notes" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etNotes"
|
||||||
|
android:inputType="textCapSentences|textMultiLine"
|
||||||
|
style="@style/FormInput"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:hint="@string/example_notes"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -43,6 +43,7 @@
|
|||||||
<attr name="iconFilter" format="reference"/>
|
<attr name="iconFilter" format="reference"/>
|
||||||
<attr name="iconArrowUp" format="reference"/>
|
<attr name="iconArrowUp" format="reference"/>
|
||||||
<attr name="iconArrowDown" format="reference"/>
|
<attr name="iconArrowDown" format="reference"/>
|
||||||
|
<attr name="dialogFormLabelColor" format="reference"/>
|
||||||
|
|
||||||
<attr name="toolbarPopupTheme" format="reference"/>
|
<attr name="toolbarPopupTheme" format="reference"/>
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@
|
|||||||
<string name="interval_always_ask">Always ask</string>
|
<string name="interval_always_ask">Always ask</string>
|
||||||
<string name="interval_custom">Custom...</string>
|
<string name="interval_custom">Custom...</string>
|
||||||
<string name="pref_toggle_title">Toggle with short press</string>
|
<string name="pref_toggle_title">Toggle with short press</string>
|
||||||
<string name="pref_toggle_description">Put checkmarks with a single tap instead of press-and-hold. More convenient, but might cause accidental toggles.</string>
|
<string name="pref_toggle_description_2">Put checkmarks with a single tap instead of press-and-hold.</string>
|
||||||
<string name="pref_rate_this_app">Rate this app on Google Play</string>
|
<string name="pref_rate_this_app">Rate this app on Google Play</string>
|
||||||
<string name="pref_send_feedback">Send feedback to developer</string>
|
<string name="pref_send_feedback">Send feedback to developer</string>
|
||||||
<string name="pref_view_source_code">View source code at GitHub</string>
|
<string name="pref_view_source_code">View source code at GitHub</string>
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
<string name="by_status">By status</string>
|
<string name="by_status">By status</string>
|
||||||
<string name="export">Export</string>
|
<string name="export">Export</string>
|
||||||
<string name="long_press_to_edit">Press-and-hold to change the value</string>
|
<string name="long_press_to_edit">Press-and-hold to change the value</string>
|
||||||
<string name="change_value">Change value</string>
|
<string name="value">Value</string>
|
||||||
<string name="calendar">Calendar</string>
|
<string name="calendar">Calendar</string>
|
||||||
<string name="unit">Unit</string>
|
<string name="unit">Unit</string>
|
||||||
<string name="target_type">Target Type</string>
|
<string name="target_type">Target Type</string>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
<item name="windowBackgroundColor">@color/grey_200</item>
|
<item name="windowBackgroundColor">@color/grey_200</item>
|
||||||
<item name="android:textColorAlertDialogListItem">@color/grey_800</item>
|
<item name="android:textColorAlertDialogListItem">@color/grey_800</item>
|
||||||
<item name="singleLineTitle">false</item>
|
<item name="singleLineTitle">false</item>
|
||||||
|
<item name="dialogFormLabelColor">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppBaseThemeDark" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
|
<style name="AppBaseThemeDark" parent="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar">
|
||||||
@@ -110,6 +111,7 @@
|
|||||||
<item name="buttonBarPositiveButtonStyle">@style/DialogButtonStyle</item>
|
<item name="buttonBarPositiveButtonStyle">@style/DialogButtonStyle</item>
|
||||||
<item name="android:textColorAlertDialogListItem">@color/grey_100</item>
|
<item name="android:textColorAlertDialogListItem">@color/grey_100</item>
|
||||||
<item name="singleLineTitle">false</item>
|
<item name="singleLineTitle">false</item>
|
||||||
|
<item name="dialogFormLabelColor">@color/grey_800</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppBaseThemeDark.PureBlack">
|
<style name="AppBaseThemeDark.PureBlack">
|
||||||
@@ -130,6 +132,7 @@
|
|||||||
<item name="textColorAlertDialogListItem">@color/grey_100</item>
|
<item name="textColorAlertDialogListItem">@color/grey_100</item>
|
||||||
<item name="windowBackgroundColor">@color/black</item>
|
<item name="windowBackgroundColor">@color/black</item>
|
||||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.PureBlack</item>
|
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material.PureBlack</item>
|
||||||
|
<item name="dialogFormLabelColor">@color/grey_800</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BaseDialog" parent="Theme.AppCompat.Light.Dialog">
|
<style name="BaseDialog" parent="Theme.AppCompat.Light.Dialog">
|
||||||
@@ -140,6 +143,7 @@
|
|||||||
<item name="contrast80">@color/grey_700</item>
|
<item name="contrast80">@color/grey_700</item>
|
||||||
<item name="contrast100">@color/grey_800</item>
|
<item name="contrast100">@color/grey_800</item>
|
||||||
<item name="palette">@array/lightPalette</item>
|
<item name="palette">@array/lightPalette</item>
|
||||||
|
<item name="dialogFormLabelColor">@color/white</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="BaseDialogDark" parent="Theme.AppCompat.Dialog">
|
<style name="BaseDialogDark" parent="Theme.AppCompat.Dialog">
|
||||||
@@ -150,6 +154,7 @@
|
|||||||
<item name="contrast80">@color/grey_300</item>
|
<item name="contrast80">@color/grey_300</item>
|
||||||
<item name="contrast100">@color/grey_100</item>
|
<item name="contrast100">@color/grey_100</item>
|
||||||
<item name="palette">@array/darkPalette</item>
|
<item name="palette">@array/darkPalette</item>
|
||||||
|
<item name="dialogFormLabelColor">@color/grey_800</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="PreferenceThemeOverlay.v14.Material.PureBlack">
|
<style name="PreferenceThemeOverlay.v14.Material.PureBlack">
|
||||||
@@ -360,4 +365,38 @@
|
|||||||
<item name="android:layout_height">1dp</item>
|
<item name="android:layout_height">1dp</item>
|
||||||
<item name="android:background">?attr/contrast20</item>
|
<item name="android:background">?attr/contrast20</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="DialogFormInnerBox">
|
||||||
|
<item name="android:background">@drawable/dialog_bg_input_box</item>
|
||||||
|
<item name="android:clipChildren">false</item>
|
||||||
|
<item name="android:clipToPadding">false</item>
|
||||||
|
<item name="android:orientation">vertical</item>
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">match_parent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="DialogFormLabel">
|
||||||
|
<item name="android:layout_width">wrap_content</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:layout_marginStart">8dp</item>
|
||||||
|
<item name="android:layout_marginTop">-15dp</item>
|
||||||
|
<item name="android:layout_marginBottom">-4dp</item>
|
||||||
|
<item name="android:paddingStart">8dp</item>
|
||||||
|
<item name="android:background">?attr/dialogFormLabelColor</item>
|
||||||
|
<item name="android:paddingEnd">8dp</item>
|
||||||
|
<item name="android:textSize">@dimen/smallTextSize</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="CheckmarkDialogBtn">
|
||||||
|
<item name="android:layout_width">48dp</item>
|
||||||
|
<item name="android:layout_height">48dp</item>
|
||||||
|
<item name="android:layout_marginTop">8dp</item>
|
||||||
|
<item name="android:layout_marginBottom">8dp</item>
|
||||||
|
<item name="android:layout_marginEnd">12dp</item>
|
||||||
|
<item name="android:textSize">@dimen/regularTextSize</item>
|
||||||
|
<item name="backgroundTint">@null</item>
|
||||||
|
<item name="android:background">@drawable/bg_select_button</item>
|
||||||
|
<item name="selectable">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@
|
|||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="pref_short_toggle"
|
android:key="pref_short_toggle"
|
||||||
android:summary="@string/pref_toggle_description"
|
android:summary="@string/pref_toggle_description_2"
|
||||||
android:title="@string/pref_toggle_title"
|
android:title="@string/pref_toggle_title"
|
||||||
app:iconSpaceReserved="false" />
|
app:iconSpaceReserved="false" />
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText
|
HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText
|
||||||
Breed dragons,with love and fire,Diet & Food,2016-03-18,1,
|
Breed dragons,with love and fire,Diet & Food,2016-03-18,1,text
|
||||||
Breed dragons,with love and fire,Diet & Food,2016-03-19,1,
|
Breed dragons,with love and fire,Diet & Food,2016-03-19,1,
|
||||||
Breed dragons,with love and fire,Diet & Food,2016-03-21,1,
|
Breed dragons,with love and fire,Diet & Food,2016-03-21,1,
|
||||||
Reduce sleep,only 2 hours per day,Time Management,2016-03-15,1,
|
Reduce sleep,only 2 hours per day,Time Management,2016-03-15,1,
|
||||||
|
|||||||
|
@@ -1,7 +1,7 @@
|
|||||||
HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText
|
HabitName,HabitDescription,HabitCategory,CalendarDate,Value,CommentText
|
||||||
H1,,C1,11/5/2020,1,
|
H1,,C1,11/5/2020,1,
|
||||||
H2,,C2,11/5/2020,-2150000000,
|
H2,,C2,11/5/2020,-2150000000,
|
||||||
H3,Habit 3,C3,4/11/2019,1,
|
H3,Habit 3,C3,4/11/2019,1,text
|
||||||
H3,Habit 3,C3,4/12/2019,1,
|
H3,Habit 3,C3,4/12/2019,1,
|
||||||
H3,Habit 3,C3,4/13/2019,0,
|
H3,Habit 3,C3,4/13/2019,0,
|
||||||
H3,Habit 3,C3,4/14/2019,1,
|
H3,Habit 3,C3,4/14/2019,1,
|
||||||
@@ -65,7 +65,7 @@ H3,Habit 3,C3,6/10/2019,1,
|
|||||||
H3,Habit 3,C3,6/11/2019,1,
|
H3,Habit 3,C3,6/11/2019,1,
|
||||||
H3,Habit 3,C3,6/12/2019,1,
|
H3,Habit 3,C3,6/12/2019,1,
|
||||||
H3,Habit 3,C3,6/13/2019,1,
|
H3,Habit 3,C3,6/13/2019,1,
|
||||||
H3,Habit 3,C3,6/14/2019,0,
|
H3,Habit 3,C3,6/14/2019,0,Habit 3 notes
|
||||||
H3,Habit 3,C3,6/15/2019,1,
|
H3,Habit 3,C3,6/15/2019,1,
|
||||||
H4,Habit 4,C4,11/6/2020,1,
|
H4,Habit 4,C4,11/6/2020,1,
|
||||||
H4,Habit 4,C4,11/9/2020,1,
|
H4,Habit 4,C4,11/9/2020,1,
|
||||||
|
|||||||
|
@@ -23,6 +23,8 @@ interface View {
|
|||||||
fun draw(canvas: Canvas)
|
fun draw(canvas: Canvas)
|
||||||
fun onClick(x: Double, y: Double) {
|
fun onClick(x: Double, y: Double) {
|
||||||
}
|
}
|
||||||
|
fun onLongClick(x: Double, y: Double) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataView : View {
|
interface DataView : View {
|
||||||
|
|||||||
@@ -20,4 +20,4 @@ package org.isoron.uhabits.core
|
|||||||
|
|
||||||
const val DATABASE_FILENAME = "uhabits.db"
|
const val DATABASE_FILENAME = "uhabits.db"
|
||||||
|
|
||||||
const val DATABASE_VERSION = 24
|
const val DATABASE_VERSION = 25
|
||||||
|
|||||||
@@ -28,10 +28,11 @@ data class CreateRepetitionCommand(
|
|||||||
val habit: Habit,
|
val habit: Habit,
|
||||||
val timestamp: Timestamp,
|
val timestamp: Timestamp,
|
||||||
val value: Int,
|
val value: Int,
|
||||||
|
val notes: String,
|
||||||
) : Command {
|
) : Command {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val entries = habit.originalEntries
|
val entries = habit.originalEntries
|
||||||
entries.add(Entry(timestamp, value))
|
entries.add(Entry(timestamp, value, notes))
|
||||||
habit.recompute()
|
habit.recompute()
|
||||||
habitList.resort()
|
habitList.resort()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,8 +76,11 @@ class HabitBullCSVImporter
|
|||||||
map[name] = h
|
map[name] = h
|
||||||
logger.info("Creating habit: $name")
|
logger.info("Creating habit: $name")
|
||||||
}
|
}
|
||||||
|
val notes = cols[5] ?: ""
|
||||||
if (parseInt(cols[4]) == 1) {
|
if (parseInt(cols[4]) == 1) {
|
||||||
h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL))
|
h.originalEntries.add(Entry(timestamp, Entry.YES_MANUAL, notes))
|
||||||
|
} else {
|
||||||
|
h.originalEntries.add(Entry(timestamp, Entry.NO, notes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,8 +101,9 @@ class LoopDBImporter
|
|||||||
|
|
||||||
for (r in entryRecords) {
|
for (r in entryRecords) {
|
||||||
val t = Timestamp(r.timestamp!!)
|
val t = Timestamp(r.timestamp!!)
|
||||||
val (_, value) = habit!!.originalEntries.get(t)
|
val (_, value, notes) = habit!!.originalEntries.get(t)
|
||||||
if (value != r.value) CreateRepetitionCommand(habitList, habit, t, r.value!!).run()
|
val oldNotes = r.notes ?: ""
|
||||||
|
if (value != r.value || notes != oldNotes) CreateRepetitionCommand(habitList, habit, t, r.value!!, oldNotes).run()
|
||||||
}
|
}
|
||||||
|
|
||||||
runner.notifyListeners(command)
|
runner.notifyListeners(command)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ package org.isoron.uhabits.core.models
|
|||||||
data class Entry(
|
data class Entry(
|
||||||
val timestamp: Timestamp,
|
val timestamp: Timestamp,
|
||||||
val value: Int,
|
val value: Int,
|
||||||
|
val notes: String = "",
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ open class EntryList {
|
|||||||
val intervals = buildIntervals(frequency, original)
|
val intervals = buildIntervals(frequency, original)
|
||||||
snapIntervalsTogether(intervals)
|
snapIntervalsTogether(intervals)
|
||||||
val computed = buildEntriesFromInterval(original, intervals)
|
val computed = buildEntriesFromInterval(original, intervals)
|
||||||
computed.filter { it.value != UNKNOWN }.forEach { add(it) }
|
computed.filter { it.value != UNKNOWN || it.notes.isNotEmpty() }.forEach { add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ package org.isoron.uhabits.core.models
|
|||||||
|
|
||||||
import org.isoron.platform.time.LocalDate
|
import org.isoron.platform.time.LocalDate
|
||||||
import org.isoron.uhabits.core.utils.DateFormats.Companion.getCSVDateFormat
|
import org.isoron.uhabits.core.utils.DateFormats.Companion.getCSVDateFormat
|
||||||
|
import org.isoron.uhabits.core.utils.DateFormats.Companion.getDialogDateFormat
|
||||||
import org.isoron.uhabits.core.utils.DateUtils
|
import org.isoron.uhabits.core.utils.DateUtils
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.getStartOfTodayCalendar
|
||||||
import org.isoron.uhabits.core.utils.DateUtils.Companion.truncate
|
import org.isoron.uhabits.core.utils.DateUtils.Companion.truncate
|
||||||
@@ -81,6 +82,10 @@ data class Timestamp(var unixTime: Long) : Comparable<Timestamp> {
|
|||||||
return day
|
return day
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toDialogDateString(): String {
|
||||||
|
return getDialogDateFormat().format(Date(unixTime))
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return getCSVDateFormat().format(Date(unixTime))
|
return getCSVDateFormat().format(Date(unixTime))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,12 +41,17 @@ class EntryRecord {
|
|||||||
|
|
||||||
@field:Column
|
@field:Column
|
||||||
var id: Long? = null
|
var id: Long? = null
|
||||||
|
|
||||||
|
@field:Column
|
||||||
|
var notes: String? = null
|
||||||
fun copyFrom(entry: Entry) {
|
fun copyFrom(entry: Entry) {
|
||||||
timestamp = entry.timestamp.unixTime
|
timestamp = entry.timestamp.unixTime
|
||||||
value = entry.value
|
value = entry.value
|
||||||
|
notes = entry.notes
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toEntry(): Entry {
|
fun toEntry(): Entry {
|
||||||
return Entry(Timestamp(timestamp!!), value!!)
|
val notes = notes ?: ""
|
||||||
|
return Entry(Timestamp(timestamp!!), value!!, notes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ class HabitCardListCache @Inject constructor(
|
|||||||
return data.checkmarks[habitId]!!
|
return data.checkmarks[habitId]!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun getNoteIndicators(habitId: Long): BooleanArray {
|
||||||
|
return data.notesIndicators[habitId]!!
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun hasNoHabit(): Boolean {
|
fun hasNoHabit(): Boolean {
|
||||||
return allHabits.isEmpty
|
return allHabits.isEmpty
|
||||||
@@ -163,6 +168,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
data.habits.removeAt(position)
|
data.habits.removeAt(position)
|
||||||
data.idToHabit.remove(id)
|
data.idToHabit.remove(id)
|
||||||
data.checkmarks.remove(id)
|
data.checkmarks.remove(id)
|
||||||
|
data.notesIndicators.remove(id)
|
||||||
data.scores.remove(id)
|
data.scores.remove(id)
|
||||||
listener.onItemRemoved(position)
|
listener.onItemRemoved(position)
|
||||||
}
|
}
|
||||||
@@ -207,6 +213,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
val habits: MutableList<Habit>
|
val habits: MutableList<Habit>
|
||||||
val checkmarks: HashMap<Long?, IntArray>
|
val checkmarks: HashMap<Long?, IntArray>
|
||||||
val scores: HashMap<Long?, Double>
|
val scores: HashMap<Long?, Double>
|
||||||
|
val notesIndicators: HashMap<Long?, BooleanArray>
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun copyCheckmarksFrom(oldData: CacheData) {
|
fun copyCheckmarksFrom(oldData: CacheData) {
|
||||||
@@ -217,6 +224,15 @@ class HabitCardListCache @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
fun copyNoteIndicatorsFrom(oldData: CacheData) {
|
||||||
|
val empty = BooleanArray(checkmarkCount)
|
||||||
|
for (id in idToHabit.keys) {
|
||||||
|
if (oldData.notesIndicators.containsKey(id)) notesIndicators[id] =
|
||||||
|
oldData.notesIndicators[id]!! else notesIndicators[id] = empty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun copyScoresFrom(oldData: CacheData) {
|
fun copyScoresFrom(oldData: CacheData) {
|
||||||
for (id in idToHabit.keys) {
|
for (id in idToHabit.keys) {
|
||||||
@@ -241,6 +257,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
habits = LinkedList()
|
habits = LinkedList()
|
||||||
checkmarks = HashMap()
|
checkmarks = HashMap()
|
||||||
scores = HashMap()
|
scores = HashMap()
|
||||||
|
notesIndicators = HashMap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,6 +288,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
newData.fetchHabits()
|
newData.fetchHabits()
|
||||||
newData.copyScoresFrom(data)
|
newData.copyScoresFrom(data)
|
||||||
newData.copyCheckmarksFrom(data)
|
newData.copyCheckmarksFrom(data)
|
||||||
|
newData.copyNoteIndicatorsFrom(data)
|
||||||
val today = getTodayWithOffset()
|
val today = getTodayWithOffset()
|
||||||
val dateFrom = today.minus(checkmarkCount - 1)
|
val dateFrom = today.minus(checkmarkCount - 1)
|
||||||
if (runner != null) runner!!.publishProgress(this, -1)
|
if (runner != null) runner!!.publishProgress(this, -1)
|
||||||
@@ -280,10 +298,14 @@ class HabitCardListCache @Inject constructor(
|
|||||||
if (targetId != null && targetId != habit.id) continue
|
if (targetId != null && targetId != habit.id) continue
|
||||||
newData.scores[habit.id] = habit.scores[today].value
|
newData.scores[habit.id] = habit.scores[today].value
|
||||||
val list: MutableList<Int> = ArrayList()
|
val list: MutableList<Int> = ArrayList()
|
||||||
for ((_, value) in habit.computedEntries.getByInterval(dateFrom, today))
|
val notesIndicators: MutableList<Boolean> = ArrayList()
|
||||||
|
for ((_, value, note) in habit.computedEntries.getByInterval(dateFrom, today)) {
|
||||||
list.add(value)
|
list.add(value)
|
||||||
|
notesIndicators.add(note.isNotEmpty())
|
||||||
|
}
|
||||||
val entries = list.toTypedArray()
|
val entries = list.toTypedArray()
|
||||||
newData.checkmarks[habit.id] = ArrayUtils.toPrimitive(entries)
|
newData.checkmarks[habit.id] = ArrayUtils.toPrimitive(entries)
|
||||||
|
newData.notesIndicators[habit.id] = notesIndicators.toBooleanArray()
|
||||||
runner!!.publishProgress(this, position)
|
runner!!.publishProgress(this, position)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -311,6 +333,7 @@ class HabitCardListCache @Inject constructor(
|
|||||||
data.idToHabit[id] = habit
|
data.idToHabit[id] = habit
|
||||||
data.scores[id] = newData.scores[id]!!
|
data.scores[id] = newData.scores[id]!!
|
||||||
data.checkmarks[id] = newData.checkmarks[id]!!
|
data.checkmarks[id] = newData.checkmarks[id]!!
|
||||||
|
data.notesIndicators[id] = newData.notesIndicators[id]!!
|
||||||
listener.onItemInserted(position)
|
listener.onItemInserted(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,14 +361,18 @@ class HabitCardListCache @Inject constructor(
|
|||||||
private fun performUpdate(id: Long, position: Int) {
|
private fun performUpdate(id: Long, position: Int) {
|
||||||
val oldScore = data.scores[id]!!
|
val oldScore = data.scores[id]!!
|
||||||
val oldCheckmarks = data.checkmarks[id]
|
val oldCheckmarks = data.checkmarks[id]
|
||||||
|
val oldNoteIndicators = data.notesIndicators[id]
|
||||||
val newScore = newData.scores[id]!!
|
val newScore = newData.scores[id]!!
|
||||||
val newCheckmarks = newData.checkmarks[id]!!
|
val newCheckmarks = newData.checkmarks[id]!!
|
||||||
|
val newNoteIndicators = newData.notesIndicators[id]!!
|
||||||
var unchanged = true
|
var unchanged = true
|
||||||
if (oldScore != newScore) unchanged = false
|
if (oldScore != newScore) unchanged = false
|
||||||
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
if (!Arrays.equals(oldCheckmarks, newCheckmarks)) unchanged = false
|
||||||
|
if (!Arrays.equals(oldNoteIndicators, newNoteIndicators)) unchanged = false
|
||||||
if (unchanged) return
|
if (unchanged) return
|
||||||
data.scores[id] = newScore
|
data.scores[id] = newScore
|
||||||
data.checkmarks[id] = newCheckmarks
|
data.checkmarks[id] = newCheckmarks
|
||||||
|
data.notesIndicators[id] = newNoteIndicators
|
||||||
listener.onItemChanged(position)
|
listener.onItemChanged(position)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import org.isoron.uhabits.core.commands.CommandRunner
|
|||||||
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
import org.isoron.uhabits.core.commands.CreateRepetitionCommand
|
||||||
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.HabitList
|
||||||
|
import org.isoron.uhabits.core.models.HabitType
|
||||||
|
import org.isoron.uhabits.core.models.PaletteColor
|
||||||
import org.isoron.uhabits.core.models.Timestamp
|
import org.isoron.uhabits.core.models.Timestamp
|
||||||
import org.isoron.uhabits.core.preferences.Preferences
|
import org.isoron.uhabits.core.preferences.Preferences
|
||||||
import org.isoron.uhabits.core.tasks.ExportCSVTask
|
import org.isoron.uhabits.core.tasks.ExportCSVTask
|
||||||
@@ -47,14 +49,27 @@ open class ListHabitsBehavior @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onEdit(habit: Habit, timestamp: Timestamp?) {
|
fun onEdit(habit: Habit, timestamp: Timestamp?) {
|
||||||
val entries = habit.computedEntries
|
val entry = habit.computedEntries.get(timestamp!!)
|
||||||
val oldValue = entries.get(timestamp!!).value.toDouble()
|
if (habit.type == HabitType.NUMERICAL) {
|
||||||
|
val oldValue = entry.value.toDouble()
|
||||||
screen.showNumberPicker(
|
screen.showNumberPicker(
|
||||||
oldValue / 1000,
|
oldValue / 1000,
|
||||||
habit.unit
|
habit.unit,
|
||||||
) { newValue: Double ->
|
entry.notes,
|
||||||
|
timestamp.toDialogDateString(),
|
||||||
|
) { newValue: Double, newNotes: String, ->
|
||||||
val value = (newValue * 1000).roundToInt()
|
val value = (newValue * 1000).roundToInt()
|
||||||
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value))
|
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
screen.showCheckmarkDialog(
|
||||||
|
entry.value,
|
||||||
|
entry.notes,
|
||||||
|
timestamp.toDialogDateString(),
|
||||||
|
habit.color,
|
||||||
|
) { newValue, newNotes ->
|
||||||
|
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,8 +120,9 @@ open class ListHabitsBehavior @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onToggle(habit: Habit, timestamp: Timestamp?, value: Int) {
|
fun onToggle(habit: Habit, timestamp: Timestamp?, value: Int) {
|
||||||
|
val notes = habit.computedEntries.get(timestamp!!).notes
|
||||||
commandRunner.run(
|
commandRunner.run(
|
||||||
CreateRepetitionCommand(habitList, habit, timestamp!!, value)
|
CreateRepetitionCommand(habitList, habit, timestamp, value, notes)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,10 +147,15 @@ open class ListHabitsBehavior @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun interface NumberPickerCallback {
|
fun interface NumberPickerCallback {
|
||||||
fun onNumberPicked(newValue: Double)
|
fun onNumberPicked(newValue: Double, notes: String)
|
||||||
fun onNumberPickerDismissed() {}
|
fun onNumberPickerDismissed() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun interface CheckMarkDialogCallback {
|
||||||
|
fun onNotesSaved(value: Int, notes: String)
|
||||||
|
fun onNotesDismissed() {}
|
||||||
|
}
|
||||||
|
|
||||||
interface Screen {
|
interface Screen {
|
||||||
fun showHabitScreen(h: Habit)
|
fun showHabitScreen(h: Habit)
|
||||||
fun showIntroScreen()
|
fun showIntroScreen()
|
||||||
@@ -142,8 +163,17 @@ open class ListHabitsBehavior @Inject constructor(
|
|||||||
fun showNumberPicker(
|
fun showNumberPicker(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
callback: NumberPickerCallback
|
callback: NumberPickerCallback
|
||||||
)
|
)
|
||||||
|
fun showCheckmarkDialog(
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
|
color: PaletteColor,
|
||||||
|
callback: CheckMarkDialogCallback
|
||||||
|
)
|
||||||
|
|
||||||
fun showSendBugReportToDeveloperScreen(log: String)
|
fun showSendBugReportToDeveloperScreen(log: String)
|
||||||
fun showSendFileScreen(filename: String)
|
fun showSendFileScreen(filename: String)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ data class HistoryCardState(
|
|||||||
val firstWeekday: DayOfWeek,
|
val firstWeekday: DayOfWeek,
|
||||||
val series: List<HistoryChart.Square>,
|
val series: List<HistoryChart.Square>,
|
||||||
val defaultSquare: HistoryChart.Square,
|
val defaultSquare: HistoryChart.Square,
|
||||||
|
val notesIndicators: List<Boolean>,
|
||||||
val theme: Theme,
|
val theme: Theme,
|
||||||
val today: LocalDate,
|
val today: LocalDate,
|
||||||
)
|
)
|
||||||
@@ -58,27 +59,15 @@ class HistoryCardPresenter(
|
|||||||
val screen: Screen,
|
val screen: Screen,
|
||||||
) : OnDateClickedListener {
|
) : OnDateClickedListener {
|
||||||
|
|
||||||
override fun onDateClicked(date: LocalDate) {
|
override fun onDateLongPress(date: LocalDate) {
|
||||||
val timestamp = Timestamp.fromLocalDate(date)
|
val timestamp = Timestamp.fromLocalDate(date)
|
||||||
screen.showFeedback()
|
screen.showFeedback()
|
||||||
if (habit.isNumerical) {
|
if (habit.isNumerical) {
|
||||||
val entries = habit.computedEntries
|
showNumberPicker(timestamp)
|
||||||
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 {
|
} else {
|
||||||
val currentValue = habit.computedEntries.get(timestamp).value
|
val entry = habit.computedEntries.get(timestamp)
|
||||||
val nextValue = Entry.nextToggleValue(
|
val nextValue = Entry.nextToggleValue(
|
||||||
value = currentValue,
|
value = entry.value,
|
||||||
isSkipEnabled = preferences.isSkipEnabled,
|
isSkipEnabled = preferences.isSkipEnabled,
|
||||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||||
)
|
)
|
||||||
@@ -88,6 +77,56 @@ class HistoryCardPresenter(
|
|||||||
habit,
|
habit,
|
||||||
timestamp,
|
timestamp,
|
||||||
nextValue,
|
nextValue,
|
||||||
|
entry.notes,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDateShortPress(date: LocalDate) {
|
||||||
|
val timestamp = Timestamp.fromLocalDate(date)
|
||||||
|
screen.showFeedback()
|
||||||
|
if (habit.isNumerical) {
|
||||||
|
showNumberPicker(timestamp)
|
||||||
|
} else {
|
||||||
|
val entry = habit.computedEntries.get(timestamp)
|
||||||
|
screen.showCheckmarkDialog(
|
||||||
|
entry.value,
|
||||||
|
entry.notes,
|
||||||
|
timestamp.toDialogDateString(),
|
||||||
|
preferences,
|
||||||
|
habit.color,
|
||||||
|
) { newValue, newNotes ->
|
||||||
|
commandRunner.run(
|
||||||
|
CreateRepetitionCommand(
|
||||||
|
habitList,
|
||||||
|
habit,
|
||||||
|
timestamp,
|
||||||
|
newValue,
|
||||||
|
newNotes,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showNumberPicker(timestamp: Timestamp) {
|
||||||
|
val entry = habit.computedEntries.get(timestamp)
|
||||||
|
val oldValue = entry.value
|
||||||
|
screen.showNumberPicker(
|
||||||
|
oldValue / 1000.0,
|
||||||
|
habit.unit,
|
||||||
|
entry.notes,
|
||||||
|
timestamp.toDialogDateString(),
|
||||||
|
) { newValue: Double, newNotes: String ->
|
||||||
|
val thousands = (newValue * 1000).roundToInt()
|
||||||
|
commandRunner.run(
|
||||||
|
CreateRepetitionCommand(
|
||||||
|
habitList,
|
||||||
|
habit,
|
||||||
|
timestamp,
|
||||||
|
thousands,
|
||||||
|
newNotes,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -137,13 +176,21 @@ class HistoryCardPresenter(
|
|||||||
else
|
else
|
||||||
HistoryChart.Square.OFF
|
HistoryChart.Square.OFF
|
||||||
|
|
||||||
|
val notesIndicators = entries.map {
|
||||||
|
when (it.notes) {
|
||||||
|
"" -> false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return HistoryCardState(
|
return HistoryCardState(
|
||||||
color = habit.color,
|
color = habit.color,
|
||||||
firstWeekday = firstWeekday,
|
firstWeekday = firstWeekday,
|
||||||
today = today.toLocalDate(),
|
today = today.toLocalDate(),
|
||||||
theme = theme,
|
theme = theme,
|
||||||
series = series,
|
series = series,
|
||||||
defaultSquare = defaultSquare
|
defaultSquare = defaultSquare,
|
||||||
|
notesIndicators = notesIndicators,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,7 +201,17 @@ class HistoryCardPresenter(
|
|||||||
fun showNumberPicker(
|
fun showNumberPicker(
|
||||||
value: Double,
|
value: Double,
|
||||||
unit: String,
|
unit: String,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
callback: ListHabitsBehavior.NumberPickerCallback,
|
callback: ListHabitsBehavior.NumberPickerCallback,
|
||||||
)
|
)
|
||||||
|
fun showCheckmarkDialog(
|
||||||
|
value: Int,
|
||||||
|
notes: String,
|
||||||
|
dateString: String,
|
||||||
|
preferences: Preferences,
|
||||||
|
color: PaletteColor,
|
||||||
|
callback: ListHabitsBehavior.CheckMarkDialogCallback,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,9 @@ import kotlin.math.max
|
|||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.round
|
import kotlin.math.round
|
||||||
|
|
||||||
fun interface OnDateClickedListener {
|
interface OnDateClickedListener {
|
||||||
fun onDateClicked(date: LocalDate)
|
fun onDateShortPress(date: LocalDate) {}
|
||||||
|
fun onDateLongPress(date: LocalDate) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HistoryChart(
|
class HistoryChart(
|
||||||
@@ -42,9 +43,10 @@ class HistoryChart(
|
|||||||
var paletteColor: PaletteColor,
|
var paletteColor: PaletteColor,
|
||||||
var series: List<Square>,
|
var series: List<Square>,
|
||||||
var defaultSquare: Square,
|
var defaultSquare: Square,
|
||||||
|
var notesIndicators: List<Boolean>,
|
||||||
var theme: Theme,
|
var theme: Theme,
|
||||||
var today: LocalDate,
|
var today: LocalDate,
|
||||||
var onDateClickedListener: OnDateClickedListener = OnDateClickedListener { },
|
var onDateClickedListener: OnDateClickedListener = object : OnDateClickedListener {},
|
||||||
var padding: Double = 0.0,
|
var padding: Double = 0.0,
|
||||||
) : DataView {
|
) : DataView {
|
||||||
|
|
||||||
@@ -72,6 +74,14 @@ class HistoryChart(
|
|||||||
get() = squareSpacing + squareSize
|
get() = squareSpacing + squareSize
|
||||||
|
|
||||||
override fun onClick(x: Double, y: Double) {
|
override fun onClick(x: Double, y: Double) {
|
||||||
|
onDateClicked(x, y, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLongClick(x: Double, y: Double) {
|
||||||
|
onDateClicked(x, y, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDateClicked(x: Double, y: Double, isLongClick: Boolean) {
|
||||||
if (width <= 0.0) throw IllegalStateException("onClick must be called after draw(canvas)")
|
if (width <= 0.0) throw IllegalStateException("onClick must be called after draw(canvas)")
|
||||||
val col = ((x - padding) / squareSize).toInt()
|
val col = ((x - padding) / squareSize).toInt()
|
||||||
val row = ((y - padding) / squareSize).toInt()
|
val row = ((y - padding) / squareSize).toInt()
|
||||||
@@ -79,7 +89,11 @@ class HistoryChart(
|
|||||||
if (row == 0 || col == nColumns) return
|
if (row == 0 || col == nColumns) return
|
||||||
val clickedDate = topLeftDate.plus(offset)
|
val clickedDate = topLeftDate.plus(offset)
|
||||||
if (clickedDate.isNewerThan(today)) return
|
if (clickedDate.isNewerThan(today)) return
|
||||||
onDateClickedListener.onDateClicked(clickedDate)
|
if (isLongClick) {
|
||||||
|
onDateClickedListener.onDateLongPress(clickedDate)
|
||||||
|
} else {
|
||||||
|
onDateClickedListener.onDateShortPress(clickedDate)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun draw(canvas: Canvas) {
|
override fun draw(canvas: Canvas) {
|
||||||
@@ -191,7 +205,9 @@ class HistoryChart(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
val value = if (offset >= series.size) defaultSquare else series[offset]
|
val value = if (offset >= series.size) defaultSquare else series[offset]
|
||||||
|
val hasNotes = if (offset >= notesIndicators.size) false else notesIndicators[offset]
|
||||||
val squareColor: Color
|
val squareColor: Color
|
||||||
|
val circleColor: Color
|
||||||
val color = theme.color(paletteColor.paletteIndex)
|
val color = theme.color(paletteColor.paletteIndex)
|
||||||
squareColor = when (value) {
|
squareColor = when (value) {
|
||||||
Square.ON -> {
|
Square.ON -> {
|
||||||
@@ -235,5 +251,14 @@ class HistoryChart(
|
|||||||
canvas.setColor(textColor)
|
canvas.setColor(textColor)
|
||||||
canvas.setTextAlign(TextAlign.CENTER)
|
canvas.setTextAlign(TextAlign.CENTER)
|
||||||
canvas.drawText(date.day.toString(), x + width / 2, y + width / 2)
|
canvas.drawText(date.day.toString(), x + width / 2, y + width / 2)
|
||||||
|
|
||||||
|
if (hasNotes) {
|
||||||
|
circleColor = when (value) {
|
||||||
|
Square.ON -> theme.lowContrastTextColor
|
||||||
|
else -> color
|
||||||
|
}
|
||||||
|
canvas.setColor(circleColor)
|
||||||
|
canvas.fillCircle(x + width - width / 5, y + width / 5, width / 12)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,40 +37,45 @@ class WidgetBehavior @Inject constructor(
|
|||||||
) {
|
) {
|
||||||
fun onAddRepetition(habit: Habit, timestamp: Timestamp?) {
|
fun onAddRepetition(habit: Habit, timestamp: Timestamp?) {
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
setValue(habit, timestamp, Entry.YES_MANUAL)
|
val entry = habit.originalEntries.get(timestamp!!)
|
||||||
|
setValue(habit, timestamp, Entry.YES_MANUAL, entry.notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onRemoveRepetition(habit: Habit, timestamp: Timestamp?) {
|
fun onRemoveRepetition(habit: Habit, timestamp: Timestamp?) {
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
setValue(habit, timestamp, Entry.NO)
|
val entry = habit.originalEntries.get(timestamp!!)
|
||||||
|
setValue(habit, timestamp, Entry.NO, entry.notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onToggleRepetition(habit: Habit, timestamp: Timestamp) {
|
fun onToggleRepetition(habit: Habit, timestamp: Timestamp) {
|
||||||
val currentValue = habit.originalEntries.get(timestamp).value
|
val entry = habit.originalEntries.get(timestamp)
|
||||||
|
val currentValue = entry.value
|
||||||
val newValue = nextToggleValue(
|
val newValue = nextToggleValue(
|
||||||
value = currentValue,
|
value = currentValue,
|
||||||
isSkipEnabled = preferences.isSkipEnabled,
|
isSkipEnabled = preferences.isSkipEnabled,
|
||||||
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
|
||||||
)
|
)
|
||||||
setValue(habit, timestamp, newValue)
|
setValue(habit, timestamp, newValue, entry.notes)
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onIncrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
fun onIncrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
||||||
val currentValue = habit.computedEntries.get(timestamp).value
|
val entry = habit.computedEntries.get(timestamp)
|
||||||
setValue(habit, timestamp, currentValue + amount)
|
val currentValue = entry.value
|
||||||
|
setValue(habit, timestamp, currentValue + amount, entry.notes)
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDecrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
fun onDecrement(habit: Habit, timestamp: Timestamp, amount: Int) {
|
||||||
val currentValue = habit.computedEntries.get(timestamp).value
|
val entry = habit.computedEntries.get(timestamp)
|
||||||
setValue(habit, timestamp, currentValue - amount)
|
val currentValue = entry.value
|
||||||
|
setValue(habit, timestamp, currentValue - amount, entry.notes)
|
||||||
notificationTray.cancel(habit)
|
notificationTray.cancel(habit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setValue(habit: Habit, timestamp: Timestamp?, newValue: Int) {
|
fun setValue(habit: Habit, timestamp: Timestamp?, newValue: Int, notes: String) {
|
||||||
commandRunner.run(
|
commandRunner.run(
|
||||||
CreateRepetitionCommand(habitList, habit, timestamp!!, newValue)
|
CreateRepetitionCommand(habitList, habit, timestamp!!, newValue, notes)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,5 +41,8 @@ class DateFormats {
|
|||||||
|
|
||||||
@JvmStatic fun getCSVDateFormat(): SimpleDateFormat =
|
@JvmStatic fun getCSVDateFormat(): SimpleDateFormat =
|
||||||
fromSkeleton("yyyy-MM-dd", Locale.US)
|
fromSkeleton("yyyy-MM-dd", Locale.US)
|
||||||
|
|
||||||
|
@JvmStatic fun getDialogDateFormat(): SimpleDateFormat =
|
||||||
|
fromSkeleton("MMM dd, yyyy", Locale.US)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
uhabits-core/src/jvmMain/resources/migrations/25.sql
Normal file
1
uhabits-core/src/jvmMain/resources/migrations/25.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
alter table Repetitions add column notes text;
|
||||||
@@ -38,7 +38,7 @@ class CreateRepetitionCommandTest : BaseUnitTest() {
|
|||||||
habit = fixtures.createShortHabit()
|
habit = fixtures.createShortHabit()
|
||||||
habitList.add(habit)
|
habitList.add(habit)
|
||||||
today = getToday()
|
today = getToday()
|
||||||
command = CreateRepetitionCommand(habitList, habit, today, 100)
|
command = CreateRepetitionCommand(habitList, habit, today, 100, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class ImportTest : BaseUnitTest() {
|
|||||||
assertTrue(isChecked(habit, 2016, 3, 18))
|
assertTrue(isChecked(habit, 2016, 3, 18))
|
||||||
assertTrue(isChecked(habit, 2016, 3, 19))
|
assertTrue(isChecked(habit, 2016, 3, 19))
|
||||||
assertFalse(isChecked(habit, 2016, 3, 20))
|
assertFalse(isChecked(habit, 2016, 3, 20))
|
||||||
|
assertTrue(isNotesEqual(habit, 2016, 3, 18, "text"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -68,6 +69,8 @@ class ImportTest : BaseUnitTest() {
|
|||||||
assertTrue(isChecked(habit, 2019, 4, 11))
|
assertTrue(isChecked(habit, 2019, 4, 11))
|
||||||
assertTrue(isChecked(habit, 2019, 5, 7))
|
assertTrue(isChecked(habit, 2019, 5, 7))
|
||||||
assertFalse(isChecked(habit, 2019, 6, 14))
|
assertFalse(isChecked(habit, 2019, 6, 14))
|
||||||
|
assertTrue(isNotesEqual(habit, 2019, 4, 11, "text"))
|
||||||
|
assertTrue(isNotesEqual(habit, 2019, 6, 14, "Habit 3 notes"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -127,6 +130,13 @@ class ImportTest : BaseUnitTest() {
|
|||||||
return h.originalEntries.get(timestamp).value == Entry.YES_MANUAL
|
return h.originalEntries.get(timestamp).value == Entry.YES_MANUAL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun isNotesEqual(h: Habit, year: Int, month: Int, day: Int, notes: String): Boolean {
|
||||||
|
val date = getStartOfTodayCalendar()
|
||||||
|
date.set(year, month - 1, day)
|
||||||
|
val timestamp = Timestamp(date)
|
||||||
|
return h.originalEntries.get(timestamp).notes == notes
|
||||||
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun importFromFile(assetFilename: String) {
|
private fun importFromFile(assetFilename: String) {
|
||||||
val file = File.createTempFile("asset", "")
|
val file = File.createTempFile("asset", "")
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class HabitCardListCacheTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testCommandListener_single() {
|
fun testCommandListener_single() {
|
||||||
val h2 = habitList.getByPosition(2)
|
val h2 = habitList.getByPosition(2)
|
||||||
commandRunner.run(CreateRepetitionCommand(habitList, h2, today, Entry.NO))
|
commandRunner.run(CreateRepetitionCommand(habitList, h2, today, Entry.NO, ""))
|
||||||
verify(listener).onItemChanged(2)
|
verify(listener).onItemChanged(2)
|
||||||
verify(listener).onRefreshFinished()
|
verify(listener).onRefreshFinished()
|
||||||
verifyNoMoreInteractions(listener)
|
verifyNoMoreInteractions(listener)
|
||||||
|
|||||||
@@ -79,8 +79,8 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testOnEdit() {
|
fun testOnEdit() {
|
||||||
behavior.onEdit(habit2, getToday())
|
behavior.onEdit(habit2, getToday())
|
||||||
verify(screen).showNumberPicker(eq(0.1), eq("miles"), picker.capture())
|
verify(screen).showNumberPicker(eq(0.1), eq("miles"), eq(""), eq("Jan 25, 2015"), picker.capture())
|
||||||
picker.lastValue.onNumberPicked(100.0)
|
picker.lastValue.onNumberPicked(100.0, "")
|
||||||
val today = getTodayWithOffset()
|
val today = getTodayWithOffset()
|
||||||
assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
|
assertThat(habit2.computedEntries.get(today).value, equalTo(100000))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,10 @@ class HistoryChartTest {
|
|||||||
1 -> DIMMED
|
1 -> DIMMED
|
||||||
else -> OFF
|
else -> OFF
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
notesIndicators = MutableList(85) {
|
||||||
|
index: Int ->
|
||||||
|
index % 3 == 0
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -86,20 +90,20 @@ class HistoryChartTest {
|
|||||||
|
|
||||||
// Click top left date
|
// Click top left date
|
||||||
view.onClick(20.0, 46.0)
|
view.onClick(20.0, 46.0)
|
||||||
verify(dateClickedListener).onDateClicked(LocalDate(2014, 10, 26))
|
verify(dateClickedListener).onDateShortPress(LocalDate(2014, 10, 26))
|
||||||
reset(dateClickedListener)
|
reset(dateClickedListener)
|
||||||
view.onClick(2.0, 28.0)
|
view.onClick(2.0, 28.0)
|
||||||
verify(dateClickedListener).onDateClicked(LocalDate(2014, 10, 26))
|
verify(dateClickedListener).onDateShortPress(LocalDate(2014, 10, 26))
|
||||||
reset(dateClickedListener)
|
reset(dateClickedListener)
|
||||||
|
|
||||||
// Click date in the middle
|
// Click date in the middle
|
||||||
view.onClick(163.0, 113.0)
|
view.onClick(163.0, 113.0)
|
||||||
verify(dateClickedListener).onDateClicked(LocalDate(2014, 12, 10))
|
verify(dateClickedListener).onDateShortPress(LocalDate(2014, 12, 10))
|
||||||
reset(dateClickedListener)
|
reset(dateClickedListener)
|
||||||
|
|
||||||
// Click today
|
// Click today
|
||||||
view.onClick(336.0, 37.0)
|
view.onClick(336.0, 37.0)
|
||||||
verify(dateClickedListener).onDateClicked(LocalDate(2015, 1, 25))
|
verify(dateClickedListener).onDateShortPress(LocalDate(2015, 1, 25))
|
||||||
reset(dateClickedListener)
|
reset(dateClickedListener)
|
||||||
|
|
||||||
// Click header
|
// Click header
|
||||||
@@ -111,6 +115,37 @@ class HistoryChartTest {
|
|||||||
verifyNoMoreInteractions(dateClickedListener)
|
verifyNoMoreInteractions(dateClickedListener)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testLongClick() = runBlocking {
|
||||||
|
assertRenders(400, 200, "$base/base.png", view)
|
||||||
|
|
||||||
|
// Click top left date
|
||||||
|
view.onLongClick(20.0, 46.0)
|
||||||
|
verify(dateClickedListener).onDateLongPress(LocalDate(2014, 10, 26))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
view.onLongClick(2.0, 28.0)
|
||||||
|
verify(dateClickedListener).onDateLongPress(LocalDate(2014, 10, 26))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click date in the middle
|
||||||
|
view.onLongClick(163.0, 113.0)
|
||||||
|
verify(dateClickedListener).onDateLongPress(LocalDate(2014, 12, 10))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click today
|
||||||
|
view.onLongClick(336.0, 37.0)
|
||||||
|
verify(dateClickedListener).onDateLongPress(LocalDate(2015, 1, 25))
|
||||||
|
reset(dateClickedListener)
|
||||||
|
|
||||||
|
// Click header
|
||||||
|
view.onLongClick(160.0, 15.0)
|
||||||
|
verifyNoMoreInteractions(dateClickedListener)
|
||||||
|
|
||||||
|
// Click right axis
|
||||||
|
view.onLongClick(360.0, 60.0)
|
||||||
|
verifyNoMoreInteractions(dateClickedListener)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDrawWeekDay() = runBlocking {
|
fun testDrawWeekDay() = runBlocking {
|
||||||
view.firstWeekday = DayOfWeek.MONDAY
|
view.firstWeekday = DayOfWeek.MONDAY
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
|
|||||||
fun testOnAddRepetition() {
|
fun testOnAddRepetition() {
|
||||||
behavior.onAddRepetition(habit, today)
|
behavior.onAddRepetition(habit, today)
|
||||||
verify(commandRunner).run(
|
verify(commandRunner).run(
|
||||||
CreateRepetitionCommand(habitList, habit, today, Entry.YES_MANUAL)
|
CreateRepetitionCommand(habitList, habit, today, Entry.YES_MANUAL, "")
|
||||||
)
|
)
|
||||||
verify(notificationTray).cancel(habit)
|
verify(notificationTray).cancel(habit)
|
||||||
verifyZeroInteractions(preferences)
|
verifyZeroInteractions(preferences)
|
||||||
@@ -68,7 +68,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
|
|||||||
fun testOnRemoveRepetition() {
|
fun testOnRemoveRepetition() {
|
||||||
behavior.onRemoveRepetition(habit, today)
|
behavior.onRemoveRepetition(habit, today)
|
||||||
verify(commandRunner).run(
|
verify(commandRunner).run(
|
||||||
CreateRepetitionCommand(habitList, habit, today, Entry.NO)
|
CreateRepetitionCommand(habitList, habit, today, Entry.NO, "")
|
||||||
)
|
)
|
||||||
verify(notificationTray).cancel(habit)
|
verify(notificationTray).cancel(habit)
|
||||||
verifyZeroInteractions(preferences)
|
verifyZeroInteractions(preferences)
|
||||||
@@ -94,7 +94,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
|
|||||||
behavior.onToggleRepetition(habit, today)
|
behavior.onToggleRepetition(habit, today)
|
||||||
verify(preferences).isSkipEnabled
|
verify(preferences).isSkipEnabled
|
||||||
verify(commandRunner).run(
|
verify(commandRunner).run(
|
||||||
CreateRepetitionCommand(habitList, habit, today, nextValue)
|
CreateRepetitionCommand(habitList, habit, today, nextValue, "")
|
||||||
)
|
)
|
||||||
verify(notificationTray).cancel(
|
verify(notificationTray).cancel(
|
||||||
habit
|
habit
|
||||||
@@ -110,7 +110,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
|
|||||||
habit.recompute()
|
habit.recompute()
|
||||||
behavior.onIncrement(habit, today, 100)
|
behavior.onIncrement(habit, today, 100)
|
||||||
verify(commandRunner).run(
|
verify(commandRunner).run(
|
||||||
CreateRepetitionCommand(habitList, habit, today, 600)
|
CreateRepetitionCommand(habitList, habit, today, 600, "")
|
||||||
)
|
)
|
||||||
verify(notificationTray).cancel(habit)
|
verify(notificationTray).cancel(habit)
|
||||||
verifyZeroInteractions(preferences)
|
verifyZeroInteractions(preferences)
|
||||||
@@ -123,7 +123,7 @@ class WidgetBehaviorTest : BaseUnitTest() {
|
|||||||
habit.recompute()
|
habit.recompute()
|
||||||
behavior.onDecrement(habit, today, 100)
|
behavior.onDecrement(habit, today, 100)
|
||||||
verify(commandRunner).run(
|
verify(commandRunner).run(
|
||||||
CreateRepetitionCommand(habitList, habit, today, 400)
|
CreateRepetitionCommand(habitList, habit, today, 400, "")
|
||||||
)
|
)
|
||||||
verify(notificationTray).cancel(habit)
|
verify(notificationTray).cancel(habit)
|
||||||
verifyZeroInteractions(preferences)
|
verifyZeroInteractions(preferences)
|
||||||
|
|||||||
Reference in New Issue
Block a user