Merge branch 'number-popup' into release/2.1.0

pull/1456/head
Alinson S. Xavier 3 years ago
commit 03b02aaa06
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -45,7 +45,7 @@ class EntryButtonViewTest : BaseViewTest() {
value = Entry.NO value = Entry.NO
color = PaletteUtils.getAndroidTestColor(5) color = PaletteUtils.getAndroidTestColor(5)
onToggle = { _, _, _ -> toggled = true } onToggle = { _, _, _ -> toggled = true }
onEdit = { _ -> edited = true } onEdit = { edited = true }
} }
measureView(view, dpToPixels(48), dpToPixels(48)) measureView(view, dpToPixels(48), dpToPixels(48))
} }

@ -76,7 +76,7 @@ class NumberPanelViewTest : BaseViewTest() {
@Test @Test
fun testEdit() { fun testEdit() {
val timestamps = mutableListOf<Timestamp>() val timestamps = mutableListOf<Timestamp>()
view.onEdit = { _, t -> timestamps.plusAssign(t) } view.onEdit = { t -> timestamps.plusAssign(t) }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
view.buttons[3].performLongClick() view.buttons[3].performLongClick()
@ -87,7 +87,7 @@ class NumberPanelViewTest : BaseViewTest() {
fun testEdit_withOffset() { fun testEdit_withOffset() {
val timestamps = mutableListOf<Timestamp>() val timestamps = mutableListOf<Timestamp>()
view.dataOffset = 3 view.dataOffset = 3
view.onEdit = { _, t -> timestamps += t } view.onEdit = { t -> timestamps += t }
view.buttons[0].performLongClick() view.buttons[0].performLongClick()
view.buttons[2].performLongClick() view.buttons[2].performLongClick()
view.buttons[3].performLongClick() view.buttons[3].performLongClick()

@ -125,7 +125,7 @@
android:exported="true" android:exported="true"
android:label="NumericalCheckmarkWidget" android:label="NumericalCheckmarkWidget"
android:noHistory="true" android:noHistory="true"
android:theme="@style/Theme.AppCompat.Light.Dialog"> android:theme="@style/Theme.Transparent">
<intent-filter> <intent-filter>
<action android:name="org.isoron.uhabits.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY" /> <action android:name="org.isoron.uhabits.ACTION_SHOW_NUMERICAL_VALUE_ACTIVITY" />
</intent-filter> </intent-filter>

@ -25,8 +25,8 @@ import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.View.GONE import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.PopupWindow import android.widget.PopupWindow
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry.Companion.NO 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.SKIP
@ -59,6 +59,7 @@ class CheckmarkPopup(
} }
init { init {
view.booleanButtons.visibility = VISIBLE
initColors() initColors()
initTypefaces() initTypefaces()
hideDisabledButtons() hideDisabledButtons()
@ -98,7 +99,7 @@ class CheckmarkPopup(
view.notes.setText(notes) view.notes.setText(notes)
} }
fun show(location: ScreenLocation) { fun show() {
val popup = PopupWindow() val popup = PopupWindow()
popup.contentView = view.root popup.contentView = view.root
popup.width = view.root.dp(POPUP_WIDTH).toInt() popup.width = view.root.dp(POPUP_WIDTH).toInt()
@ -116,12 +117,7 @@ class CheckmarkPopup(
popup.setOnDismissListener { popup.setOnDismissListener {
onToggle(value, view.notes.text.toString()) onToggle(value, view.notes.text.toString())
} }
popup.showAtLocation( popup.showAtLocation(anchor, Gravity.CENTER, 0, 0)
anchor,
Gravity.NO_GRAVITY,
view.root.dp(location.x.toFloat()).toInt(),
view.root.dp(location.y.toFloat()).toInt(),
)
popup.dimBehind() popup.dimBehind()
} }
} }

@ -1,205 +0,0 @@
/*
* 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/>.
*/
package org.isoron.uhabits.activities.common.dialogs
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.DialogInterface.BUTTON_NEGATIVE
import android.text.InputFilter
import android.text.Spanned
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import android.widget.NumberPicker
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Frequency.Companion.DAILY
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.InterfaceUtils
import java.text.DecimalFormatSymbols
import javax.inject.Inject
import kotlin.math.roundToLong
class NumberPickerFactory
@Inject constructor(
@ActivityContext private val context: Context
) {
@SuppressLint("SetTextI18n")
fun create(
value: Double,
unit: String,
notes: String,
dateString: String,
frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback
): AlertDialog {
clearCurrentDialog()
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.number_picker_dialog, null)
val picker = view.findViewById<NumberPicker>(R.id.picker)
val picker2 = view.findViewById<NumberPicker>(R.id.picker2)
val etNotes = view.findViewById<EditText>(R.id.etNotes)
// Install filter to intercept decimal separator before it is parsed
val watcherFilter: InputFilter = SeparatorWatcherInputFilter(picker2)
val pickerInputText = getNumberPickerInputText(picker)
pickerInputText.filters = arrayOf(watcherFilter).plus(pickerInputText.filters)
// Install custom focus listener to replace "5" by "50" instead of "05"
val picker2InputText = getNumberPickerInputText(picker2)
val prevFocusChangeListener = picker2InputText.onFocusChangeListener
picker2InputText.onFocusChangeListener = View.OnFocusChangeListener { v, hasFocus ->
val str = picker2InputText.text.toString()
if (str.length == 1) picker2InputText.setText("${str}0")
prevFocusChangeListener.onFocusChange(v, hasFocus)
}
view.findViewById<TextView>(R.id.tvUnit).text = unit
view.findViewById<TextView>(R.id.tvSeparator).text =
DecimalFormatSymbols.getInstance().decimalSeparator.toString()
val intValue = (value * 100).roundToLong().toInt()
picker.minValue = 0
picker.maxValue = Integer.MAX_VALUE / 100
picker.value = intValue / 100
picker.wrapSelectorWheel = false
picker2.minValue = 0
picker2.maxValue = 99
picker2.setFormatter { v -> String.format("%02d", v) }
picker2.value = intValue % 100
etNotes.setText(notes)
val dialogBuilder = AlertDialog.Builder(context)
.setView(view)
.setTitle(dateString)
.setPositiveButton(R.string.save) { _, _ ->
picker.clearFocus()
picker2.clearFocus()
val v = picker.value + 0.01 * picker2.value
val note = etNotes.text.toString().trim()
callback.onNumberPicked(v, note)
}
.setNegativeButton(android.R.string.cancel) { _, _ ->
callback.onNumberPickerDismissed()
}
.setOnDismissListener {
callback.onNumberPickerDismissed()
currentDialog = null
}
if (frequency == DAILY) {
dialogBuilder.setNegativeButton(R.string.skip_day) { _, _ ->
picker.clearFocus()
val v = Entry.SKIP.toDouble() / 1000
val note = etNotes.text.toString()
callback.onNumberPicked(v, note)
}
}
val dialog = dialogBuilder.create()
dialog.setOnShowListener {
val preferences =
(context.applicationContext as HabitsApplication).component.preferences
if (!preferences.isSkipEnabled) {
dialog.getButton(BUTTON_NEGATIVE).visibility = View.GONE
}
showSoftInput(dialog, pickerInputText)
}
InterfaceUtils.setupEditorAction(
picker
) { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
}
false
}
InterfaceUtils.setupEditorAction(
picker2
) { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
}
false
}
currentDialog = dialog
return dialog
}
@SuppressLint("DiscouragedPrivateApi")
private fun getNumberPickerInputText(picker: NumberPicker): EditText {
val f = NumberPicker::class.java.getDeclaredField("mInputText")
f.isAccessible = true
return f.get(picker) as EditText
}
private fun showSoftInput(dialog: AlertDialog, v: View) {
dialog.window?.setSoftInputMode(SOFT_INPUT_STATE_ALWAYS_VISIBLE)
v.requestFocus()
val inputMethodManager = context.getSystemService(InputMethodManager::class.java)
inputMethodManager?.showSoftInput(v, 0)
}
companion object {
private var currentDialog: AlertDialog? = null
fun clearCurrentDialog() {
currentDialog?.dismiss()
currentDialog = null
}
}
}
class SeparatorWatcherInputFilter(private val nextPicker: NumberPicker) : InputFilter {
override fun filter(
source: CharSequence?,
start: Int,
end: Int,
dest: Spanned?,
dstart: Int,
dend: Int
): CharSequence {
if (source == null || source.isEmpty()) {
return ""
}
for (c in source) {
if (c == DecimalFormatSymbols.getInstance().decimalSeparator || c == '.' || c == ',') {
nextPicker.performLongClick()
break
}
}
return source
}
}

@ -0,0 +1,106 @@
/*
* 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/>.
*/
package org.isoron.uhabits.activities.common.dialogs
import android.content.Context
import android.view.Gravity
import android.view.KeyEvent.KEYCODE_ENTER
import android.view.LayoutInflater
import android.view.MotionEvent.ACTION_DOWN
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.PopupWindow
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.dimBehind
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.requestFocusWithKeyboard
import java.text.DecimalFormat
class NumberPopup(
private val context: Context,
private var notes: String,
private var value: Double,
private val prefs: Preferences,
private val anchor: View,
) {
var onToggle: (Double, String) -> Unit = { _, _ -> }
private val originalValue = value
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
// Required for round corners
container.clipToOutline = true
}
init {
view.numberButtons.visibility = VISIBLE
hideDisabledButtons()
populate()
}
private fun hideDisabledButtons() {
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = GONE
}
private fun populate() {
view.notes.setText(notes)
view.value.setText(
when {
value < 0.01 -> "0"
else -> DecimalFormat("#.##").format(value)
}
)
}
fun show() {
val popup = PopupWindow()
popup.contentView = view.root
popup.width = view.root.dp(POPUP_WIDTH).toInt()
popup.height = view.root.dp(POPUP_HEIGHT).toInt()
popup.isFocusable = true
popup.elevation = view.root.dp(24f)
popup.setOnDismissListener {
save()
}
view.value.setOnKeyListener { _, keyCode, event ->
if (event.action == ACTION_DOWN && keyCode == KEYCODE_ENTER) {
popup.dismiss()
return@setOnKeyListener true
}
return@setOnKeyListener false
}
view.saveBtn.setOnClickListener { popup.dismiss() }
view.skipBtnNumber.setOnClickListener {
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
popup.dismiss()
}
popup.showAtLocation(anchor, Gravity.CENTER, 0, 0)
view.value.requestFocusWithKeyboard()
popup.dimBehind()
}
fun save() {
val value = view.value.text.toString().toDoubleOrNull() ?: originalValue
val notes = view.notes.text.toString()
onToggle(value, notes)
}
}

@ -24,14 +24,12 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy import dagger.Lazy
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.gui.toInt import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
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.NumberPopup
import org.isoron.uhabits.activities.common.dialogs.POPUP_WIDTH
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
@ -42,7 +40,6 @@ import org.isoron.uhabits.core.commands.CreateHabitCommand
import org.isoron.uhabits.core.commands.DeleteHabitsCommand import org.isoron.uhabits.core.commands.DeleteHabitsCommand
import org.isoron.uhabits.core.commands.EditHabitCommand import org.isoron.uhabits.core.commands.EditHabitCommand
import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand import org.isoron.uhabits.core.commands.UnarchiveHabitsCommand
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@ -95,7 +92,6 @@ class ListHabitsScreen
private val exportDBFactory: ExportDBTaskFactory, private val exportDBFactory: ExportDBTaskFactory,
private val importTaskFactory: ImportDataTaskFactory, private val importTaskFactory: ImportDataTaskFactory,
private val colorPickerFactory: ColorPickerDialogFactory, private val colorPickerFactory: ColorPickerDialogFactory,
private val numberPickerFactory: NumberPickerFactory,
private val behavior: Lazy<ListHabitsBehavior>, private val behavior: Lazy<ListHabitsBehavior>,
private val preferences: Preferences, private val preferences: Preferences,
private val rootView: Lazy<ListHabitsRootView>, private val rootView: Lazy<ListHabitsRootView>,
@ -231,22 +227,28 @@ class ListHabitsScreen
picker.show(activity.supportFragmentManager, "picker") picker.show(activity.supportFragmentManager, "picker")
} }
override fun showNumberPicker( override fun showNumberPopup(
value: Double, value: Double,
unit: String,
notes: String, notes: String,
dateString: String,
frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback callback: ListHabitsBehavior.NumberPickerCallback
) { ) {
numberPickerFactory.create(value, unit, notes, dateString, frequency, callback).show() val view = rootView.get()
NumberPopup(
context = context,
prefs = preferences,
anchor = view,
notes = notes,
value = value,
).apply {
onToggle = { value, notes -> callback.onNumberPicked(value, notes) }
show()
}
} }
override fun showCheckmarkPopup( override fun showCheckmarkPopup(
selectedValue: Int, selectedValue: Int,
notes: String, notes: String,
color: PaletteColor, color: PaletteColor,
location: ScreenLocation,
callback: ListHabitsBehavior.CheckMarkDialogCallback callback: ListHabitsBehavior.CheckMarkDialogCallback
) { ) {
val view = rootView.get() val view = rootView.get()
@ -259,12 +261,7 @@ class ListHabitsScreen
value = selectedValue, value = selectedValue,
).apply { ).apply {
onToggle = { value, notes -> callback.onNotesSaved(value, notes) } onToggle = { value, notes -> callback.onNotesSaved(value, notes) }
show( show()
ScreenLocation(
x = location.x - POPUP_WIDTH / 2,
y = location.y
)
)
} }
} }

