Compare commits

..
Author SHA1 Message Date
makiolaj 0d30c0e0a4 Review fixes for the reminder description setting (#156) 2026-11-12 16:03:16 +01:00
makiolaj d9dc358883 Add setting for the description shown in reminders (#156) 2026-11-12 15:55:20 +01:00
Jean-Luc Makiolaandmakiolaj 3e34c60577 Release 1.3.1 (#146)
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 18s
Renovate / renovate (push) Successful in 2m30s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Successful in 21m13s
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Successful in 1m28s
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Skipped
Bumps to 1.3.1 (1030199), dates the changelog and adds the What's New for all eight store locales.

- Ticking a repeating task moves the series on (#137, #144)
- Recurring tasks keep their rule with tasks.org and OpenTasks (#135, #142)
- Keep the list order with tasks.org and OpenTasks (#136, #143)

Closes #135, closes #136, closes #137

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/146
2026-11-09 20:02:41 +01:00
Jean-Luc Makiola caf4357cee Translations update from Weblate (#138)
Release — F-Droid repo + Gitea/Codeberg release + Play / detect (push) Successful in 10s
Release — F-Droid repo + Gitea/Codeberg release + Play / release (push) Skipped
Release — F-Droid repo + Gitea/Codeberg release + Play / play (push) Skipped
Renovate / renovate (push) Successful in 2m21s
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/138
2026-11-09 19:15:30 +01:00
Weblate 97164feb39 Translated using Weblate (Arabic)
Currently translated at 87.5% (613 of 700 strings)

Translation: Agendula/agendula
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/agendula/agendula/ar/
2026-11-08 01:42:41 +00:00
Sup99999 8a521d32f9 Translated using Weblate (Arabic)
Currently translated at 87.5% (613 of 700 strings)

Translation: Agendula/agendula
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/agendula/agendula/ar/
2026-11-08 01:42:41 +00:00
Савелий b68582177b Translated using Weblate (Russian)
Currently translated at 5.8% (41 of 700 strings)

Translation: Agendula/agendula
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/agendula/agendula/ru/
2026-11-07 15:27:54 +00:00
Sup99999 05f43d6f76 Translated using Weblate (Arabic)
Currently translated at 80.0% (560 of 700 strings)

Translation: Agendula/agendula
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/agendula/agendula/ar/
2026-11-05 04:12:37 +00:00
Knee8811 7498ffb37e Translated using Weblate (Czech)
Currently translated at 39.3% (266 of 676 strings)

Translation: Agendula/agendula
Translate-URL: https://weblate.dev.jeanlucmakiola.de/projects/agendula/agendula/cs/
2026-11-04 22:18:19 +00:00
10 changed files with 373 additions and 2 deletions

No files matched your search

+5
View File
@@ -7,6 +7,11 @@ All notable changes to this project are documented here. The format follows
## [Unreleased]
### Added
- A "Description in reminders" setting under Notifications sets how much of a
task's description a reminder shows: none, a short preview of up to three
lines (the new default), or all of it as before (#156).
## [1.3.1] - 2026-11-09
### Changed
@@ -54,6 +54,9 @@ fun TimeFormatPref.is24Hour(systemIs24Hour: Boolean): Boolean = when (this) {
TimeFormatPref.TWENTY_FOUR_HOUR -> true
}
/** How much of a task's description a due reminder shows when expanded. */
enum class ReminderDescription { HIDDEN, SHORT, FULL }
const val DEFAULT_SNOOZE_MINUTES = 10
/** Done starts folded away, to keep the open tasks in focus. */ val DEFAULT_COLLAPSED_SECTIONS: Set<TaskSection> = setOf(TaskSection.COMPLETED)
@@ -103,6 +106,7 @@ data class Settings(
val sortOrder: TaskSortOrder = TaskSortOrder.DUE,
/** How long a reminder's "Snooze" action puts it off. */
val snoozeMinutes: Int = DEFAULT_SNOOZE_MINUTES,
val reminderDescription: ReminderDescription = ReminderDescription.SHORT,
val timeFormat: TimeFormatPref = TimeFormatPref.AUTO,
/** The first day of the week; `null` follows the locale. */
val weekStart: DayOfWeek? = null,
@@ -171,6 +175,9 @@ class SettingsPrefs @Inject constructor(
sortOrder = p[SORT_ORDER]?.let { runCatching { TaskSortOrder.valueOf(it) }.getOrNull() }
?: TaskSortOrder.DUE,
snoozeMinutes = p[SNOOZE_MINUTES]?.takeIf { it > 0 } ?: DEFAULT_SNOOZE_MINUTES,
reminderDescription = p[REMINDER_DESCRIPTION]
?.let { runCatching { ReminderDescription.valueOf(it) }.getOrNull() }
?: ReminderDescription.SHORT,
timeFormat = p[TIME_FORMAT]?.let { runCatching { TimeFormatPref.valueOf(it) }.getOrNull() }
?: TimeFormatPref.AUTO,
autofocusTitle = p[AUTOFOCUS_TITLE] ?: true,
@@ -199,6 +206,9 @@ class SettingsPrefs @Inject constructor(
suspend fun setSnoozeMinutes(minutes: Int) = dataStore.edit { it[SNOOZE_MINUTES] = minutes.coerceAtLeast(1) }
suspend fun setReminderDescription(pref: ReminderDescription) =
dataStore.edit { it[REMINDER_DESCRIPTION] = pref.name }
suspend fun setSyncIntervalMinutes(minutes: Int) = dataStore.edit { it[SYNC_INTERVAL] = minutes }
suspend fun setPushEnabled(enabled: Boolean) = dataStore.edit { it[PUSH_ENABLED] = enabled }
@@ -345,6 +355,7 @@ class SettingsPrefs @Inject constructor(
val DEFAULT_EDIT_FIELDS = stringSetPreferencesKey("default_edit_fields")
val SORT_ORDER = stringPreferencesKey("sort_order")
val SNOOZE_MINUTES = intPreferencesKey("snooze_minutes")
val REMINDER_DESCRIPTION = stringPreferencesKey("reminder_description")
val TIME_FORMAT = stringPreferencesKey("time_format")
val WEEK_START = stringPreferencesKey("week_start")
val AUTOFOCUS_TITLE = booleanPreferencesKey("autofocus_title")
@@ -18,6 +18,7 @@ import androidx.core.content.ContextCompat
import androidx.core.content.edit
import de.jeanlucmakiola.agendula.MainActivity
import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.prefs.ReminderDescription
import de.jeanlucmakiola.agendula.data.prefs.Settings
import de.jeanlucmakiola.agendula.domain.readableDescription
import de.jeanlucmakiola.agendula.data.prefs.is24Hour
@@ -86,7 +87,11 @@ class TaskNotifier @Inject constructor(
),
)
}
val description = task.description?.readableDescription()?.trim()?.takeIf { it.isNotBlank() }
val description = reminderBody(
task.description?.readableDescription(),
settings.reminderDescription,
lineChars = (NOTIFICATION_LINE_CHARS / context.resources.configuration.fontScale).toInt(),
)
val tapIntent = PendingIntent.getActivity(
context,
task.taskId.toInt(),
@@ -277,3 +282,96 @@ internal fun expandedText(dueLine: String?, description: String?): String? = whe
dueLine == null -> description
else -> "$dueLine\n\n$description"
}
/** Rough characters per line of an expanded notification on a phone, at the default font size. */
private const val NOTIFICATION_LINE_CHARS = 40
/** The description a reminder shows, as [mode] asks; null when there is none to show. */
internal fun reminderBody(
description: String?,
mode: ReminderDescription,
lineChars: Int = NOTIFICATION_LINE_CHARS,
): String? {
val text = description?.trim()?.takeIf { it.isNotBlank() } ?: return null
return when (mode) {
ReminderDescription.HIDDEN -> null
ReminderDescription.SHORT -> shortDescription(text, lineChars = lineChars)
ReminderDescription.FULL -> text
}
}
/**
* [description] cut to about [maxLines] lines of an expanded notification,
* ending in an ellipsis when anything was left out. Blank lines are dropped,
* and a long line counts as the lines it wraps to.
*/
internal fun shortDescription(
description: String,
maxLines: Int = 3,
lineChars: Int = NOTIFICATION_LINE_CHARS,
): String {
val perLine = lineChars.coerceAtLeast(2)
val lines = description.lines().map { it.trimEnd() }.filter { it.isNotBlank() }
val kept = mutableListOf<String>()
var rowsLeft = maxLines
for ((index, line) in lines.withIndex()) {
val width = displayWidth(line)
val rows = (width + perLine - 1) / perLine
val last = index == lines.lastIndex
// The ellipsis after a line that ends the budget needs a cell of its own.
val fits = rows < rowsLeft || (rows == rowsLeft && (last || width < rows * perLine))
if (fits) {
kept += line
rowsLeft -= rows
if (rowsLeft == 0 && !last) return ellipsize(kept)
continue
}
if (rowsLeft > 0) kept += cutAtWord(line, rowsLeft * perLine - 1)
return ellipsize(kept)
}
return kept.joinToString("\n")
}
private fun ellipsize(lines: List<String>): String =
lines.joinToString("\n").trimEnd { it.isWhitespace() || it in ".,;:" } + "\u2026"
/** [line] cut to at most [max] cells, at a word break when there is one, never inside a character. */
private fun cutAtWord(line: String, max: Int): String {
var end = 0
var width = 0
while (end < line.length) {
val codePoint = line.codePointAt(end)
width += cellsOf(codePoint)
if (width > max) break
end += Character.charCount(codePoint)
}
if (end >= line.length || line[end].isWhitespace()) return line.substring(0, end)
val cut = line.substring(0, end)
val space = cut.indexOfLast { it.isWhitespace() }
return if (space > end / 2) cut.substring(0, space) else cut
}
/** How many cells [text] takes, wide characters (CJK, emoji) counting two. */
private fun displayWidth(text: String): Int {
var width = 0
var i = 0
while (i < text.length) {
val codePoint = text.codePointAt(i)
width += cellsOf(codePoint)
i += Character.charCount(codePoint)
}
return width
}
private fun cellsOf(codePoint: Int): Int = when {
Character.isSupplementaryCodePoint(codePoint) -> 2
Character.UnicodeScript.of(codePoint) in WIDE_SCRIPTS -> 2
else -> 1
}
private val WIDE_SCRIPTS = setOf(
Character.UnicodeScript.HAN,
Character.UnicodeScript.HIRAGANA,
Character.UnicodeScript.KATAKANA,
Character.UnicodeScript.HANGUL,
)
@@ -39,6 +39,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.prefs.ReminderDescription
import de.jeanlucmakiola.agendula.data.prefs.SNOOZE_PRESETS
import de.jeanlucmakiola.agendula.data.reminders.TaskNotifier
import de.jeanlucmakiola.agendula.domain.TaskReminderOrder
@@ -113,6 +114,7 @@ internal fun NotificationsScreen(
var showOffset by remember { mutableStateOf(false) }
var showAllDay by remember { mutableStateOf(false) }
var showSnooze by remember { mutableStateOf(false) }
var showDescription by remember { mutableStateOf(false) }
var showAllDayTime by remember { mutableStateOf(false) }
var perListExpanded by remember { mutableStateOf(false) }
var expandedLists by remember { mutableStateOf(emptySet<Long>()) }
@@ -178,9 +180,15 @@ internal fun NotificationsScreen(
R.string.settings_allday_reminder_time_summary,
formatMinuteOfDay(state.settings.allDayReminderMinuteOfDay, LocalUse24HourFormat.current),
),
position = Position.Bottom,
position = Position.Middle,
onClick = { showAllDayTime = true },
)
GroupedRow(
title = stringResource(R.string.settings_reminder_description),
summary = reminderDescriptionLabel(state.settings.reminderDescription),
position = Position.Bottom,
onClick = { showDescription = true },
)
Spacer(Modifier.height(24.dp))
val batteryExempt = rememberBatteryOptimizationExempt(context)
@@ -318,6 +326,17 @@ internal fun NotificationsScreen(
onDismiss = { showSnooze = false },
)
}
if (showDescription) {
OptionPicker(
title = stringResource(R.string.settings_reminder_description),
options = ReminderDescription.entries,
selected = state.settings.reminderDescription,
label = { reminderDescriptionLabel(it) },
summary = { reminderDescriptionSummary(it) },
onSelect = viewModel::setReminderDescription,
onDismiss = { showDescription = false },
)
}
if (showAllDayTime) {
val minute = state.settings.allDayReminderMinuteOfDay
TimePickerAlert(
@@ -354,6 +373,24 @@ private fun snoozeLabel(minutes: Int): String =
if (minutes % 60 == 0) pluralStringResource(R.plurals.duration_hours, minutes / 60, minutes / 60)
else pluralStringResource(R.plurals.duration_minutes, minutes, minutes)
@Composable
private fun reminderDescriptionLabel(pref: ReminderDescription): String = stringResource(
when (pref) {
ReminderDescription.HIDDEN -> R.string.settings_reminder_description_hidden
ReminderDescription.SHORT -> R.string.settings_reminder_description_short
ReminderDescription.FULL -> R.string.settings_reminder_description_full
},
)
@Composable
private fun reminderDescriptionSummary(pref: ReminderDescription): String = stringResource(
when (pref) {
ReminderDescription.HIDDEN -> R.string.settings_reminder_description_hidden_summary
ReminderDescription.SHORT -> R.string.settings_reminder_description_short_summary
ReminderDescription.FULL -> R.string.settings_reminder_description_full_summary
},
)
/** Which list's override picker is open, and for which kind of task. */
private data class OverrideTarget(val listId: Long, val allDay: Boolean)
@@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.agendula.data.appname.LauncherName
import de.jeanlucmakiola.agendula.data.appname.LauncherNameManager
import de.jeanlucmakiola.agendula.data.di.IoDispatcher
import de.jeanlucmakiola.agendula.data.prefs.ReminderDescription
import de.jeanlucmakiola.agendula.data.prefs.Settings
import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
@@ -259,6 +260,7 @@ class SettingsViewModel @Inject constructor(
fun setTimeFormat(pref: TimeFormatPref) = viewModelScope.launch { prefs.setTimeFormat(pref) }
fun setWeekStart(day: DayOfWeek?) = viewModelScope.launch { prefs.setWeekStart(day) }
fun setSnoozeMinutes(minutes: Int) = viewModelScope.launch { prefs.setSnoozeMinutes(minutes) }
fun setReminderDescription(pref: ReminderDescription) = viewModelScope.launch { prefs.setReminderDescription(pref) }
fun setShowAddSubtaskRow(show: Boolean) = viewModelScope.launch { prefs.setShowAddSubtaskRow(show) }
fun setBottomAddBar(enabled: Boolean) = viewModelScope.launch { prefs.setBottomAddBar(enabled) }
fun setDefaultStartToday(enabled: Boolean) = viewModelScope.launch { prefs.setDefaultStartToday(enabled) }
+91
View File
@@ -763,4 +763,95 @@
<string name="widget_config_background">الخلفية</string>
<string name="widget_config_background_solid">سادة</string>
<string name="widget_config_theme">السمة</string>
<string name="app_name_tasks_alias">مهام</string>
<string name="list_where">أين</string>
<string name="settings_backup_subtitle">التصدير و الاستيراد</string>
<string name="error_reminder_without_date">اضبط تاريخ بدء أو استحقاق لاستخدام تذكير.</string>
<string name="reminder_at_start">في وقت البدء</string>
<string name="reminder_at_due_time">في وقت الاستحقاق</string>
<string name="reminder_on_start_date">عند موعد البدء</string>
<string name="reminder_on_due_date">عند موعد الاستحقاق</string>
<string name="reminder_at_time">%1$s عند %2$s</string>
<string name="reminder_anchor_start">من البدء</string>
<string name="reminder_anchor_due">من تاريخ الاستحقاق</string>
<string name="reminder_time">الوقت</string>
<string name="reminder_benefit_delivery_title">تنبيه عندما يكون الأمر مهمًا</string>
<string name="reminder_benefit_reversible_title">يمكنك تغييره في أي وقت</string>
<string name="settings_app_name_hint">اعرض Agendula كـ ”مهام“ في مشغّل التطبيقات الخاص بك. فقط الاسم في المشغّل يتغير؛ وقد ينتقل الرمز إلى مكان جديد. يُغلَق Agendula عندما تقوم بالتبديل، لذا أعِد فتحه من المشغّل.</string>
<string name="settings_allday_reminder_time_summary">العد التنازلي لمهام التي تستمر طوال اليوم عند %1$s</string>
<string name="settings_store_permission_denied_hint">مهام التطبيق الآخر تبقى غير قابلة للوصول حتى تسمح بالوصول. اضغط لفتح إعدادات التطبيق.</string>
<string name="settings_copy_counting">جارٍ حساب ما هناك لنسخه…</string>
<string name="settings_copy_confirm_empty">%1$s لا يحتوي على مهام لنسخها.</string>
<string name="settings_copy_nothing">لم يكن هناك شيء للنسخ</string>
<string name="export_hint">ملف .ics واحد لكل قائمة، قابل للقراءة بواسطة تطبيقات المهام والتقويم الأخرى. القوائم الموضوع عليها بعلامة صح تذهب إلى مجلد تختاره أنت، أو إلى ملف مضغوط (zip) واحد.</string>
<string name="export_no_lists">لا توجد قوائم للتصدير</string>
<string name="export_to_folder">الحفظ إلى مجلد</string>
<string name="export_to_zip">حفظ كملف مضغوط (zip)</string>
<plurals name="export_done">
<item quantity="zero">تم تصدير (%1$d) لا قوائم</item>
<item quantity="one">تم تصدير قائمة واحدة (%1$d)</item>
<item quantity="two">تم تصدير قائمتان (%1$d)</item>
<item quantity="few">تم تصدير %1$d قوائم</item>
<item quantity="many">تم تصدير %1$d قائمة</item>
<item quantity="other">تم تصدير %1$d قائمة</item>
</plurals>
<plurals name="reminder_minutes_before_start">
<item quantity="zero">(%1$d) لا دقائق قبل البدء</item>
<item quantity="one">%1$d دقيقة واحده قبل البدء</item>
<item quantity="two">%1$d دقيقتان قبل البدء</item>
<item quantity="few">%1$d دقائق قبل البدء</item>
<item quantity="many">%1$d دقيقة قبل البدء</item>
<item quantity="other">%1$d دقيقة قبل البدء</item>
</plurals>
<plurals name="reminder_hours_before_start">
<item quantity="zero">(%1$d) لا ساعات قبل البدء</item>
<item quantity="one">%1$d ساعة واحده قبل البدء</item>
<item quantity="two">%1$d ساعتان قبل البدء</item>
<item quantity="few">%1$d ساعات قبل البدء</item>
<item quantity="many">%1$d ساعة قبل البدء</item>
<item quantity="other">%1$d ساعة قبل البدء</item>
</plurals>
<plurals name="reminder_days_before_start">
<item quantity="zero">(%1$d) لا أيام قبل البدء</item>
<item quantity="one">%1$d يوم واحد قبل البدء</item>
<item quantity="two">%1$d يومان قبل البدء</item>
<item quantity="few">%1$d أيام قبل البدء</item>
<item quantity="many">%1$d يوم قبل البدء</item>
<item quantity="other">%1$d يوم قبل البدء</item>
</plurals>
<plurals name="reminder_weeks_before_start">
<item quantity="zero">(%1$d) لا أسابيع قبل البدء</item>
<item quantity="one">%1$d أسبوع واحد قبل البدء</item>
<item quantity="two">%1$d أسبوعان قبل البدء</item>
<item quantity="few">%1$d أسابيع قبل البدء</item>
<item quantity="many">%1$d أسبوع قبل البدء</item>
<item quantity="other">%1$d أسبوع قبل البدء</item>
</plurals>
<string name="settings_accounts_subtitle">مزامنة مهامك مع خادم CalDAV</string>
<string name="accounts_add">إضافة حسابًا</string>
<string name="accounts_sync_interval">المزامنة تلقائيًا</string>
<string name="accounts_sync_interval_manual">فقط عند تقوم بالمزامنة</string>
<string name="accounts_push_via">تصل التغييرات فورًا، عبر %1$s</string>
<string name="widget_unavailable">افتح Agendula لإنهاء الإعداد</string>
<string name="widget_today_all_done">انتهى كل شيء</string>
<string name="widget_config_title">إظهار في الأداة المصغرة</string>
<string name="widget_config_group_by_list">تجميع حسب القائمة</string>
<string name="widget_config_group_by_list_hint">إظهار مهام كل قائمة ضمن عنوانها الخاص</string>
<string name="widget_config_background_solid_hint">يملأ الأداة المصغّرة بلون سطح السمة</string>
<string name="widget_config_background_tinted_hint">أكثر نعومة، تعبئة لونية متناغمة</string>
<string name="widget_config_background_transparent">شفافة</string>
<string name="widget_config_background_transparent_hint">لا تعبئة — تبقى المهام قابلة للقراءة في حاوياتها الخاصة</string>
<string name="widget_config_row_color_neutral_hint">يستخدم كل صف نفس التعبئة اللونية المتناغمة</string>
<string name="widget_config_row_color_by_list">حسب القائمة</string>
<string name="widget_config_row_color_by_list_hint">كل صف متناغم باللون الخاص بقائمته</string>
<string name="widget_today_description">حلقة تقدم لمهام اليوم</string>
<string name="widget_up_next_description">مهمتك الوحيدة الأقرب لموعد الاستحقاق</string>
<string name="widget_lists_label">قوائم</string>
<string name="widget_lists_description">مربع اختصار لكل قائمة، مع عدد مرّات فتحه</string>
<string name="widget_week_label">أسبوع</string>
<string name="widget_week_description">شريط من سبعة أيام لعدد المهام</string>
<string name="widget_config_background_tinted">ملونة</string>
<string name="settings_theme_black">أسود</string>
<string name="settings_theme_black_summary">داكن مع خلفيات سوداء نقية، لشاشات AMOLED</string>
<string name="settings_app_name">اسم التطبيق</string>
</resources>
+14
View File
@@ -320,4 +320,18 @@
<string name="reminder_at_due">V čase úkolu</string>
<string name="reminder_on_the_day">V daný den</string>
<string name="settings_list_reminders_title">Upozornění pro jednotlivé seznamy</string>
<string name="shortcut_new_task">Nový úkol</string>
<string name="shortcut_today">Dnes</string>
<string name="qs_tile_new_task">Nový úkol</string>
<string name="sort_title">Seřadit podle</string>
<string name="view_options_title">Řazení a filtrování</string>
<string name="view_filter_tags">Filtrovat podle štítku</string>
<string name="view_filter_clear">Zrušit filtr</string>
<string name="sort_due">Termín splnění</string>
<string name="sort_title_alpha">Název</string>
<string name="sort_created">Nedávno přidané</string>
<string name="clear_completed_action">Zrušit</string>
<string name="undo">Zpět</string>
<string name="section_today">Dnes</string>
<string name="section_upcoming">Nadcházející</string>
</resources>
+7
View File
@@ -28,4 +28,11 @@
<string name="qs_tile_new_task">Новая задача</string>
<string name="lists_reorder">Переопределить список</string>
<string name="lists_reorder_done">Завершить переопределение</string>
<string name="app_name_tasks_alias">Задачи</string>
<string name="lists_reorder_handle">Перетащите, чтобы изменить порядок</string>
<string name="sort_title">Сортировать по</string>
<string name="view_options_title">Сортировать и фильтровать</string>
<string name="view_filter_tags">Фильтр по тегу</string>
<string name="view_filter_clear">Очистить фильтр</string>
<string name="sort_due">Срок выполнения</string>
</resources>
+7
View File
@@ -470,6 +470,13 @@
<string name="settings_exact_alarms_blocked">Blocked — tap to allow exact reminders</string>
<string name="settings_reliable_delivery">Reliable delivery</string>
<string name="settings_snooze_duration">Snooze duration</string>
<string name="settings_reminder_description">Description in reminders</string>
<string name="settings_reminder_description_hidden">Hidden</string>
<string name="settings_reminder_description_short">Short</string>
<string name="settings_reminder_description_full">Full</string>
<string name="settings_reminder_description_hidden_summary">Only the title and due time</string>
<string name="settings_reminder_description_short_summary">A short preview of the description</string>
<string name="settings_reminder_description_full_summary">The whole description</string>
<string name="settings_reliable_delivery_hint">Android may delay reminders to save battery. Exempt Agendula so they arrive on time.</string>
<string name="settings_reliable_delivery_exempt">Exempt from battery optimisation — reminders arrive on time.</string>
<string name="settings_default_list">Default list</string>
@@ -0,0 +1,99 @@
package de.jeanlucmakiola.agendula.data.reminders
import com.google.common.truth.Truth.assertThat
import de.jeanlucmakiola.agendula.data.prefs.ReminderDescription
import org.junit.jupiter.api.Test
class ShortDescriptionTest {
@Test
fun `short text is left alone`() {
assertThat(shortDescription("Buy oat milk")).isEqualTo("Buy oat milk")
}
@Test
fun `three short lines fit`() {
assertThat(shortDescription("one\ntwo\nthree")).isEqualTo("one\ntwo\nthree")
}
@Test
fun `lines past the third are cut with an ellipsis`() {
assertThat(shortDescription("one\ntwo\nthree\nfour\nfive")).isEqualTo("one\ntwo\nthree…")
}
@Test
fun `blank lines take no room`() {
assertThat(shortDescription("one\n\n\ntwo\n \nthree")).isEqualTo("one\ntwo\nthree")
}
@Test
fun `a long paragraph is cut at a word within the line budget`() {
val text = List(40) { "word" }.joinToString(" ")
val short = shortDescription(text, maxLines = 3, lineChars = 40)
assertThat(short).endsWith("word…")
assertThat(short.length).isAtMost(3 * 40)
}
@Test
fun `a wrapping line counts as the lines it takes`() {
val long = "a".repeat(70)
val short = shortDescription("$long\nsecond\nthird", maxLines = 3, lineChars = 40)
assertThat(short).isEqualTo("$long\nsecond…")
}
@Test
fun `a word with no break is cut hard`() {
val short = shortDescription("x".repeat(200), maxLines = 3, lineChars = 40)
assertThat(short).isEqualTo("x".repeat(119) + "…")
}
@Test
fun `trailing punctuation gives way to the ellipsis`() {
assertThat(shortDescription("one.\ntwo,\nthree.\nfour")).isEqualTo("one.\ntwo,\nthree…")
}
@Test
fun `a line that fills the budget exactly leaves room for the ellipsis`() {
val full = "a".repeat(120)
val short = shortDescription("$full\nmore", maxLines = 3, lineChars = 40)
assertThat(short).isEqualTo("a".repeat(119) + "…")
}
@Test
fun `a cut never splits an emoji`() {
val short = shortDescription("x".repeat(118) + "😀" + "y".repeat(10), maxLines = 3, lineChars = 40)
assertThat(short).isEqualTo("x".repeat(118) + "…")
}
@Test
fun `a word that ends right at the cut is kept`() {
val short = shortDescription("aaaa bbbb cccc", maxLines = 1, lineChars = 10)
assertThat(short).isEqualTo("aaaa bbbb…")
}
@Test
fun `tabs count as word breaks`() {
val short = shortDescription("Meeting\tagenda\tnotes", maxLines = 1, lineChars = 12)
assertThat(short).isEqualTo("Meeting…")
}
@Test
fun `no space is left before the ellipsis`() {
assertThat(shortDescription("see list :\nmore", maxLines = 1, lineChars = 40)).isEqualTo("see list…")
}
@Test
fun `wide characters take two cells`() {
val short = shortDescription("中".repeat(30), maxLines = 1, lineChars = 20)
assertThat(short).isEqualTo("中".repeat(9) + "…")
}
@Test
fun `the setting picks what the reminder shows`() {
val long = List(60) { "word" }.joinToString(" ")
assertThat(reminderBody(long, ReminderDescription.HIDDEN)).isNull()
assertThat(reminderBody(long, ReminderDescription.FULL)).isEqualTo(long)
assertThat(reminderBody(long, ReminderDescription.SHORT)).isEqualTo(shortDescription(long))
assertThat(reminderBody(" ", ReminderDescription.FULL)).isNull()
}
}