Skip to content

Commit d977e7f

Browse files
authored
fix: fast follow bug fixes and refactors from upcoming time pill (#253)
* fix: fast follow bug fixes and refactors from upcoming time pill * fix: address latest bug bot * fix: latest bug bot
1 parent 8b9e1af commit d977e7f

7 files changed

Lines changed: 119 additions & 61 deletions

File tree

android/app/src/main/java/com/github/quarck/calnotify/prefs/PreferenceUtils.kt

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@
1919

2020
package com.github.quarck.calnotify.prefs
2121

22+
import android.content.Context
2223
import com.github.quarck.calnotify.Consts
24+
import com.github.quarck.calnotify.R
2325

2426
object PreferenceUtils {
2527

@@ -115,10 +117,55 @@ object PreferenceUtils {
115117
fun formatPattern(pattern: LongArray): String =
116118
pattern.map { p -> formatSnoozePreset(p) }.joinToString(", ")
117119

120+
data class NormalizeResult(val value: String, val droppedCount: Int)
121+
118122
/**
119-
* Format a millisecond duration as a human-readable label (e.g., "8 hours", "3 days", "1 week").
120-
* Used for display in bottom sheets and chips.
123+
* Parse and normalize a comma-separated preset string.
124+
* Returns null if parsing failed entirely.
125+
* [filter] removes individual millis values that don't pass (e.g., negative, over max).
126+
* [droppedCount] in the result indicates how many values were removed by the filter.
121127
*/
128+
fun normalizePresetInput(value: String, defaultValue: String, filter: ((Long) -> Boolean)? = null): NormalizeResult? {
129+
val presets = parseSnoozePresets(value) ?: return null
130+
val tokens = value.split(',').map { it.trim() }.filter { it.isNotEmpty() }
131+
val totalCount = tokens.size
132+
val kept = if (filter != null) {
133+
tokens.zip(presets.toList()).filter { (_, millis) -> filter(millis) }.map { it.first }
134+
} else {
135+
tokens
136+
}
137+
if (kept.isEmpty()) return NormalizeResult(defaultValue, totalCount)
138+
return NormalizeResult(kept.joinToString(", "), totalCount - kept.size)
139+
}
140+
141+
/**
142+
* Format a millisecond duration as a localizable human-readable label using Android plural resources.
143+
* Prefer this over the non-Context overload for user-facing text.
144+
*/
145+
fun formatPresetHumanReadable(context: Context, millis: Long): String {
146+
val seconds = millis / 1000L
147+
val res = context.resources
148+
149+
if (seconds % Consts.WEEK_IN_SECONDS == 0L) {
150+
val n = (seconds / Consts.WEEK_IN_SECONDS).toInt()
151+
return res.getQuantityString(R.plurals.duration_weeks, n, n)
152+
}
153+
if (seconds % Consts.DAY_IN_SECONDS == 0L) {
154+
val n = (seconds / Consts.DAY_IN_SECONDS).toInt()
155+
return res.getQuantityString(R.plurals.duration_days, n, n)
156+
}
157+
if (seconds % Consts.HOUR_IN_SECONDS == 0L) {
158+
val n = (seconds / Consts.HOUR_IN_SECONDS).toInt()
159+
return res.getQuantityString(R.plurals.duration_hours, n, n)
160+
}
161+
if (seconds % Consts.MINUTE_IN_SECONDS == 0L) {
162+
val n = (seconds / Consts.MINUTE_IN_SECONDS).toInt()
163+
return res.getQuantityString(R.plurals.duration_minutes, n, n)
164+
}
165+
return res.getQuantityString(R.plurals.duration_seconds, seconds.toInt(), seconds.toInt())
166+
}
167+
168+
/** Non-Context fallback — English-only. Prefer the Context overload for user-facing text. */
122169
fun formatPresetHumanReadable(millis: Long): String {
123170
val seconds = millis / 1000L
124171

android/app/src/main/java/com/github/quarck/calnotify/prefs/SnoozePresetPreferenceX.kt

Lines changed: 15 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -74,33 +74,22 @@ class SnoozePresetPreferenceX @JvmOverloads constructor(
7474
}
7575

7676
override fun onDialogClosed(positiveResult: Boolean) {
77-
if (positiveResult) {
78-
val value = edit?.text?.toString()
79-
80-
if (value != null) {
81-
val presets = PreferenceUtils.parseSnoozePresets(value)
82-
if (presets != null) {
83-
val newValue = if (presets.isEmpty()) {
84-
Settings.DEFAULT_SNOOZE_PRESET
85-
} else {
86-
value.split(',')
87-
.map { it.trim() }
88-
.filter { it.isNotEmpty() }
89-
.joinToString(", ")
90-
}
91-
92-
val pref = preference as SnoozePresetPreferenceX
93-
if (pref.callChangeListener(newValue)) {
94-
pref.persistPreset(newValue)
95-
}
96-
97-
if (presets.size > Consts.MAX_SUPPORTED_PRESETS) {
98-
showMessage(R.string.error_too_many_presets)
99-
}
100-
} else {
101-
showMessage(R.string.error_cannot_parse_preset)
102-
}
77+
if (!positiveResult) return
78+
val value = edit?.text?.toString() ?: return
79+
80+
val result = PreferenceUtils.normalizePresetInput(value, Settings.DEFAULT_SNOOZE_PRESET)
81+
if (result != null) {
82+
val pref = preference as SnoozePresetPreferenceX
83+
if (pref.callChangeListener(result.value)) {
84+
pref.persistPreset(result.value)
10385
}
86+
87+
val parsed = PreferenceUtils.parseSnoozePresets(result.value)
88+
if (parsed != null && parsed.size > Consts.MAX_SUPPORTED_PRESETS) {
89+
showMessage(R.string.error_too_many_presets)
90+
}
91+
} else {
92+
showMessage(R.string.error_cannot_parse_preset)
10493
}
10594
}
10695

android/app/src/main/java/com/github/quarck/calnotify/prefs/UpcomingTimePresetPreferenceX.kt

Lines changed: 22 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -77,35 +77,29 @@ class UpcomingTimePresetPreferenceX @JvmOverloads constructor(
7777
}
7878

7979
override fun onDialogClosed(positiveResult: Boolean) {
80-
if (positiveResult) {
81-
val value = edit?.text?.toString()
82-
83-
if (value != null) {
84-
val presets = PreferenceUtils.parseSnoozePresets(value)
85-
if (presets != null) {
86-
// Filter out negative values
87-
val validPresets = presets.filter { it > 0 }
88-
val newValue = if (validPresets.isEmpty()) {
89-
Settings.DEFAULT_UPCOMING_TIME_PRESETS
90-
} else {
91-
value.split(',')
92-
.map { it.trim() }
93-
.filter { it.isNotEmpty() }
94-
.joinToString(", ")
95-
}
96-
97-
val pref = preference as UpcomingTimePresetPreferenceX
98-
if (pref.callChangeListener(newValue)) {
99-
pref.persistPreset(newValue)
100-
}
101-
102-
if (validPresets.size > Settings.MAX_UPCOMING_TIME_PRESETS) {
103-
showFormattedMessage(R.string.error_too_many_upcoming_presets, Settings.MAX_UPCOMING_TIME_PRESETS)
104-
}
105-
} else {
106-
showMessage(R.string.error_cannot_parse_preset)
107-
}
80+
if (!positiveResult) return
81+
val value = edit?.text?.toString() ?: return
82+
83+
val result = PreferenceUtils.normalizePresetInput(
84+
value, Settings.DEFAULT_UPCOMING_TIME_PRESETS
85+
) { it > 0 && it <= Settings.MAX_LOOKAHEAD_MILLIS }
86+
87+
if (result != null) {
88+
val pref = preference as UpcomingTimePresetPreferenceX
89+
if (pref.callChangeListener(result.value)) {
90+
pref.persistPreset(result.value)
91+
}
92+
93+
if (result.droppedCount > 0) {
94+
showMessage(R.string.warning_presets_invalid_removed)
95+
}
96+
97+
val parsed = PreferenceUtils.parseSnoozePresets(result.value)
98+
if (parsed != null && parsed.size > Settings.MAX_UPCOMING_TIME_PRESETS) {
99+
showFormattedMessage(R.string.error_too_many_upcoming_presets, Settings.MAX_UPCOMING_TIME_PRESETS)
108100
}
101+
} else {
102+
showMessage(R.string.error_cannot_parse_preset)
109103
}
110104
}
111105

android/app/src/main/java/com/github/quarck/calnotify/ui/MainActivityModern.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ class MainActivityModern : MainActivityBase() {
614614
return if (settings.upcomingEventsMode == UpcomingEventsLookahead.MODE_DAY_BOUNDARY) {
615615
getString(R.string.upcoming_events_mode_day_boundary)
616616
} else {
617-
PreferenceUtils.formatPresetHumanReadable(settings.upcomingEventsFixedLookaheadMillis)
617+
PreferenceUtils.formatPresetHumanReadable(this, settings.upcomingEventsFixedLookaheadMillis)
618618
}
619619
}
620620

android/app/src/main/java/com/github/quarck/calnotify/ui/UpcomingTimeFilterBottomSheet.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,15 @@ class UpcomingTimeFilterBottomSheet : BottomSheetDialogFragment() {
6868
val currentMode = settings.upcomingEventsMode
6969
val currentMillis = settings.upcomingEventsFixedLookaheadMillis
7070
val presets = settings.upcomingTimePresets
71+
val radioPaddingV = resources.getDimensionPixelSize(R.dimen.bottom_sheet_radio_padding_vertical)
72+
val dividerHeight = resources.getDimensionPixelSize(R.dimen.bottom_sheet_divider_height)
7173

7274
// Day boundary option
7375
val dayBoundaryRadio = RadioButton(requireContext()).apply {
7476
id = DAY_BOUNDARY_RADIO_ID
7577
val hourStr = DateTimeUtils.formatHourOfDay(settings.upcomingEventsDayBoundaryHour)
7678
text = getString(R.string.upcoming_time_day_boundary, hourStr)
77-
setPadding(0, 24, 0, 24)
79+
setPadding(0, radioPaddingV, 0, radioPaddingV)
7880
}
7981
radioGroup.addView(dayBoundaryRadio)
8082

@@ -85,7 +87,7 @@ class UpcomingTimeFilterBottomSheet : BottomSheetDialogFragment() {
8587
ta.recycle()
8688
val divider = View(requireContext()).apply {
8789
layoutParams = ViewGroup.LayoutParams(
88-
ViewGroup.LayoutParams.MATCH_PARENT, 2
90+
ViewGroup.LayoutParams.MATCH_PARENT, dividerHeight
8991
)
9092
background = dividerDrawable
9193
}
@@ -98,8 +100,8 @@ class UpcomingTimeFilterBottomSheet : BottomSheetDialogFragment() {
98100
presetRadioIds.add(radioId to presetMillis)
99101
val radio = RadioButton(requireContext()).apply {
100102
id = radioId
101-
text = PreferenceUtils.formatPresetHumanReadable(presetMillis)
102-
setPadding(0, 24, 0, 24)
103+
text = PreferenceUtils.formatPresetHumanReadable(requireContext(), presetMillis)
104+
setPadding(0, radioPaddingV, 0, radioPaddingV)
103105
}
104106
radioGroup.addView(radio)
105107
}

android/app/src/main/res/values/dimens.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109

110110

111111
<dimen name="add_event_view_title_height">60dp</dimen>
112+
<dimen name="bottom_sheet_radio_padding_vertical">12dp</dimen>
113+
<dimen name="bottom_sheet_divider_height">1dp</dimen>
114+
112115
<dimen name="dimen_main_settings_padding_bottom">15dp</dimen>
113116
<dimen name="dimen_main_settings_padding_end">20dp</dimen>
114117
<dimen name="dimen_main_settings_padding_start">20dp</dimen>

android/app/src/main/res/values/strings.xml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,7 @@
817817
<string name="upcoming_time_day_boundary">Day boundary (%s)</string>
818818
<string name="upcoming_time_presets_title">Lookahead interval presets</string>
819819
<string name="error_too_many_upcoming_presets">Only up to %d presets are supported, extra presets will be ignored</string>
820+
<string name="warning_presets_invalid_removed">Invalid values were removed</string>
820821
<string name="upcoming_time_presets_summary">Comma-separated intervals (e.g., 4h, 8h, 1d, 3d, 1w)</string>
821822
<string name="dialog_upcoming_time_presets_label">Comma-separated list of lookahead intervals\n\nSupported values like \'4h\', \'1d\' or \'1w\'\n\nMaximum %1$d days (calendar scan limit)\n\nUp to %2$d items in the list\n\nLeave empty to use defaults</string>
822823

@@ -849,6 +850,28 @@
849850
<item>50</item>
850851
</string-array>
851852

853+
<!-- Time duration plurals (for human-readable preset labels) -->
854+
<plurals name="duration_weeks">
855+
<item quantity="one">%d week</item>
856+
<item quantity="other">%d weeks</item>
857+
</plurals>
858+
<plurals name="duration_days">
859+
<item quantity="one">%d day</item>
860+
<item quantity="other">%d days</item>
861+
</plurals>
862+
<plurals name="duration_hours">
863+
<item quantity="one">%d hour</item>
864+
<item quantity="other">%d hours</item>
865+
</plurals>
866+
<plurals name="duration_minutes">
867+
<item quantity="one">%d minute</item>
868+
<item quantity="other">%d minutes</item>
869+
</plurals>
870+
<plurals name="duration_seconds">
871+
<item quantity="one">%d second</item>
872+
<item quantity="other">%d seconds</item>
873+
</plurals>
874+
852875
<!-- Multi-select mode -->
853876
<string name="cancel_selection">Cancel selection</string>
854877
<string name="select_all">Select all</string>

0 commit comments

Comments
 (0)