Merge branch 'master' into dev

pull/1715/head
Alinson S. Xavier 2 years ago
commit 6a55d3c01a
Signed by: isoron
GPG Key ID: 0DA8E4B9E1109DCA

@ -1,5 +1,20 @@
# Changelog
## [2.1.2] -- 2023-05-26
### Fixed
- Fix bug that caused widget to enter checkmark on wrong date (@iSoron, #1541)
- Fix widget corners on Android 12 (@iSoron)
- Fix bug that caused notes to be lost when editing a checkmark (@iSoron, #1566)
- Prevent soft keyboard from covering entry popup (@iSoron)
- Accept comma (instead of dot) in certain locales (@iSoron)
### Changed
- Remove update delay after entering a checkmark (@iSoron)
### Removed
- Remove stack widgets (@iSoron)
## [2.1.1] -- 2022-09-24
### Fixed
- Fix Tasker plugin (@iSoron, #1503)

@ -217,20 +217,28 @@ android_test_parallel() {
(
LOG=build/android-test-$API.log
log_info "API $API: Running tests..."
if android_test $API 1>$LOG 2>&1; then
android_test $API 1>$LOG 2>&1
ret_code=$?
if [ $ret_code = 0 ]; then
log_info "API $API: Passed"
else
log_error "API $API: Failed"
fi
pkill -9 -f ${AVD_PREFIX}${API}
exit $ret_code
)&
PIDS+=" $!"
done
# Check exit codes
RET_CODE=0
success=0
for pid in $PIDS; do
wait $pid || RET_CODE=1
wait $pid
ret_code=$?
if [ $ret_code != 0 ]; then
success=1
fi
echo pid=$pid ret_code=$ret_code success=$success
done
# Print all logs
@ -240,7 +248,7 @@ android_test_parallel() {
echo "::endgroup::"
done
return $RET_CODE
return $success
}
android_build() {

@ -35,9 +35,9 @@ android {
compileSdk = 32
defaultConfig {
versionCode = 20101
versionName = "2.1.1"
minSdk = 28
versionCode = 20200
versionName = "2.2.0"
minSdk = 23
targetSdk = 31
applicationId = "org.isoron.uhabits"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 16 KiB

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

@ -77,7 +77,7 @@ class EntryPanelViewTest : BaseViewTest() {
@Test
fun testToggle() {
val timestamps = mutableListOf<Timestamp>()
view.onToggle = { t, _, _, _ -> timestamps.add(t) }
view.onToggle = { t, _, _ -> timestamps.add(t) }
view.buttons[0].performLongClick()
view.buttons[2].performLongClick()
view.buttons[3].performLongClick()
@ -88,7 +88,7 @@ class EntryPanelViewTest : BaseViewTest() {
fun testToggle_withOffset() {
val timestamps = mutableListOf<Timestamp>()
view.dataOffset = 3
view.onToggle = { t, _, _, _ -> timestamps += t }
view.onToggle = { t, _, _ -> timestamps += t }
view.buttons[0].performLongClick()
view.buttons[2].performLongClick()
view.buttons[3].performLongClick()

@ -36,6 +36,7 @@ class CheckmarkWidgetViewTest : BaseViewTest() {
@Before
override fun setUp() {
super.setUp()
similarityCutoff = 0.00025
setTheme(R.style.WidgetTheme)
val habit = fixtures.createShortHabit()
val computedEntries = habit.computedEntries

@ -20,109 +20,60 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.appcompat.app.AppCompatDialogFragment
import org.isoron.uhabits.HabitsApplication
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.preferences.Preferences
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils.getFontAwesome
import org.isoron.uhabits.utils.dimBehind
import org.isoron.uhabits.utils.dismissCurrentAndShow
import org.isoron.uhabits.utils.dp
import org.isoron.uhabits.utils.sres
const val POPUP_WIDTH = 4 * 48f + 16f
const val POPUP_HEIGHT = 48f * 2.5f + 8f
class CheckmarkPopup(
private val context: Context,
private val color: Int,
private var notes: String,
private var value: Int,
private val prefs: Preferences,
private val anchor: View,
) {
class CheckmarkDialog : AppCompatDialogFragment() {
var onToggle: (Int, String) -> Unit = { _, _ -> }
private lateinit var dialog: Dialog
private val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context)).apply {
// Required for round corners
container.clipToOutline = true
}
init {
view.booleanButtons.visibility = VISIBLE
initColors()
initTypefaces()
hideDisabledButtons()
populate()
}
private fun initColors() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences
val view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
arrayOf(view.yesBtn, view.skipBtn).forEach {
it.setTextColor(color)
it.setTextColor(requireArguments().getInt("color"))
}
arrayOf(view.noBtn, view.unknownBtn).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
}
}
private fun initTypefaces() {
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
it.typeface = getFontAwesome(context)
it.typeface = getFontAwesome(requireContext())
}
}
private fun hideDisabledButtons() {
view.notes.setText(requireArguments().getString("notes")!!)
if (!prefs.isSkipEnabled) view.skipBtn.visibility = GONE
if (!prefs.areQuestionMarksEnabled) view.unknownBtn.visibility = GONE
}
private fun populate() {
val selectedBtn = when (value) {
YES_MANUAL -> view.yesBtn
YES_AUTO -> view.noBtn
NO -> view.noBtn
UNKNOWN -> if (prefs.areQuestionMarksEnabled) view.unknownBtn else view.noBtn
SKIP -> if (prefs.isSkipEnabled) view.skipBtn else view.noBtn
else -> null
}
view.notes.setText(notes)
}
fun show() {
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
view.booleanButtons.visibility = VISIBLE
val dialog = Dialog(requireContext())
dialog.setContentView(view.root)
dialog.window?.apply {
setLayout(
view.root.dp(POPUP_WIDTH).toInt(),
view.root.dp(POPUP_HEIGHT).toInt()
)
setBackgroundDrawableResource(android.R.color.transparent)
}
fun onClick(v: Int) {
this.value = v
save()
val notes = view.notes.text.toString().trim()
onToggle(v, notes)
requireDialog().dismiss()
}
view.yesBtn.setOnClickListener { onClick(YES_MANUAL) }
view.noBtn.setOnClickListener { onClick(NO) }
view.skipBtn.setOnClickListener { onClick(SKIP) }
view.unknownBtn.setOnClickListener { onClick(UNKNOWN) }
dialog.setCanceledOnTouchOutside(true)
dialog.dimBehind()
dialog.dismissCurrentAndShow()
view.notes.setOnEditorActionListener { v, actionId, event ->
onClick(requireArguments().getInt("value"))
true
}
fun save() {
onToggle(value, view.notes.text.toString().trim())
dialog.dismiss()
return dialog
}
}

@ -0,0 +1,104 @@
package org.isoron.uhabits.activities.common.dialogs
import android.app.Dialog
import android.os.Bundle
import android.text.method.DigitsKeyListener
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.app.AppCompatDialogFragment
import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Entry
import org.isoron.uhabits.databinding.CheckmarkPopupBinding
import org.isoron.uhabits.utils.InterfaceUtils
import org.isoron.uhabits.utils.requestFocusWithKeyboard
import org.isoron.uhabits.utils.sres
import java.text.DecimalFormat
import java.text.DecimalFormatSymbols
import java.text.NumberFormat
import java.text.ParseException
class NumberDialog : AppCompatDialogFragment() {
var onToggle: (Double, String) -> Unit = { _, _ -> }
var onDismiss: () -> Unit = {}
private var originalNotes: String = ""
private var originalValue: Double = 0.0
private lateinit var view: CheckmarkPopupBinding
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val appComponent = (requireActivity().application as HabitsApplication).component
val prefs = appComponent.preferences
view = CheckmarkPopupBinding.inflate(LayoutInflater.from(context))
arrayOf(view.yesBtn, view.skipBtn).forEach {
it.setTextColor(requireArguments().getInt("color"))
}
arrayOf(view.noBtn, view.unknownBtn).forEach {
it.setTextColor(view.root.sres.getColor(R.attr.contrast60))
}
arrayOf(view.yesBtn, view.noBtn, view.skipBtn, view.unknownBtn).forEach {
it.typeface = InterfaceUtils.getFontAwesome(requireContext())
}
if (!prefs.isSkipEnabled) view.skipBtnNumber.visibility = View.GONE
view.numberButtons.visibility = View.VISIBLE
fixDecimalSeparator(view)
originalNotes = requireArguments().getString("notes")!!
originalValue = requireArguments().getDouble("value")
view.notes.setText(originalNotes)
view.value.setText(
when {
originalValue < 0.01 -> "0"
else -> DecimalFormat("#.##").format(originalValue)
}
)
view.value.setOnKeyListener { _, keyCode, event ->
if (event.action == MotionEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) {
save()
return@setOnKeyListener true
}
return@setOnKeyListener false
}
view.saveBtn.setOnClickListener {
save()
}
view.skipBtnNumber.setOnClickListener {
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
save()
}
view.notes.setOnEditorActionListener { v, actionId, event ->
save()
true
}
view.value.requestFocusWithKeyboard()
val dialog = Dialog(requireContext())
dialog.setContentView(view.root)
dialog.window?.apply {
setBackgroundDrawableResource(android.R.color.transparent)
}
dialog.setOnDismissListener { onDismiss() }
return dialog
}
private fun fixDecimalSeparator(view: CheckmarkPopupBinding) {
// https://stackoverflow.com/a/34256139
val separator = DecimalFormatSymbols.getInstance().decimalSeparator
view.value.keyListener = DigitsKeyListener.getInstance("0123456789$separator")
}
fun save() {
var value = originalValue
try {
val numberFormat = NumberFormat.getInstance()
val valueStr = view.value.text.toString()
value = numberFormat.parse(valueStr)!!.toDouble()
} catch (e: ParseException) {
// NOP
}
val notes = view.notes.text.toString()
onToggle(value, notes)
requireDialog().dismiss()
}
}

@ -1,116 +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.app.Dialog
import android.content.Context
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 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.dismissCurrentAndShow
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 = { _, _ -> }
var onDismiss: () -> Unit = {}
private val originalValue = value
private lateinit var dialog: Dialog
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() {
dialog = Dialog(context, android.R.style.Theme_NoTitleBar)
dialog.setContentView(view.root)
dialog.window?.apply {
setLayout(
view.root.dp(POPUP_WIDTH).toInt(),
view.root.dp(POPUP_HEIGHT).toInt()
)
setBackgroundDrawableResource(android.R.color.transparent)
}
dialog.setOnDismissListener {
onDismiss()
}
view.value.setOnKeyListener { _, keyCode, event ->
if (event.action == ACTION_DOWN && keyCode == KEYCODE_ENTER) {
save()
return@setOnKeyListener true
}
return@setOnKeyListener false
}
view.saveBtn.setOnClickListener {
save()
}
view.skipBtnNumber.setOnClickListener {
view.value.setText((Entry.SKIP.toDouble() / 1000).toString())
save()
}
view.value.requestFocusWithKeyboard()
dialog.setCanceledOnTouchOutside(true)
dialog.dimBehind()
dialog.dismissCurrentAndShow()
}
fun save() {
val value = view.value.text.toString().toDoubleOrNull() ?: originalValue
val notes = view.notes.text.toString()
onToggle(value, notes)
dialog.dismiss()
}
}

@ -40,6 +40,7 @@ import org.isoron.uhabits.inject.ActivityContextModule
import org.isoron.uhabits.inject.DaggerHabitsActivityComponent
import org.isoron.uhabits.inject.HabitsActivityComponent
import org.isoron.uhabits.inject.HabitsApplicationComponent
import org.isoron.uhabits.utils.dismissCurrentDialog
import org.isoron.uhabits.utils.restartWithFade
class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
@ -91,6 +92,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
midnightTimer.onPause()
screen.onDetached()
adapter.cancelRefresh()
dismissCurrentDialog()
super.onPause()
}
@ -99,6 +101,7 @@ class ListHabitsActivity : AppCompatActivity(), Preferences.Listener {
screen.onAttached()
rootView.postInvalidate()
midnightTimer.onResume()
appComponent.reminderScheduler.scheduleAll()
taskRunner.run {
try {
AutoBackup(this@ListHabitsActivity).run()

@ -22,14 +22,15 @@ package org.isoron.uhabits.activities.habits.list
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.Lazy
import org.isoron.platform.gui.toInt
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
import org.isoron.uhabits.activities.common.dialogs.ColorPickerDialogFactory
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
import org.isoron.uhabits.activities.habits.edit.HabitTypeDialog
import org.isoron.uhabits.activities.habits.list.views.HabitCardListAdapter
import org.isoron.uhabits.core.commands.ArchiveHabitsCommand
@ -233,17 +234,14 @@ class ListHabitsScreen
notes: String,
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()
val fm = (context as AppCompatActivity).supportFragmentManager
val dialog = NumberDialog()
dialog.arguments = Bundle().apply {
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.dismissCurrentAndShow(fm, "numberDialog")
}
override fun showCheckmarkPopup(
@ -252,18 +250,16 @@ class ListHabitsScreen
color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback
) {
val view = rootView.get()
CheckmarkPopup(
context = context,
prefs = preferences,
anchor = view,
color = view.currentTheme().color(color).toInt(),
notes = notes,
value = selectedValue,
).apply {
onToggle = { value, notes -> callback.onNotesSaved(value, notes) }
show()
}
val theme = rootView.get().currentTheme()
val fm = (context as AppCompatActivity).supportFragmentManager
val dialog = CheckmarkDialog()
dialog.arguments = Bundle().apply {
putInt("color", theme.color(color).toInt())
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.dismissCurrentAndShow(fm, "checkmarkDialog")
}
private fun getExecuteString(command: Command): String? {

@ -44,8 +44,6 @@ import org.isoron.uhabits.utils.sres
import org.isoron.uhabits.utils.toMeasureSpec
import javax.inject.Inject
const val TOGGLE_DELAY_MILLIS = 2000L
class CheckmarkButtonViewFactory
@Inject constructor(
@ActivityContext val context: Context,
@ -79,7 +77,7 @@ class CheckmarkButtonView(
invalidate()
}
var onToggle: (Int, String, Long) -> Unit = { _, _, _ -> }
var onToggle: (Int, String) -> Unit = { _, _ -> }
var onEdit: () -> Unit = { }
@ -90,25 +88,25 @@ class CheckmarkButtonView(
setOnLongClickListener(this)
}
fun performToggle(delay: Long) {
fun performToggle() {
value = Entry.nextToggleValue(
value = value,
isSkipEnabled = preferences.isSkipEnabled,
areQuestionMarksEnabled = preferences.areQuestionMarksEnabled
)
onToggle(value, notes, delay)
onToggle(value, notes)
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
invalidate()
}
override fun onClick(v: View) {
if (preferences.isShortToggleEnabled) performToggle(TOGGLE_DELAY_MILLIS)
if (preferences.isShortToggleEnabled) performToggle()
else onEdit()
}
override fun onLongClick(v: View): Boolean {
if (preferences.isShortToggleEnabled) onEdit()
else performToggle(TOGGLE_DELAY_MILLIS)
else performToggle()
return true
}

@ -60,7 +60,7 @@ class CheckmarkPanelView(
setupButtons()
}
var onToggle: (Timestamp, Int, String, Long) -> Unit = { _, _, _, _ -> }
var onToggle: (Timestamp, Int, String) -> Unit = { _, _, _ -> }
set(value) {
field = value
setupButtons()
@ -89,7 +89,7 @@ class CheckmarkPanelView(
else -> ""
}
button.color = color
button.onToggle = { value, notes, delay -> onToggle(timestamp, value, notes, delay) }
button.onToggle = { value, notes -> onToggle(timestamp, value, notes) }
button.onEdit = { onEdit(timestamp) }
}
}

@ -57,13 +57,6 @@ class HabitCardViewFactory
fun create() = HabitCardView(context, checkmarkPanelFactory, numberPanelFactory, behavior)
}
data class DelayedToggle(
var habit: Habit,
var timestamp: Timestamp,
var value: Int,
var notes: String
)
class HabitCardView(
@ActivityContext context: Context,
checkmarkPanelFactory: CheckmarkPanelViewFactory,
@ -136,7 +129,6 @@ class HabitCardView(
private var scoreRing: RingView
private var currentToggleTaskId = 0
private var queuedToggles = mutableListOf<DelayedToggle>()
init {
scoreRing = RingView(context).apply {
@ -160,12 +152,9 @@ class HabitCardView(
}
checkmarkPanel = checkmarkPanelFactory.create().apply {
onToggle = { timestamp, value, notes, delay ->
if (delay > 0) triggerRipple(timestamp)
habit?.let {
val taskId = queueToggle(it, timestamp, value, notes);
{ runPendingToggles(taskId) }.delay(delay)
}
onToggle = { timestamp, value, notes ->
triggerRipple(timestamp)
habit?.let { behavior.onToggle(it, timestamp, value, notes) }
}
onEdit = { timestamp ->
triggerRipple(timestamp)
@ -205,25 +194,6 @@ class HabitCardView(
addView(innerFrame)
}
@Synchronized
private fun runPendingToggles(id: Int) {
if (currentToggleTaskId != id) return
for ((h, t, v, n) in queuedToggles) behavior.onToggle(h, t, v, n)
queuedToggles.clear()
}
@Synchronized
private fun queueToggle(
it: Habit,
timestamp: Timestamp,
value: Int,
notes: String,
): Int {
currentToggleTaskId += 1
queuedToggles.add(DelayedToggle(it, timestamp, value, notes))
return currentToggleTaskId
}
override fun onModelChange() {
Handler(Looper.getMainLooper()).post {
habit?.let { copyAttributesFrom(it) }

@ -34,10 +34,10 @@ import org.isoron.uhabits.HabitsApplication
import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.activities.HabitsDirFinder
import org.isoron.uhabits.activities.common.dialogs.CheckmarkPopup
import org.isoron.uhabits.activities.common.dialogs.CheckmarkDialog
import org.isoron.uhabits.activities.common.dialogs.ConfirmDeleteDialog
import org.isoron.uhabits.activities.common.dialogs.HistoryEditorDialog
import org.isoron.uhabits.activities.common.dialogs.NumberPopup
import org.isoron.uhabits.activities.common.dialogs.NumberDialog
import org.isoron.uhabits.core.commands.Command
import org.isoron.uhabits.core.commands.CommandRunner
import org.isoron.uhabits.core.models.Habit
@ -51,6 +51,7 @@ import org.isoron.uhabits.core.ui.views.OnDateClickedListener
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.utils.currentTheme
import org.isoron.uhabits.utils.dismissCurrentAndShow
import org.isoron.uhabits.utils.dismissCurrentDialog
import org.isoron.uhabits.utils.showMessage
import org.isoron.uhabits.utils.showSendFileScreen
import org.isoron.uhabits.widgets.WidgetUpdater
@ -129,6 +130,7 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
}
override fun onPause() {
dismissCurrentDialog()
commandRunner.removeListener(this)
super.onPause()
}
@ -170,41 +172,32 @@ class ShowHabitActivity : AppCompatActivity(), CommandRunner.Listener {
override fun showNumberPopup(
value: Double,
notes: String,
preferences: Preferences,
callback: ListHabitsBehavior.NumberPickerCallback
) {
val anchor = getPopupAnchor() ?: return
NumberPopup(
context = this@ShowHabitActivity,
prefs = preferences,
notes = notes,
anchor = anchor,
value = value,
).apply {
onToggle = { v, n -> callback.onNumberPicked(v, n) }
show()
val dialog = NumberDialog()
dialog.arguments = Bundle().apply {
putDouble("value", value)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNumberPicked(v, n) }
dialog.dismissCurrentAndShow(supportFragmentManager, "numberDialog")
}
override fun showCheckmarkPopup(
selectedValue: Int,
notes: String,
preferences: Preferences,
color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback
) {
val anchor = getPopupAnchor() ?: return
CheckmarkPopup(
context = this@ShowHabitActivity,
prefs = preferences,
notes = notes,
color = view.currentTheme().color(color).toInt(),
anchor = anchor,
value = selectedValue,
).apply {
onToggle = { v, n -> callback.onNotesSaved(v, n) }
show()
val theme = view.currentTheme()
val dialog = CheckmarkDialog()
dialog.arguments = Bundle().apply {
putInt("color", theme.color(color).toInt())
putInt("value", selectedValue)
putString("notes", notes)
}
dialog.onToggle = { v, n -> callback.onNotesSaved(v, n) }
dialog.dismissCurrentAndShow(supportFragmentManager, "checkmarkDialog")
}
private fun getPopupAnchor(): View? {

@ -6,16 +6,24 @@ import androidx.fragment.app.FragmentManager
import java.lang.ref.WeakReference
var currentDialog: WeakReference<Dialog> = WeakReference(null)
var currentDialogFragment: WeakReference<DialogFragment> = WeakReference(null)
fun Dialog.dismissCurrentAndShow() {
fun dismissCurrentDialog() {
currentDialog.get()?.dismiss()
currentDialog = WeakReference(null)
currentDialogFragment.get()?.dismiss()
currentDialogFragment = WeakReference(null)
}
fun Dialog.dismissCurrentAndShow() {
dismissCurrentDialog()
currentDialog = WeakReference(this)
show()
}
fun DialogFragment.dismissCurrentAndShow(fragmentManager: FragmentManager, tag: String) {
currentDialog.get()?.dismiss()
dismissCurrentDialog()
currentDialogFragment = WeakReference(this)
show(fragmentManager, tag)
fragmentManager.executePendingTransactions()
currentDialog = WeakReference(this.dialog)
}

@ -41,7 +41,7 @@ open class CheckmarkWidget(
override fun getOnClickPendingIntent(context: Context): PendingIntent? {
return if (habit.isNumerical) {
pendingIntentFactory.showNumberPicker(habit, DateUtils.getToday())
pendingIntentFactory.showNumberPicker(habit, DateUtils.getTodayWithOffset())
} else {
pendingIntentFactory.toggleCheckmark(habit, null)
}

@ -33,7 +33,7 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.core.models.Habit
import org.isoron.uhabits.core.models.HabitNotFoundException
import org.isoron.uhabits.core.preferences.Preferences
import org.isoron.uhabits.core.utils.DateUtils.Companion.getToday
import org.isoron.uhabits.core.utils.DateUtils.Companion.getTodayWithOffset
import org.isoron.uhabits.intents.IntentFactory
import org.isoron.uhabits.intents.PendingIntentFactory
import org.isoron.uhabits.utils.InterfaceUtils.dpToPixels
@ -101,7 +101,7 @@ internal class StackRemoteViewsFactory(private val context: Context, intent: Int
val landscapeViews = widget.landscapeRemoteViews
val portraitViews = widget.portraitRemoteViews
val factory = PendingIntentFactory(context, IntentFactory())
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getToday())
val intent = StackWidgetType.getIntentFillIn(factory, widgetType, h, habits, getTodayWithOffset())
landscapeViews.setOnClickFillInIntent(R.id.button, intent)
portraitViews.setOnClickFillInIntent(R.id.button, intent)
val remoteViews = RemoteViews(landscapeViews, portraitViews)

@ -24,7 +24,6 @@ import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
import android.content.Intent
import android.os.Bundle
import android.widget.AbsListView.CHOICE_MODE_MULTIPLE
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ListView
@ -34,7 +33,6 @@ import org.isoron.uhabits.R
import org.isoron.uhabits.activities.AndroidThemeSwitcher
import org.isoron.uhabits.core.preferences.WidgetPreferences
import org.isoron.uhabits.widgets.WidgetUpdater
import java.util.ArrayList
class BooleanHabitPickerDialog : HabitPickerDialog() {
override fun shouldHideNumerical() = true
@ -88,20 +86,12 @@ open class HabitPickerDialog : Activity() {
with(listView) {
adapter = ArrayAdapter(
context,
android.R.layout.simple_list_item_multiple_choice,
android.R.layout.simple_list_item_1,
habitNames
)
choiceMode = CHOICE_MODE_MULTIPLE
itemsCanFocus = false
setOnItemClickListener { parent, view, position, id ->
confirm(mutableListOf(habitIds[position]))
}
saveButton.setOnClickListener {
val selectedIds = mutableListOf<Long>()
for (i in 0..listView.count) {
if (listView.isItemChecked(i)) {
selectedIds.add(habitIds[i])
}
}
confirm(selectedIds)
}
}

@ -66,23 +66,21 @@ class CheckmarkWidgetView : HabitWidgetView {
val res = StyledResources(context)
val bgColor: Int
val fgColor: Int
setShadowAlpha(0x4f)
when (entryState) {
YES_MANUAL, SKIP -> {
bgColor = activeColor
fgColor = res.getColor(R.attr.contrast0)
setShadowAlpha(0x4f)
backgroundPaint!!.color = bgColor
frame!!.setBackgroundDrawable(background)
}
YES_AUTO, NO, UNKNOWN -> {
bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
}
else -> {
bgColor = res.getColor(R.attr.cardBgColor)
fgColor = res.getColor(R.attr.contrast60)
setShadowAlpha(0x00)
}
}
ring.setPercentage(percentage)
@ -126,7 +124,7 @@ class CheckmarkWidgetView : HabitWidgetView {
} else {
width = min(width, height)
}
val textSize = min(0.2f * width, getDimension(context, R.dimen.smallerTextSize))
val textSize = min(0.175f * width, getDimension(context, R.dimen.smallTextSize))
label.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
if (isNumerical) {
ring.setTextSize(textSize * 0.9f)
@ -141,7 +139,8 @@ class CheckmarkWidgetView : HabitWidgetView {
}
private fun init() {
val appComponent: HabitsApplicationComponent = (context.applicationContext as HabitsApplication).component
val appComponent: HabitsApplicationComponent =
(context.applicationContext as HabitsApplication).component
preferences = appComponent.preferences
ring = findViewById<View>(R.id.scoreRing) as RingView
label = findViewById<View>(R.id.label) as TextView

@ -69,7 +69,7 @@ abstract class HabitWidgetView : FrameLayout {
val shadowRadius = dpToPixels(context, 2f).toInt()
val shadowOffset = dpToPixels(context, 1f).toInt()
val shadowColor = Color.argb(shadowAlpha, 0, 0, 0)
val cornerRadius = dpToPixels(context, 5f)
val cornerRadius = dpToPixels(context, 12f)
val radii = FloatArray(8)
Arrays.fill(radii, cornerRadius)
val shape = RoundRectShape(radii, null, null)

@ -28,7 +28,7 @@
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<corners android:radius="5dp"/>
<corners android:radius="12dp"/>
<solid android:color="?android:colorPrimary"/>
</shape>
<color android:color="@color/white"/>

@ -21,8 +21,10 @@
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="128dp"
android:minWidth="208dp"
app:divider="@drawable/checkmark_dialog_divider"
app:showDividers="middle"
android:orientation="vertical"
@ -34,7 +36,7 @@
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:inputType="textCapSentences|textMultiLine"
android:inputType="textCapSentences"
android:textSize="@dimen/smallTextSize"
android:padding="4dp"
android:background="@color/transparent"

@ -30,10 +30,4 @@
android:layout_weight="1">
</ListView>
<Button
android:id="@+id/buttonSave"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/save"/>
</LinearLayout>

@ -35,7 +35,7 @@
android:paddingTop="4dp"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingBottom="4dp"
android:paddingBottom="8dp"
tools:ignore="UselessParent">
<TextView

@ -149,7 +149,7 @@ class HabitsCSVExporter(
val timeframe = getTimeframe()
val oldest = timeframe[0]
val newest = DateUtils.getToday()
val newest = DateUtils.getTodayWithOffset()
val checkmarks: MutableList<ArrayList<Entry>> = ArrayList()
val scores: MutableList<ArrayList<Score>> = ArrayList()
for (habit in selectedHabits) {

@ -204,9 +204,16 @@ open class EntryList {
// Copy original entries
original.forEach { entry ->
val offset = entry.timestamp.daysUntil(to)
if (result[offset].value == UNKNOWN || entry.value == SKIP || entry.value == YES_MANUAL) {
result[offset] = entry
val value = if (
result[offset].value == UNKNOWN ||
entry.value == SKIP ||
entry.value == YES_MANUAL
) {
entry.value
} else {
YES_AUTO
}
result[offset] = Entry(entry.timestamp, value, entry.notes)
}
return result

@ -57,7 +57,7 @@ class BarCardPresenter(
} else {
boolBucketSizes[boolSpinnerPosition]
}
val today = DateUtils.getToday()
val today = DateUtils.getTodayWithOffset()
val oldest = habit.computedEntries.getKnown().lastOrNull()?.timestamp ?: today
val entries = habit.computedEntries.getByInterval(oldest, today).groupedSum(
truncateField = ScoreCardPresenter.getTruncateField(bucketSize),

@ -91,7 +91,6 @@ class HistoryCardPresenter(
screen.showCheckmarkPopup(
entry.value,
entry.notes,
preferences,
habit.color,
) { newValue, newNotes ->
commandRunner.run(
@ -130,7 +129,6 @@ class HistoryCardPresenter(
screen.showNumberPopup(
value = oldValue / 1000.0,
notes = entry.notes,
preferences = preferences,
) { newValue: Double, newNotes: String ->
val thousands = (newValue * 1000).roundToInt()
commandRunner.run(
@ -203,13 +201,11 @@ class HistoryCardPresenter(
fun showNumberPopup(
value: Double,
notes: String,
preferences: Preferences,
callback: ListHabitsBehavior.NumberPickerCallback,
)
fun showCheckmarkPopup(
selectedValue: Int,
notes: String,
preferences: Preferences,
color: PaletteColor,
callback: ListHabitsBehavior.CheckMarkDialogCallback,
)

@ -217,7 +217,7 @@ class EntryListTest {
fun testAddFromInterval() {
val entries = listOf(
Entry(day(1), YES_MANUAL),
Entry(day(2), NO),
Entry(day(2), NO, "Test"),
Entry(day(4), NO),
Entry(day(5), YES_MANUAL),
Entry(day(10), YES_MANUAL),
@ -230,7 +230,7 @@ class EntryListTest {
)
val expected = listOf(
Entry(day(1), YES_MANUAL),
Entry(day(2), YES_AUTO),
Entry(day(2), YES_AUTO, "Test"),
Entry(day(3), UNKNOWN),
Entry(day(4), YES_AUTO),
Entry(day(5), YES_MANUAL),

Loading…
Cancel
Save