@ -28,7 +28,6 @@ import android.text.TextPaint
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.View import android.view.View
import android.view.View.MeasureSpec.EXACTLY import android.view.View.MeasureSpec.EXACTLY
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.R import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.NO import org.isoron.uhabits.core.models.Entry.Companion.NO
@ -39,7 +38,6 @@ 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.drawNotesIndicator
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.getFontAwesome import org.isoron.uhabits.utils.getFontAwesome
import org.isoron.uhabits.utils.sp import org.isoron.uhabits.utils.sp
import org.isoron.uhabits.utils.sres import org.isoron.uhabits.utils.sres
@ -83,7 +81,7 @@ class CheckmarkButtonView(
var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> } var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> }
var onEdit: (ScreenLocation) -> Unit = { _ -> } var onEdit: () -> Unit = { }
private var drawer = Drawer() private var drawer = Drawer()
@ -105,11 +103,11 @@ class CheckmarkButtonView(
override fun onClick(v: View) { override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS) if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
else onEdit(getCenter()) else onEdit()
} }
override fun onLongClick(v: View): Boolean { override fun onLongClick(v: View): Boolean {
if (preferences.isShortToggleEnabled) onEdit(getCenter()) if (preferences.isShortToggleEnabled) onEdit()
else performToggle(TOGGLE_DELAY_MILLIS) else performToggle(TOGGLE_DELAY_MILLIS)
return true return true
} }

