Implement NumberPopup

pull/1370/head
Alinson S. Xavier 3 years ago
parent f04e37e905
commit d1de3a852b
No known key found for this signature in database
GPG Key ID: DCA0DAD4D2F58624

@ -25,6 +25,7 @@ import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.PopupWindow
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.R
@ -59,6 +60,7 @@ class CheckmarkPopup(
}
init {
view.booleanButtons.visibility = VISIBLE
initColors()
initTypefaces()
hideDisabledButtons()

@ -0,0 +1,112 @@
/*
* 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.platform.gui.ScreenLocation
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(location: ScreenLocation) {
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.NO_GRAVITY,
view.root.dp(location.x.toFloat()).toInt(),
view.root.dp(location.y.toFloat()).toInt(),
)
view.value.requestFocusWithKeyboard()
popup.dimBehind()
}
fun save() {
val value = view.value.text.toString().toDoubleOrNull() ?: originalValue
val notes = view.notes.text.toString()
onToggle(value, notes)
}
}

@ -31,6 +31,7 @@ import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
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.list.views.HabitCardListAdapter
@ -242,6 +243,25 @@ class ListHabitsScreen
numberPickerFactory.create(value, unit, notes, dateString, frequency, callback).show()
}
override fun showNumberPopup(
value: Double,
notes: String,
location: ScreenLocation,
callback: ListHabitsBehavior.NumberPickerCallback
) {
val view = rootView.get()
NumberPopup(
context = context,
prefs = preferences,
anchor = view,
notes = notes,
value = value,
).apply {
onToggle = { value, notes -> callback.onNumberPicked(value, notes) }
show(getPopupLocation(location))
}
}
override fun showCheckmarkPopup(
selectedValue: Int,
notes: String,
@ -259,15 +279,15 @@ class ListHabitsScreen
value = selectedValue,
).apply {
onToggle = { value, notes -> callback.onNotesSaved(value, notes) }
show(
ScreenLocation(
x = location.x - POPUP_WIDTH / 2,
y = location.y
)
)
show(getPopupLocation(location))
}
}
private fun getPopupLocation(clickLocation: ScreenLocation) = ScreenLocation(
x = clickLocation.x - POPUP_WIDTH / 2,
y = clickLocation.y
)
private fun getExecuteString(command: Command): String? {
when (command) {
is ArchiveHabitsCommand -> {

@ -28,6 +28,7 @@ import android.text.TextPaint
import android.view.View
import android.view.View.OnClickListener
import android.view.View.OnLongClickListener
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.core.models.NumericalHabitType.AT_LEAST
@ -37,6 +38,7 @@ import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.InterfaceUtils.getDimension
import org.isoron.uhabits.utils.dim
import org.isoron.uhabits.utils.drawNotesIndicator
import org.isoron.uhabits.utils.getCenter
import org.isoron.uhabits.utils.getFontAwesome
import org.isoron.uhabits.utils.sres
import java.text.DecimalFormat
@ -108,7 +110,8 @@ class NumberButtonView(
invalidate()
}
var onEdit: () -> Unit = {}
var onEdit: (ScreenLocation) -> Unit = { _ -> }
private var drawer: Drawer = Drawer(context)
init {
@ -117,11 +120,11 @@ class NumberButtonView(
}
override fun onClick(v: View) {
onEdit()
onEdit(getCenter())
}
override fun onLongClick(v: View): Boolean {
onEdit()
onEdit(getCenter())
return true
}

@ -26,7 +26,6 @@ import org.isoron.uhabits.core.models.Timestamp
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils
import org.isoron.uhabits.inject.ActivityContext
import org.isoron.uhabits.utils.getCenter
import javax.inject.Inject
class NumberPanelViewFactory
@ -106,7 +105,7 @@ class NumberPanelView(
button.targetType = targetType
button.threshold = threshold
button.units = units
button.onEdit = { onEdit(getCenter(), timestamp) }
button.onEdit = { location -> onEdit(location, timestamp) }
}
}
}

@ -205,7 +205,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
prefs = preferences,
notes = notes,
color = view.currentTheme().color(color).toInt(),
anchor = view,
anchor = anchor,
value = selectedValue,
).apply {
onToggle = { v, n -> callback.onNotesSaved(v, n) }

@ -28,12 +28,15 @@ import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.SystemClock
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowManager
import android.widget.EditText
import android.widget.PopupWindow
import android.widget.RelativeLayout
import android.widget.RelativeLayout.ALIGN_PARENT_BOTTOM
@ -256,3 +259,16 @@ fun View.getTopLeftCorner(): ScreenLocation {
y = (loc[1] / density).toDouble(),
)
}
fun View.requestFocusWithKeyboard() {
// For some reason, Android does not open the soft keyboard by default when view.requestFocus
// 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
// after focus is lost. Here, we simulate a click on the view, which triggers the keyboard.
// Based on: https://stackoverflow.com/a/7699556
postDelayed({
val time = SystemClock.uptimeMillis()
dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_DOWN, 0f, 0f, 0))
dispatchTouchEvent(MotionEvent.obtain(time, time, MotionEvent.ACTION_UP, 0f, 0f, 0))
}, 250)
}

@ -42,6 +42,8 @@
android:text="" />
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/booleanButtons"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="48dp"
android:orientation="horizontal"
@ -69,4 +71,34 @@
android:text="@string/fa_question" />
</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,19 @@
<item name="android:textSize">@dimen/smallerTextSize</item>
</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>
</resources>

@ -53,14 +53,8 @@ open class ListHabitsBehavior @Inject constructor(
fun onEdit(location: ScreenLocation, habit: Habit, timestamp: Timestamp?) {
val entry = habit.computedEntries.get(timestamp!!)
if (habit.type == HabitType.NUMERICAL) {
val oldValue = entry.value.toDouble()
screen.showNumberPicker(
oldValue / 1000,
habit.unit,
entry.notes,
timestamp.toDialogDateString(),
habit.frequency
) { newValue: Double, newNotes: String, ->
val oldValue = entry.value.toDouble() / 1000
screen.showNumberPopup(oldValue, entry.notes, location) { newValue: Double, newNotes: String ->
val value = (newValue * 1000).roundToInt()
commandRunner.run(CreateRepetitionCommand(habitList, habit, timestamp, value, newNotes))
}
@ -170,6 +164,12 @@ open class ListHabitsBehavior @Inject constructor(
frequency: Frequency,
callback: NumberPickerCallback
)
fun showNumberPopup(
value: Double,
notes: String,
location: ScreenLocation,
callback: NumberPickerCallback
)
fun showCheckmarkPopup(
selectedValue: Int,
notes: String,

@ -34,7 +34,6 @@ import org.hamcrest.core.IsEqual.equalTo
import org.isoron.platform.gui.ScreenLocation
import org.isoron.uhabits.core.BaseUnitTest
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.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
@ -81,12 +80,10 @@ class ListHabitsBehaviorTest : BaseUnitTest() {
@Test
fun testOnEdit() {
behavior.onEdit(ScreenLocation(0.0, 0.0), habit2, getToday())
verify(screen).showNumberPicker(
verify(screen).showNumberPopup(
eq(0.1),
eq("miles"),
eq(""),
eq("Jan 25, 2015"),
eq(Frequency.DAILY),
any(),
picker.capture()
)
picker.lastValue.onNumberPicked(100.0, "")

Loading…
Cancel
Save