@ -20,7 +20,6 @@
package org.isoron.uhabits.activities.habits.list.views package org.isoron.uhabits.activities.habits.list.views
import android.content.Context import android.content.Context
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN import org.isoron.uhabits.core.models.Entry.Companion.UNKNOWN
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
@ -67,7 +66,7 @@ class CheckmarkPanelView(
setupButtons() setupButtons()
} }
var onEdit: (ScreenLocation, Timestamp) -> Unit = { _, _ -> } var onEdit: (Timestamp) -> Unit = { _ -> }
set(value) { set(value) {
field = value field = value
setupButtons() setupButtons()
@ -91,7 +90,7 @@ class CheckmarkPanelView(
} }
button.color = color button.color = color
button.onToggle = { value, notes, delay -> onToggle(timestamp, value, notes, delay) } button.onToggle = { value, notes, delay -> onToggle(timestamp, value, notes, delay) }
button.onEdit = { location -> onEdit(location, timestamp) } button.onEdit = { onEdit(timestamp) }
} }
} }
} }

@ -167,17 +167,17 @@ class HabitCardView(
{ runPendingToggles(taskId) }.delay(delay) { runPendingToggles(taskId) }.delay(delay)
} }
} }
onEdit = { location, timestamp -> onEdit = { timestamp ->
triggerRipple(timestamp) triggerRipple(timestamp)
habit?.let { behavior.onEdit(location, it, timestamp) } habit?.let { behavior.onEdit(it, timestamp) }
} }
} }
numberPanel = numberPanelFactory.create().apply { numberPanel = numberPanelFactory.create().apply {
visibility = GONE visibility = GONE
onEdit = { location, timestamp -> onEdit = { timestamp ->
triggerRipple(timestamp) triggerRipple(timestamp)
habit?.let { behavior.onEdit(location, it, timestamp) } habit?.let { behavior.onEdit(it, timestamp) }
} }
} }

@ -108,7 +108,8 @@ class NumberButtonView(
invalidate() invalidate()
} }
var onEdit: () -> Unit = {} var onEdit: () -> Unit = { }
private var drawer: Drawer = Drawer(context) private var drawer: Drawer = Drawer(context)
init { init {

@ -20,13 +20,11 @@
package org.isoron.uhabits.activities.habits.list.views package org.isoron.uhabits.activities.habits.list.views
import android.content.Context import android.content.Context
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.core.models.NumericalHabitType import org.isoron.uhabits.core.models.NumericalHabitType
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.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.getCenter
import javax.inject.Inject import javax.inject.Inject
class NumberPanelViewFactory class NumberPanelViewFactory
@ -80,7 +78,7 @@ class NumberPanelView(
setupButtons() setupButtons()
} }
var onEdit: (ScreenLocation, Timestamp) -> Unit = { _, _ -> } var onEdit: (Timestamp) -> Unit = { _ -> }
set(value) { set(value) {
field = value field = value
setupButtons() setupButtons()
@ -106,7 +104,7 @@ class NumberPanelView(
button.targetType = targetType button.targetType = targetType
button.threshold = threshold button.threshold = threshold
button.units = units button.units = units
button.onEdit = { onEdit(getCenter(), timestamp) } button.onEdit = { onEdit(timestamp) }
} }
} }
} }

@ -23,11 +23,11 @@ import android.os.Bundle
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.gui.toInt import org.isoron.platform.gui.toInt
import org.isoron.uhabits.AndroidDirFinder import org.isoron.uhabits.AndroidDirFinder
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
@ -37,11 +37,9 @@ import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
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.NumberPopup
import org.isoron.uhabits.activities.common.dialogs.POPUP_WIDTH
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.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.PaletteColor import org.isoron.uhabits.core.models.PaletteColor
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
@ -52,7 +50,6 @@ import org.isoron.uhabits.core.ui.screens.habits.show.ShowHabitPresenter
import org.isoron.uhabits.core.ui.views.OnDateClickedListener import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.intents.IntentFactory import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.currentTheme import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.getTopLeftCorner
import org.isoron.uhabits.utils.showMessage import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater import org.isoron.uhabits.widgets.WidgetUpdater
@ -169,22 +166,23 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
window.decorView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY) window.decorView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
} }
override fun showNumberPicker( override fun showNumberPopup(
value: Double, value: Double,
unit: String,
notes: String, notes: String,
dateString: String, preferences: Preferences,
frequency: Frequency,
callback: ListHabitsBehavior.NumberPickerCallback callback: ListHabitsBehavior.NumberPickerCallback
) { ) {
NumberPickerFactory(this@ShowHabitActivity).create( val anchor = getPopupAnchor() ?: return
value, NumberPopup(
unit, context = this@ShowHabitActivity,
notes, prefs = preferences,
dateString, notes = notes,
frequency, anchor = anchor,
callback value = value,
).show() ).apply {
onToggle = { v, n -> callback.onNumberPicked(v, n) }
show()
}
} }
override fun showCheckmarkPopup( override fun showCheckmarkPopup(
@ -192,32 +190,27 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
notes: String, notes: String,
preferences: Preferences, preferences: Preferences,
color: PaletteColor, color: PaletteColor,
location: ScreenLocation,
callback: ListHabitsBehavior.CheckMarkDialogCallback callback: ListHabitsBehavior.CheckMarkDialogCallback
) { ) {
val dialog = val anchor = getPopupAnchor() ?: return
supportFragmentManager.findFragmentByTag("historyEditor") as HistoryEditorDialog?
?: return
val view = dialog.dataView
val corner = view.getTopLeftCorner()
CheckmarkPopup( CheckmarkPopup(
context = this@ShowHabitActivity, context = this@ShowHabitActivity,
prefs = preferences, prefs = preferences,
notes = notes, notes = notes,
color = view.currentTheme().color(color).toInt(), color = view.currentTheme().color(color).toInt(),
anchor = view, anchor = anchor,
value = selectedValue, value = selectedValue,
).apply { ).apply {
onToggle = { v, n -> callback.onNotesSaved(v, n) } onToggle = { v, n -> callback.onNotesSaved(v, n) }
show( show()
ScreenLocation(
x = corner.x + location.x - POPUP_WIDTH / 2,
y = corner.y + location.y,
)
)
} }
} }
private fun getPopupAnchor(): View? {
val dialog = supportFragmentManager.findFragmentByTag("historyEditor") as HistoryEditorDialog?
return dialog?.dataView
}
override fun showEditHabitScreen(habit: Habit) { override fun showEditHabitScreen(habit: Habit) {
startActivity(IntentFactory().startEditActivity(this@ShowHabitActivity, habit)) startActivity(IntentFactory().startEditActivity(this@ShowHabitActivity, habit))
} }

@ -28,7 +28,9 @@ import android.graphics.Color
import android.graphics.Paint import android.graphics.Paint
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.os.Handler import android.os.Handler
import android.os.SystemClock
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
@ -45,7 +47,6 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.gui.toInt import org.isoron.platform.gui.toInt
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R import org.isoron.uhabits.R
@ -229,30 +230,15 @@ fun PopupWindow.dimBehind() {
wm.updateViewLayout(container, p) wm.updateViewLayout(container, p)
} }
/** fun View.requestFocusWithKeyboard() {
* Returns the absolute screen coordinates for the center of this view (in density-independent // For some reason, Android does not open the soft keyboard by default when view.requestFocus
* pixels). // is called. Several online solutions suggest using InputMethodManager, but these solutions
*/ // are not reliable; sometimes the keyboard does not show, and sometimes it does not go away
fun View.getCenter(): ScreenLocation { // after focus is lost. Here, we simulate a click on the view, which triggers the keyboard.
val density = resources.displayMetrics.density // Based on: https://stackoverflow.com/a/7699556
val loc = IntArray(2) postDelayed({
this.getLocationInWindow(loc) val time = SystemClock.uptimeMillis()
return ScreenLocation( dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
x = ((loc[0] + width / 2) / density).toDouble(), dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, 0f, 0f, 0))
y = ((loc[1] + height / 2) / density).toDouble(), }, 250)
)
}
/**
* Returns the absolute screen coordinates for the top left corner of this view (in
* density-independent pixels).
*/
fun View.getTopLeftCorner(): ScreenLocation {
val density = resources.displayMetrics.density
val loc = IntArray(2)
this.getLocationInWindow(loc)
return ScreenLocation(
x = (loc[0] / density).toDouble(),
y = (loc[1] / density).toDouble(),
)
} }

@ -22,11 +22,12 @@ package org.isoron.uhabits.widgets.activities
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.view.Window import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout import android.widget.FrameLayout
import org.isoron.uhabits.HabitsApplication import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.activities.AndroidThemeSwitcher import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.common.dialogs.NumberPickerFactory import org.isoron.uhabits.activities.common.dialogs.NumberPopup
import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior import org.isoron.uhabits.core.ui.screens.habits.list.ListHabitsBehavior
import org.isoron.uhabits.core.ui.widgets.WidgetBehavior import org.isoron.uhabits.core.ui.widgets.WidgetBehavior
import org.isoron.uhabits.core.utils.DateUtils import org.isoron.uhabits.core.utils.DateUtils
@ -39,11 +40,13 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
private lateinit var behavior: WidgetBehavior private lateinit var behavior: WidgetBehavior
private lateinit var data: IntentParser.CheckmarkIntentData private lateinit var data: IntentParser.CheckmarkIntentData
private lateinit var widgetUpdater: WidgetUpdater private lateinit var widgetUpdater: WidgetUpdater
private lateinit var rootView: View
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE) rootView = FrameLayout(this)
setContentView(FrameLayout(this)) rootView.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
setContentView(rootView)
val app = this.applicationContext as HabitsApplication val app = this.applicationContext as HabitsApplication
val component = app.component val component = app.component
val parser = app.component.intentParser val parser = app.component.intentParser
@ -55,8 +58,9 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
component.preferences component.preferences
) )
widgetUpdater = component.widgetUpdater widgetUpdater = component.widgetUpdater
showNumberSelector(this) rootView.post {
showNumberSelector(this)
}
SystemUtils.unlockScreen(this) SystemUtils.unlockScreen(this)
} }
@ -73,17 +77,22 @@ class NumericalCheckmarkWidgetActivity : Activity(), ListHabitsBehavior.NumberPi
private fun showNumberSelector(context: Context) { private fun showNumberSelector(context: Context) {
val app = this.applicationContext as HabitsApplication val app = this.applicationContext as HabitsApplication
AndroidThemeSwitcher(this, app.component.preferences).apply() AndroidThemeSwitcher(this, app.component.preferences).apply()
val numberPickerFactory = NumberPickerFactory(context)
val today = DateUtils.getTodayWithOffset() val today = DateUtils.getTodayWithOffset()
val entry = data.habit.computedEntries.get(today) val entry = data.habit.computedEntries.get(today)
numberPickerFactory.create( NumberPopup(
entry.value / 1000.0, context = context,
data.habit.unit, prefs = app.component.preferences,
entry.notes, anchor = rootView,
today.toDialogDateString(), notes = entry.notes,
data.habit.frequency, value = entry.value / 1000.0,
this ).apply {
).show() onToggle = { value, notes ->
onNumberPicked(value, notes)
finish()
overridePendingTransition(0, 0)
}
show()
}
} }
companion object { companion object {

@ -42,6 +42,8 @@
android:text="" /> android:text="" />
<androidx.appcompat.widget.LinearLayoutCompat <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/booleanButtons"
android:visibility="gone"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="48dp" android:layout_height="48dp"
android:orientation="horizontal" android:orientation="horizontal"
@ -69,4 +71,34 @@
android:text="@string/fa_question" /> android:text="@string/fa_question" />
</androidx.appcompat.widget.LinearLayoutCompat> </androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat> <androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/numberButtons"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/value"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/transparent"
android:textAlignment="center"
android:inputType="numberDecimal"
android:selectAllOnFocus="true"
android:textSize="@dimen/smallTextSize" />
<TextView
android:id="@+id/skipBtnNumber"
style="@style/NumericalPopupBtn"
android:text="@string/skip_day" />
<TextView
android:id="@+id/saveBtn"
style="@style/NumericalPopupBtn"
android:text="@string/save" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>

@ -398,4 +398,28 @@
<item name="android:textSize">@dimen/smallerTextSize</item> <item name="android:textSize">@dimen/smallerTextSize</item>
</style> </style>
<style name="NumericalPopupBtn">
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">match_parent</item>
<item name="android:paddingStart">12dp</item>
<item name="android:paddingEnd">12dp</item>
<item name="android:textStyle">bold</item>
<item name="android:gravity">center</item>
<item name="android:clickable">true</item>
<item name="android:focusable">true</item>
<item name="android:background">@drawable/ripple_transparent</item>
<item name="android:textSize">@dimen/smallerTextSize</item>
<item name="android:textAllCaps">true</item>
</style>
<style name="Theme.Transparent" parent="android:Theme">
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:backgroundDimEnabled">false</item>
<item name="android:windowAnimationStyle">@null</item>
</style>
</resources> </resources>

@ -18,10 +18,8 @@
*/ */
package org.isoron.uhabits.core.ui.screens.habits.list package org.isoron.uhabits.core.ui.screens.habits.list
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.core.commands.CommandRunner 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.Frequency
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.HabitType
@ -50,17 +48,11 @@ open class ListHabitsBehavior @Inject constructor(
screen.showHabitScreen(h) screen.showHabitScreen(h)
} }
fun onEdit(location: ScreenLocation, habit: Habit, timestamp: Timestamp?) { fun onEdit(habit: Habit, timestamp: Timestamp?) {
val entry = habit.computedEntries.get(timestamp!!) val entry = habit.computedEntries.get(timestamp!!)
if (habit.type == HabitType.NUMERICAL) { if (habit.type == HabitType.NUMERICAL) {
val oldValue = entry.value.toDouble() val oldValue = entry.value.toDouble() / 1000
screen.showNumberPicker( screen.showNumberPopup(oldValue, entry.notes) { newValue: Double, newNotes: String ->
oldValue / 1000,
habit.unit,
entry.notes,
timestamp.toDialogDateString(),
habit.frequency
) { newValue: Double, newNotes: String, ->
val value = (newValue * 1000).roundToInt() val value = (newValue * 1000).roundToInt()
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes)) commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
} }
@ -69,7 +61,6 @@ open class ListHabitsBehavior @Inject constructor(
entry.value, entry.value,
entry.notes, entry.notes,
habit.color, habit.color,
location,
) { newValue, newNotes -> ) { newValue, newNotes ->
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes)) commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, newValue, newNotes))
} }
@ -162,19 +153,15 @@ open class ListHabitsBehavior @Inject constructor(
fun showHabitScreen(h: Habit) fun showHabitScreen(h: Habit)
fun showIntroScreen() fun showIntroScreen()
fun showMessage(m: Message) fun showMessage(m: Message)
fun showNumberPicker( fun showNumberPopup(
value: Double, value: Double,
unit: String,
notes: String, notes: String,
dateString: String,
frequency: Frequency,
callback: NumberPickerCallback callback: NumberPickerCallback
) )
fun showCheckmarkPopup( fun showCheckmarkPopup(
selectedValue: Int, selectedValue: Int,
notes: String, notes: String,
color: PaletteColor, color: PaletteColor,
location: ScreenLocation,
callback: CheckMarkDialogCallback callback: CheckMarkDialogCallback
) )
fun showSendBugReportToDeveloperScreen(log: String) fun showSendBugReportToDeveloperScreen(log: String)

@ -19,7 +19,6 @@
package org.isoron.uhabits.core.ui.screens.habits.show.views package org.isoron.uhabits.core.ui.screens.habits.show.views
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.time.DayOfWeek import org.isoron.platform.time.DayOfWeek
import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate
import org.isoron.uhabits.core.commands.CommandRunner import org.isoron.uhabits.core.commands.CommandRunner
@ -28,7 +27,6 @@ import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Entry.Companion.SKIP import org.isoron.uhabits.core.models.Entry.Companion.SKIP
import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO import org.isoron.uhabits.core.models.Entry.Companion.YES_AUTO
import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL import org.isoron.uhabits.core.models.Entry.Companion.YES_MANUAL
import org.isoron.uhabits.core.models.Frequency
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.NumericalHabitType.AT_LEAST import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
@ -66,36 +64,35 @@ class HistoryCardPresenter(
val screen: Screen, val screen: Screen,
) : OnDateClickedListener { ) : OnDateClickedListener {
override fun onDateLongPress(location: ScreenLocation, 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) {
showNumberPicker(timestamp) showNumberPopup(timestamp)
} else { } else {
if (preferences.isShortToggleEnabled) showCheckmarkPopup(location, timestamp) if (preferences.isShortToggleEnabled) showCheckmarkPopup(timestamp)
else toggle(timestamp) else toggle(timestamp)
} }
} }
override fun onDateShortPress(location: ScreenLocation, date: LocalDate) { override fun onDateShortPress(date: LocalDate) {
val timestamp = Timestamp.fromLocalDate(date) val timestamp = Timestamp.fromLocalDate(date)
screen.showFeedback() screen.showFeedback()
if (habit.isNumerical) { if (habit.isNumerical) {
showNumberPicker(timestamp) showNumberPopup(timestamp)
} else { } else {
if (preferences.isShortToggleEnabled) toggle(timestamp) if (preferences.isShortToggleEnabled) toggle(timestamp)
else showCheckmarkPopup(location, timestamp) else showCheckmarkPopup(timestamp)
} }
} }
private fun showCheckmarkPopup(location: ScreenLocation, timestamp: Timestamp) { private fun showCheckmarkPopup(timestamp: Timestamp) {
val entry = habit.computedEntries.get(timestamp) val entry = habit.computedEntries.get(timestamp)
screen.showCheckmarkPopup( screen.showCheckmarkPopup(
entry.value, entry.value,
entry.notes, entry.notes,
preferences, preferences,
habit.color, habit.color,
location,
) { newValue, newNotes -> ) { newValue, newNotes ->
commandRunner.run( commandRunner.run(
CreateRepetitionCommand( CreateRepetitionCommand(
@ -127,15 +124,13 @@ class HistoryCardPresenter(
) )
} }
private fun showNumberPicker(timestamp: Timestamp) { private fun showNumberPopup(timestamp: Timestamp) {
val entry = habit.computedEntries.get(timestamp) val entry = habit.computedEntries.get(timestamp)
val oldValue = entry.value val oldValue = entry.value
screen.showNumberPicker( screen.showNumberPopup(
oldValue / 1000.0, value = oldValue / 1000.0,
habit.unit, notes = entry.notes,
entry.notes, preferences = preferences,
timestamp.toDialogDateString(),
frequency = habit.frequency
) { newValue: Double, newNotes: String -> ) { newValue: Double, newNotes: String ->
val thousands = (newValue * 1000).roundToInt() val thousands = (newValue * 1000).roundToInt()
commandRunner.run( commandRunner.run(
@ -205,21 +200,17 @@ class HistoryCardPresenter(
interface Screen { interface Screen {
fun showHistoryEditorDialog(listener: OnDateClickedListener) fun showHistoryEditorDialog(listener: OnDateClickedListener)
fun showFeedback() fun showFeedback()
fun showNumberPicker( fun showNumberPopup(
value: Double, value: Double,
unit: String,
notes: String, notes: String,
dateString: String, preferences: Preferences,
frequency: Frequency, callback: ListHabitsBehavior.NumberPickerCallback,
callback: ListHabitsBehavior.NumberPickerCallback
) )
fun showCheckmarkPopup( fun showCheckmarkPopup(
selectedValue: Int, selectedValue: Int,
notes: String, notes: String,
preferences: Preferences, preferences: Preferences,
color: PaletteColor, color: PaletteColor,
location: ScreenLocation,
callback: ListHabitsBehavior.CheckMarkDialogCallback, callback: ListHabitsBehavior.CheckMarkDialogCallback,
) )
} }

@ -22,7 +22,6 @@ package org.isoron.uhabits.core.ui.views
import org.isoron.platform.gui.Canvas import org.isoron.platform.gui.Canvas
import org.isoron.platform.gui.Color import org.isoron.platform.gui.Color
import org.isoron.platform.gui.DataView import org.isoron.platform.gui.DataView
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.gui.TextAlign import org.isoron.platform.gui.TextAlign
import org.isoron.platform.time.DayOfWeek import org.isoron.platform.time.DayOfWeek
import org.isoron.platform.time.LocalDate import org.isoron.platform.time.LocalDate
@ -34,8 +33,8 @@ import kotlin.math.min
import kotlin.math.round import kotlin.math.round
interface OnDateClickedListener { interface OnDateClickedListener {
fun onDateShortPress(location: ScreenLocation, date: LocalDate) {} fun onDateShortPress(date: LocalDate) {}
fun onDateLongPress(location: ScreenLocation, date: LocalDate) {} fun onDateLongPress(date: LocalDate) {}
} }
class HistoryChart( class HistoryChart(
@ -91,11 +90,10 @@ class HistoryChart(
if (x - padding < 0 || row == 0 || row > 7 || col == nColumns) return if (x - padding < 0 || row == 0 || row > 7 || col == nColumns) return
val clickedDate = topLeftDate.plus(offset) val clickedDate = topLeftDate.plus(offset)
if (clickedDate.isNewerThan(today)) return if (clickedDate.isNewerThan(today)) return
val location = ScreenLocation(x, y)
if (isLongClick) { if (isLongClick) {
onDateClickedListener.onDateLongPress(location, clickedDate) onDateClickedListener.onDateLongPress(clickedDate)
} else { } else {
onDateClickedListener.onDateShortPress(location, clickedDate) onDateClickedListener.onDateShortPress(clickedDate)
} }
} }

@ -31,10 +31,8 @@ import junit.framework.Assert.assertTrue
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.core.IsEqual.equalTo import org.hamcrest.core.IsEqual.equalTo
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.core.BaseUnitTest import org.isoron.uhabits.core.BaseUnitTest
import org.isoron.uhabits.core.models.Entry import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.Frequency
import org.isoron.uhabits.core.models.Habit import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.preferences.Preferences import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
@ -80,13 +78,10 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
@Test @Test
fun testOnEdit() { fun testOnEdit() {
behavior.onEdit(ScreenLocation(0.0, 0.0), habit2, getToday()) behavior.onEdit(habit2, getToday())
verify(screen).showNumberPicker( verify(screen).showNumberPopup(
eq(0.1), eq(0.1),
eq("miles"),
eq(""), eq(""),
eq("Jan 25, 2015"),
eq(Frequency.DAILY),
picker.capture() picker.capture()
) )
picker.lastValue.onNumberPicked(100.0, "") picker.lastValue.onNumberPicked(100.0, "")

@ -24,7 +24,6 @@ import com.nhaarman.mockitokotlin2.reset
import com.nhaarman.mockitokotlin2.verify import com.nhaarman.mockitokotlin2.verify
import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions import com.nhaarman.mockitokotlin2.verifyNoMoreInteractions
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.isoron.platform.gui.ScreenLocation
import org.isoron.platform.gui.assertRenders import org.isoron.platform.gui.assertRenders
import org.isoron.platform.time.DayOfWeek import org.isoron.platform.time.DayOfWeek
import org.isoron.platform.time.DayOfWeek.SUNDAY import org.isoron.platform.time.DayOfWeek.SUNDAY
@ -90,32 +89,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).onDateShortPress( verify(dateClickedListener).onDateShortPress(LocalDate(2014, 10, 26))
ScreenLocation(20.0, 46.0),
LocalDate(2014, 10, 26)
)
reset(dateClickedListener) reset(dateClickedListener)
view.onClick(2.0, 28.0) view.onClick(2.0, 28.0)
verify(dateClickedListener).onDateShortPress( verify(dateClickedListener).onDateShortPress(LocalDate(2014, 10, 26))
ScreenLocation(2.0, 28.0),
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).onDateShortPress( verify(dateClickedListener).onDateShortPress(LocalDate(2014, 12, 10))
ScreenLocation(163.0, 113.0),
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).onDateShortPress( verify(dateClickedListener).onDateShortPress(LocalDate(2015, 1, 25))
ScreenLocation(336.0, 37.0),
LocalDate(2015, 1, 25)
)
reset(dateClickedListener) reset(dateClickedListener)
// Click header // Click header
@ -133,32 +120,20 @@ class HistoryChartTest {
// Click top left date // Click top left date
view.onLongClick(20.0, 46.0) view.onLongClick(20.0, 46.0)
verify(dateClickedListener).onDateLongPress( verify(dateClickedListener).onDateLongPress(LocalDate(2014, 10, 26))
ScreenLocation(20.0, 46.0),
LocalDate(2014, 10, 26)
)
reset(dateClickedListener) reset(dateClickedListener)
view.onLongClick(2.0, 28.0) view.onLongClick(2.0, 28.0)
verify(dateClickedListener).onDateLongPress( verify(dateClickedListener).onDateLongPress(LocalDate(2014, 10, 26))
ScreenLocation(2.0, 28.0),
LocalDate(2014, 10, 26)
)
reset(dateClickedListener) reset(dateClickedListener)
// Click date in the middle // Click date in the middle
view.onLongClick(163.0, 113.0) view.onLongClick(163.0, 113.0)
verify(dateClickedListener).onDateLongPress( verify(dateClickedListener).onDateLongPress(LocalDate(2014, 12, 10))
ScreenLocation(163.0, 113.0),
LocalDate(2014, 12, 10)
)
reset(dateClickedListener) reset(dateClickedListener)
// Click today // Click today
view.onLongClick(336.0, 37.0) view.onLongClick(336.0, 37.0)
verify(dateClickedListener).onDateLongPress( verify(dateClickedListener).onDateLongPress(LocalDate(2015, 1, 25))
ScreenLocation(336.0, 37.0),
LocalDate(2015, 1, 25)
)
reset(dateClickedListener) reset(dateClickedListener)
// Click header // Click header

Loading…
Cancel
Save