chore: clear out what the own-store branch left behind, and refresh deps

Dead weight, found by reading `lintDebug` rather than by anything breaking.

Gone with the deleted `:provider` module: its entire dependency block in the
version catalog — jems, rfc5545-datetime, Robolectric, JUnit 4, Hamcrest,
Mockito — plus the comments explaining a `provider/PROVENANCE.md` that is not
there any more. Only lib-recur survived the deletion and it is now documented
where it lives. That block was also the source of most of the catalog's
"newer version available" noise.

Gone outright: `glance-appwidget` and `glance-material3`, declared but not
referenced by a single line, so they were shipping in the APK for nothing. The
WorkManager `ListenableWorker` keep rule went with them, since Glance was what
dragged WorkManager in; the RoomDatabase keep rule stays and its comment now
says why it matters *more* than it did — Room used to arrive transitively
through that chain, and is now our own task store.

Also gone: thirteen unused resources (twelve strings and a colour), which
volunteers on Weblate were translating for nothing; a `SDK_INT < O` branch that
cannot be false at minSdk 29; and the `-v26` qualifier on the mipmap folder,
unnecessary at the same minSdk.

`DemoSeeder` is injected as a `Provider` now. Its call site is
`BuildConfig.DEBUG`-gated, i.e. compile-time dead in a release build, but
injecting the instance still constructed one on every launch of the shipped
app.

`ProviderChangeReceiver` gets one intent-filter per authority rather than two
`<data>` tags in one filter. Identical behaviour — Android takes the cross
product of every data attribute in a filter — but it no longer reads as if it
might not, which is what lint's IntentFilterUniqueDataAttributes warns about.

Dependency refresh, app-level only: KSP 2.3.11, Hilt 2.60.1, lifecycle 2.11.0,
material3 1.5.0-alpha26, JUnit 6.1.3, hilt-navigation-compose 1.4.0. That last
one moved `hiltViewModel` into `androidx.hilt.lifecycle.viewmodel.compose`, so
nine call sites follow it and the artifact it now lives in is declared rather
than inherited. The floret-kit coordinates move into the catalog, version-less,
since the composite build substitutes them.

Deliberately not touched: AGP, Gradle, Kotlin, the Compose BOM and
kotlinx-* — toolchain moves that want their own change and a
reproducible-build check, not a release cut. lib-recur stays pinned at 0.12.2
(0.16.0 removed `RecurrenceSet`).
This commit is contained in:
2026-09-21 13:37:49 +02:00
parent 003abcce79
commit b49a8af83d
21 changed files with 75 additions and 118 deletions
+8 -12
View File
@@ -174,12 +174,11 @@ dependencies {
implementation(libs.hilt.android) implementation(libs.hilt.android)
implementation(libs.androidx.hilt.navigation.compose) implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)
ksp(libs.hilt.compiler) ksp(libs.hilt.compiler)
// RFC 5545 recurrence expansion, in-process. Pinned at 0.12.2 — 0.16.0 // RFC 5545 recurrence expansion, in-process; see the catalog for the pin.
// removed RecurrenceSet. rfc5545-datetime comes with it and is part of its
// API surface, so it isn't declared separately.
implementation(libs.dmfs.lib.recur) implementation(libs.dmfs.lib.recur)
implementation(libs.androidx.room.runtime) implementation(libs.androidx.room.runtime)
@@ -189,17 +188,14 @@ dependencies {
implementation(libs.androidx.datastore.preferences) implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.documentfile) implementation(libs.androidx.documentfile)
implementation(libs.androidx.glance.appwidget)
implementation(libs.androidx.glance.material3)
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation("de.jeanlucmakiola.floret:core-time") implementation(libs.floret.core.time)
implementation("de.jeanlucmakiola.floret:core-reminders") implementation(libs.floret.core.reminders)
implementation("de.jeanlucmakiola.floret:core-locale") implementation(libs.floret.core.locale)
implementation("de.jeanlucmakiola.floret:core-crash") implementation(libs.floret.core.crash)
implementation("de.jeanlucmakiola.floret:identity") implementation(libs.floret.identity)
implementation("de.jeanlucmakiola.floret:components") implementation(libs.floret.components)
debugImplementation(libs.androidx.ui.tooling) debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest) debugImplementation(libs.androidx.ui.test.manifest)
+4 -10
View File
@@ -5,17 +5,11 @@
# Room instantiates its generated <Database>_Impl reflectively through a no-arg # Room instantiates its generated <Database>_Impl reflectively through a no-arg
# constructor. R8 under AGP 9 keeps the class but prunes that constructor, since # constructor. R8 under AGP 9 keeps the class but prunes that constructor, since
# nothing calls it directly — Room then throws InstantiationException, reported # nothing calls it directly — Room then throws InstantiationException, reported
# as "Failed to create an instance of ...". We pull Room in transitively via # as "Failed to create an instance of ...". This first bit us through a
# Glance -> WorkManager, whose WorkDatabase is built by WorkManagerInitializer # transitive Room (Glance -> WorkManager -> WorkDatabase, built at startup:
# at startup, so the app died on launch in every minified build (issue #1). # issue #1); Glance is gone and Room is now our own task store, so the rule
# matters more, not less — TasksDatabase is built on the first store read.
-keep class * extends androidx.room.RoomDatabase { <init>(); } -keep class * extends androidx.room.RoomDatabase { <init>(); }
# WorkManager likewise looks its workers up by name and calls this constructor
# reflectively — same pruning, but it only bites once a worker actually runs
# (Glance's widget updates), so keep it explicitly rather than wait for it.
-keep class * extends androidx.work.ListenableWorker {
<init>(android.content.Context, androidx.work.WorkerParameters);
}
# Compose Compiler may keep its own; defaults are fine # Compose Compiler may keep its own; defaults are fine
-dontwarn org.jetbrains.annotations.** -dontwarn org.jetbrains.annotations.**
+11 -3
View File
@@ -83,16 +83,24 @@
<!-- Re-sync reminders when an external provider changes — DAVx5 pulling <!-- Re-sync reminders when an external provider changes — DAVx5 pulling
tasks while Agendula is backgrounded. External mode only: in OWN mode tasks while Agendula is backgrounded. External mode only: in OWN mode
nothing outside the app can change our data, and Room's nothing outside the app can change our data, and Room's
InvalidationTracker covers our own writes. An intent-filter host must InvalidationTracker covers our own writes.
be a literal, so both external authorities are listed. -->
An intent-filter host must be a literal, so both external authorities
are listed — one filter each. Two <data> tags in a single filter would
mean the same thing (Android takes the cross product of every data
attribute in a filter), but reads as if it might not, which is what
lint's IntentFilterUniqueDataAttributes warns about. -->
<receiver <receiver
android:name=".data.reminders.ProviderChangeReceiver" android:name=".data.reminders.ProviderChangeReceiver"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.PROVIDER_CHANGED" /> <action android:name="android.intent.action.PROVIDER_CHANGED" />
<data android:scheme="content" android:host="org.tasks.opentasks" />
<data android:scheme="content" android:host="org.dmfs.tasks" /> <data android:scheme="content" android:host="org.dmfs.tasks" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.PROVIDER_CHANGED" />
<data android:scheme="content" android:host="org.tasks.opentasks" />
</intent-filter>
</receiver> </receiver>
<!-- Persists the per-app language on API < 33, where the platform <!-- Persists the per-app language on API < 33, where the platform
@@ -12,7 +12,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@@ -27,6 +27,7 @@ import de.jeanlucmakiola.floret.crash.CrashReporter
import de.jeanlucmakiola.floret.crash.submitCrashReport import de.jeanlucmakiola.floret.crash.submitCrashReport
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
/** /**
* Single activity. The theme follows [SettingsViewModel]; [RootScreen] is the * Single activity. The theme follows [SettingsViewModel]; [RootScreen] is the
@@ -36,7 +37,10 @@ import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@Inject lateinit var demoSeeder: DemoSeeder // A Provider, not the seeder itself: the call site below is dead in a release
// build (`BuildConfig.DEBUG` is a compile-time false), and injecting the
// instance would still construct one on every launch of the shipped app.
@Inject lateinit var demoSeeder: Provider<DemoSeeder>
// A captured crash report awaiting the user's decision, surfaced as a dialog // A captured crash report awaiting the user's decision, surfaced as a dialog
// over the app on the next launch (the single-crash path). A startup // over the app on the next launch (the single-crash path). A startup
@@ -63,7 +67,7 @@ class MainActivity : ComponentActivity() {
// Debug-only sample data: `am start ... --ez agendula_seed true`. Seeds a // Debug-only sample data: `am start ... --ez agendula_seed true`. Seeds a
// local (non-syncing) demo list once; no-op without the extra. // local (non-syncing) demo list once; no-op without the extra.
if (BuildConfig.DEBUG && intent.getBooleanExtra(EXTRA_SEED, false)) { if (BuildConfig.DEBUG && intent.getBooleanExtra(EXTRA_SEED, false)) {
lifecycleScope.launch { runCatching { demoSeeder.seed() } } lifecycleScope.launch { runCatching { demoSeeder.get().seed() } }
} }
setContent { setContent {
val settingsViewModel: SettingsViewModel = hiltViewModel() val settingsViewModel: SettingsViewModel = hiltViewModel()
@@ -79,7 +79,6 @@ class TaskNotifier @Inject constructor(
} }
private fun ensureChannel() { private fun ensureChannel() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val manager = context.getSystemService(NotificationManager::class.java) val manager = context.getSystemService(NotificationManager::class.java)
if (manager.getNotificationChannel(CHANNEL_ID) != null) return if (manager.getNotificationChannel(CHANNEL_ID) != null) return
manager.createNotificationChannel( manager.createNotificationChannel(
@@ -1,6 +1,7 @@
package de.jeanlucmakiola.agendula.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri
/** /**
* The subset of the OpenTasks `TaskContract` that Agendula uses, vendored as * The subset of the OpenTasks `TaskContract` that Agendula uses, vendored as
@@ -150,7 +151,7 @@ object TasksContract {
const val TYPE_MESSAGE = 1 const val TYPE_MESSAGE = 1
} }
fun propertiesUri(authority: String): Uri = Uri.parse("content://$authority/${Properties.PATH}") fun propertiesUri(authority: String): Uri = "content://$authority/${Properties.PATH}".toUri()
// --- status values (TaskColumns.STATUS_*) -------------------------------- // --- status values (TaskColumns.STATUS_*) --------------------------------
const val STATUS_NEEDS_ACTION = 0 const val STATUS_NEEDS_ACTION = 0
@@ -161,12 +162,12 @@ object TasksContract {
/** Priority 0 means "no priority"; 1 is highest, 9 lowest (iCalendar). */ /** Priority 0 means "no priority"; 1 is highest, 9 lowest (iCalendar). */
const val PRIORITY_NONE = 0 const val PRIORITY_NONE = 0
fun authorityUri(authority: String): Uri = Uri.parse("content://$authority") fun authorityUri(authority: String): Uri = "content://$authority".toUri()
fun listsUri(authority: String): Uri = Uri.parse("content://$authority/${Lists.PATH}") fun listsUri(authority: String): Uri = "content://$authority/${Lists.PATH}".toUri()
fun listUri(authority: String, listId: Long): Uri = fun listUri(authority: String, listId: Long): Uri =
Uri.parse("content://$authority/${Lists.PATH}/$listId") "content://$authority/${Lists.PATH}/$listId".toUri()
fun tasksUri(authority: String): Uri = Uri.parse("content://$authority/${Tasks.PATH}") fun tasksUri(authority: String): Uri = "content://$authority/${Tasks.PATH}".toUri()
fun instancesUri(authority: String): Uri = Uri.parse("content://$authority/${Instances.PATH}") fun instancesUri(authority: String): Uri = "content://$authority/${Instances.PATH}".toUri()
/** /**
* A single occurrence. Updating through this URI is how a *recurring* task is * A single occurrence. Updating through this URI is how a *recurring* task is
@@ -175,7 +176,7 @@ object TasksContract {
* the series anchor, which is what writing to `tasks/<id>` would do. * the series anchor, which is what writing to `tasks/<id>` would do.
*/ */
fun instanceUri(authority: String, instanceId: Long): Uri = fun instanceUri(authority: String, instanceId: Long): Uri =
Uri.parse("content://$authority/${Instances.PATH}/$instanceId") "content://$authority/${Instances.PATH}/$instanceId".toUri()
/** Append the sync-adapter params required to write local-account rows. */ /** Append the sync-adapter params required to write local-account rows. */
fun asSyncAdapter(uri: Uri, accountName: String, accountType: String): Uri = fun asSyncAdapter(uri: Uri, accountName: String, accountType: String): Uri =
@@ -17,7 +17,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.ui.common.OnResume import de.jeanlucmakiola.agendula.ui.common.OnResume
@@ -65,7 +65,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.domain.Task import de.jeanlucmakiola.agendula.domain.Task
@@ -78,7 +78,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.time.DayWindow import de.jeanlucmakiola.floret.time.DayWindow
@@ -30,7 +30,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.export.ExportFailure import de.jeanlucmakiola.agendula.data.export.ExportFailure
@@ -72,7 +72,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
@@ -8,7 +8,7 @@ import androidx.compose.animation.scaleOut
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
@@ -74,7 +74,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.agendula.data.prefs.ThemeMode import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
@@ -87,7 +87,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.time.DayWindow import de.jeanlucmakiola.floret.time.DayWindow
@@ -203,7 +203,7 @@ fun TaskListScreen(
}, },
bottomBar = { bottomBar = {
if (showBottomAddBar) { if (showBottomAddBar) {
listId?.let { id -> QuickAddBar(onAdd = { title -> viewModel.quickAdd(title, id) }) } QuickAddBar(onAdd = { title -> viewModel.quickAdd(title, listId) })
} }
}, },
) { inner -> ) { inner ->
-11
View File
@@ -15,9 +15,6 @@
<string name="smart_completed">Abgeschlossen</string> <string name="smart_completed">Abgeschlossen</string>
<string name="task_detail_title">Aufgabe</string> <string name="task_detail_title">Aufgabe</string>
<string name="task_detail_not_found">Diese Aufgabe ist nicht mehr verfügbar.</string> <string name="task_detail_not_found">Diese Aufgabe ist nicht mehr verfügbar.</string>
<string name="edit_task_title">Aufgabe bearbeiten</string>
<string name="new_task_title">Neue Aufgabe</string>
<string name="field_title">Titel</string>
<string name="edit_save_failed">Konnte nicht gespeichert werden. Versuche es erneut.</string> <string name="edit_save_failed">Konnte nicht gespeichert werden. Versuche es erneut.</string>
<string name="add_task_hint">Aufgabe hinzufügen</string> <string name="add_task_hint">Aufgabe hinzufügen</string>
<string name="add_subtask_hint">Unteraufgabe hinzufügen</string> <string name="add_subtask_hint">Unteraufgabe hinzufügen</string>
@@ -36,11 +33,8 @@
<string name="priority_low">Niedrig</string> <string name="priority_low">Niedrig</string>
<string name="priority_medium">Mittel</string> <string name="priority_medium">Mittel</string>
<string name="priority_high">Hoch</string> <string name="priority_high">Hoch</string>
<string name="detail_mark_complete">Als abgeschlossen markieren</string>
<string name="detail_mark_incomplete">Als nicht abgeschlossen markieren</string>
<string name="detail_list">Liste</string> <string name="detail_list">Liste</string>
<string name="detail_due">Fällig</string> <string name="detail_due">Fällig</string>
<string name="detail_start">Beginn</string>
<string name="detail_priority">Priorität</string> <string name="detail_priority">Priorität</string>
<string name="detail_progress">Fortschritt</string> <string name="detail_progress">Fortschritt</string>
<string name="detail_subtasks">Unteraufgaben</string> <string name="detail_subtasks">Unteraufgaben</string>
@@ -98,7 +92,6 @@
<string name="smart_overdue">Überfällig</string> <string name="smart_overdue">Überfällig</string>
<string name="smart_upcoming">Anstehend</string> <string name="smart_upcoming">Anstehend</string>
<string name="smart_all">Alle</string> <string name="smart_all">Alle</string>
<string name="open_count">%1$d offen</string>
<string name="home_today_progress">%1$d von %2$d erledigt</string> <string name="home_today_progress">%1$d von %2$d erledigt</string>
<string name="home_today_remaining">%1$d übrig</string> <string name="home_today_remaining">%1$d übrig</string>
<string name="home_today_all_done">Alles erledigt 🎉</string> <string name="home_today_all_done">Alles erledigt 🎉</string>
@@ -108,7 +101,6 @@
<string name="home_due_tomorrow">Morgen</string> <string name="home_due_tomorrow">Morgen</string>
<string name="home_search_hint">Aufgaben suchen</string> <string name="home_search_hint">Aufgaben suchen</string>
<string name="home_search_clear">Suche zurücksetzen</string> <string name="home_search_clear">Suche zurücksetzen</string>
<string name="home_search_close">Suche schließen</string>
<string name="home_search_empty">Keine Aufgaben passen zu „%1$s“</string> <string name="home_search_empty">Keine Aufgaben passen zu „%1$s“</string>
<string name="reminder_due_at">Fälligkeiten %1$s</string> <string name="reminder_due_at">Fälligkeiten %1$s</string>
<string name="reminder_channel_name">Aufgaben Erinnerungen</string> <string name="reminder_channel_name">Aufgaben Erinnerungen</string>
@@ -118,8 +110,6 @@
<string name="onboarding_permission_title">Erlaube Zugriff auf deine Aufgaben</string> <string name="onboarding_permission_title">Erlaube Zugriff auf deine Aufgaben</string>
<string name="onboarding_permission_body">Agendula benötigt die Berechtigung, deine Aufgaben zu lesen und zu schreiben. Das ist die einzige Berechtigung, die es jemals anfragt.</string> <string name="onboarding_permission_body">Agendula benötigt die Berechtigung, deine Aufgaben zu lesen und zu schreiben. Das ist die einzige Berechtigung, die es jemals anfragt.</string>
<string name="onboarding_permission_button">Zugriff gewähren</string> <string name="onboarding_permission_button">Zugriff gewähren</string>
<string name="onboarding_install_opentasks">Installiere OpenTasks</string>
<string name="onboarding_install_tasksorg">Installiere tasks.org</string>
<string name="reminder_onboarding_title">Verpasse nichts, was fällig ist</string> <string name="reminder_onboarding_title">Verpasse nichts, was fällig ist</string>
<string name="reminder_onboarding_body">Aufgaben-Apps senden selbst keine Erinnerungen, daher liefert Agendula sie für dich. Schalte sie ein, um eine Benachrichtigung zu erhalten, wenn eine Aufgabe fällig ist.</string> <string name="reminder_onboarding_body">Aufgaben-Apps senden selbst keine Erinnerungen, daher liefert Agendula sie für dich. Schalte sie ein, um eine Benachrichtigung zu erhalten, wenn eine Aufgabe fällig ist.</string>
<string name="reminder_onboarding_enable_button">Erinnerungen aktivieren</string> <string name="reminder_onboarding_enable_button">Erinnerungen aktivieren</string>
@@ -162,7 +152,6 @@
<string name="settings_exact_alarms">Exakter Zeitpunkt</string> <string name="settings_exact_alarms">Exakter Zeitpunkt</string>
<string name="settings_exact_alarms_allowed">Die Erinnerungen erfolgen zum exakten Zeitpunkt</string> <string name="settings_exact_alarms_allowed">Die Erinnerungen erfolgen zum exakten Zeitpunkt</string>
<string name="settings_exact_alarms_blocked">Blockiert – tippe, um exakte Erinnerungen zu erlauben</string> <string name="settings_exact_alarms_blocked">Blockiert – tippe, um exakte Erinnerungen zu erlauben</string>
<string name="settings_section_tasks">Aufgaben</string>
<string name="settings_default_list">Standard-Liste</string> <string name="settings_default_list">Standard-Liste</string>
<string name="settings_default_list_first">Erste verfügbare Felder</string> <string name="settings_default_list_first">Erste verfügbare Felder</string>
<string name="settings_add_subtask_row">Zeile \"Unteraufgaben hinzufügen\" anzeigen</string> <string name="settings_add_subtask_row">Zeile \"Unteraufgaben hinzufügen\" anzeigen</string>
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Agendula</string> <string name="app_name">Agendula</string>
<string name="app_tagline">Um app de tarefas em Material 3 Expressive. \nFlorescendo.</string>
<string name="task_untitled">Tarefa sem título</string> <string name="task_untitled">Tarefa sem título</string>
<string name="back">Voltar</string> <string name="back">Voltar</string>
<string name="save">Salvar</string> <string name="save">Salvar</string>
@@ -14,9 +13,6 @@
<string name="task_detail_title">Tarefa</string> <string name="task_detail_title">Tarefa</string>
<string name="task_detail_not_found">Esta tarefa não existe mais.</string> <string name="task_detail_not_found">Esta tarefa não existe mais.</string>
<string name="edit">Editar</string> <string name="edit">Editar</string>
<string name="edit_task_title">Editar tarefa</string>
<string name="new_task_title">Nova tarefa</string>
<string name="field_title">Título</string>
<string name="field_description">Descrição</string> <string name="field_description">Descrição</string>
<string name="edit_save_failed">Não foi possível salvar. Tente novamente.</string> <string name="edit_save_failed">Não foi possível salvar. Tente novamente.</string>
<string name="add_task_hint">Adicionar tarefa</string> <string name="add_task_hint">Adicionar tarefa</string>
@@ -37,11 +33,8 @@
<string name="priority_low">Baixa</string> <string name="priority_low">Baixa</string>
<string name="priority_medium">Média</string> <string name="priority_medium">Média</string>
<string name="priority_high">Alta</string> <string name="priority_high">Alta</string>
<string name="detail_mark_complete">Marcar como concluído</string>
<string name="detail_mark_incomplete">Marcar como não concluído</string>
<string name="detail_list">Lista</string> <string name="detail_list">Lista</string>
<string name="detail_due">Prazo</string> <string name="detail_due">Prazo</string>
<string name="detail_start">Inicia</string>
<string name="detail_priority">Prioridade</string> <string name="detail_priority">Prioridade</string>
<string name="detail_progress">Progresso</string> <string name="detail_progress">Progresso</string>
<string name="detail_subtasks">Subtarefas</string> <string name="detail_subtasks">Subtarefas</string>
@@ -100,7 +93,6 @@
<string name="smart_overdue">Prazo expirado</string> <string name="smart_overdue">Prazo expirado</string>
<string name="smart_upcoming">A seguir</string> <string name="smart_upcoming">A seguir</string>
<string name="smart_all">Tudo</string> <string name="smart_all">Tudo</string>
<string name="open_count">%1$d abertos</string>
<string name="home_today_progress">%1$d de %2$d concluídos</string> <string name="home_today_progress">%1$d de %2$d concluídos</string>
<string name="home_today_remaining">%1$d restantes</string> <string name="home_today_remaining">%1$d restantes</string>
<string name="home_today_all_done">Tudo pronto 🎉</string> <string name="home_today_all_done">Tudo pronto 🎉</string>
@@ -110,7 +102,6 @@
<string name="home_due_tomorrow">Amanhã</string> <string name="home_due_tomorrow">Amanhã</string>
<string name="home_search_hint">Buscar tarefas</string> <string name="home_search_hint">Buscar tarefas</string>
<string name="home_search_clear">Limpar buscas</string> <string name="home_search_clear">Limpar buscas</string>
<string name="home_search_close">Fechar busca</string>
<string name="home_search_empty">Nenhuma tarefa corresponde a \"%1$s\"</string> <string name="home_search_empty">Nenhuma tarefa corresponde a \"%1$s\"</string>
<string name="reminder_due_at">Termina em %1$s</string> <string name="reminder_due_at">Termina em %1$s</string>
<string name="reminder_channel_name">Lembretes de tarefas</string> <string name="reminder_channel_name">Lembretes de tarefas</string>
@@ -120,8 +111,6 @@
<string name="onboarding_permission_title">Permitir acesso às suas tarefas</string> <string name="onboarding_permission_title">Permitir acesso às suas tarefas</string>
<string name="onboarding_permission_body">O Agendula precisa de permissão para ler e salvar suas tarefas. É tudo o que ele pede.</string> <string name="onboarding_permission_body">O Agendula precisa de permissão para ler e salvar suas tarefas. É tudo o que ele pede.</string>
<string name="onboarding_permission_button">Conceder acesso às tarefas</string> <string name="onboarding_permission_button">Conceder acesso às tarefas</string>
<string name="onboarding_install_opentasks">Instalar OpenTasks</string>
<string name="onboarding_install_tasksorg">Instalar tasks.org</string>
<string name="reminder_onboarding_title">Nunca perca seus prazos</string> <string name="reminder_onboarding_title">Nunca perca seus prazos</string>
<string name="reminder_onboarding_body">Apps de tarefas não enviam lembretes sozinhos, então o Agendula os entrega para você. Ative para receber uma notificação de quando uma tarefa estiver no prazo.</string> <string name="reminder_onboarding_body">Apps de tarefas não enviam lembretes sozinhos, então o Agendula os entrega para você. Ative para receber uma notificação de quando uma tarefa estiver no prazo.</string>
<string name="reminder_onboarding_enable_button">Ativar lembretes</string> <string name="reminder_onboarding_enable_button">Ativar lembretes</string>
@@ -166,7 +155,6 @@
<string name="settings_exact_alarms">Na hora</string> <string name="settings_exact_alarms">Na hora</string>
<string name="settings_exact_alarms_allowed">Lembretes disparam na mesma hora</string> <string name="settings_exact_alarms_allowed">Lembretes disparam na mesma hora</string>
<string name="settings_exact_alarms_blocked">Bloqueado — toque para permitir lembretes na hora</string> <string name="settings_exact_alarms_blocked">Bloqueado — toque para permitir lembretes na hora</string>
<string name="settings_section_tasks">Tarefas</string>
<string name="settings_default_list">Lista padrão</string> <string name="settings_default_list">Lista padrão</string>
<string name="settings_default_list_first">Primeira lista disponível</string> <string name="settings_default_list_first">Primeira lista disponível</string>
<string name="settings_add_subtask_row">Mostrar uma linha \"adicionar subtarefa\"</string> <string name="settings_add_subtask_row">Mostrar uma linha \"adicionar subtarefa\"</string>
-1
View File
@@ -1,6 +1,5 @@
<resources> <resources>
<!-- Seed color: warm mauve. Material 3 derives Light & Dark schemes from this. --> <!-- Seed color: warm mauve. Material 3 derives Light & Dark schemes from this. -->
<color name="seed">#FF7A5C6B</color>
<!-- Adaptive icon background --> <!-- Adaptive icon background -->
<color name="ic_launcher_background">#FF7A5C6B</color> <color name="ic_launcher_background">#FF7A5C6B</color>
</resources> </resources>
-12
View File
@@ -1,6 +1,5 @@
<resources> <resources>
<string name="app_name">Agendula</string> <string name="app_name">Agendula</string>
<string name="app_tagline">A modern Material 3 Expressive task app.\nComing into bloom.</string>
<!-- Generic --> <!-- Generic -->
<string name="task_untitled">Untitled task</string> <string name="task_untitled">Untitled task</string>
@@ -21,9 +20,6 @@
<string name="edit">Edit</string> <string name="edit">Edit</string>
<!-- Task edit --> <!-- Task edit -->
<string name="edit_task_title">Edit task</string>
<string name="new_task_title">New task</string>
<string name="field_title">Title</string>
<string name="field_description">Description</string> <string name="field_description">Description</string>
<string name="edit_save_failed">Could not save. Try again.</string> <string name="edit_save_failed">Could not save. Try again.</string>
@@ -52,11 +48,8 @@
<string name="priority_high">High</string> <string name="priority_high">High</string>
<!-- Task detail --> <!-- Task detail -->
<string name="detail_mark_complete">Mark complete</string>
<string name="detail_mark_incomplete">Mark not complete</string>
<string name="detail_list">List</string> <string name="detail_list">List</string>
<string name="detail_due">Due</string> <string name="detail_due">Due</string>
<string name="detail_start">Starts</string>
<string name="detail_priority">Priority</string> <string name="detail_priority">Priority</string>
<string name="detail_progress">Progress</string> <string name="detail_progress">Progress</string>
<string name="detail_subtasks">Subtasks</string> <string name="detail_subtasks">Subtasks</string>
@@ -152,7 +145,6 @@
<string name="smart_overdue">Overdue</string> <string name="smart_overdue">Overdue</string>
<string name="smart_upcoming">Upcoming</string> <string name="smart_upcoming">Upcoming</string>
<string name="smart_all">All</string> <string name="smart_all">All</string>
<string name="open_count">%1$d open</string>
<!-- Home: Today progress hero --> <!-- Home: Today progress hero -->
<string name="home_today_progress">%1$d of %2$d done</string> <string name="home_today_progress">%1$d of %2$d done</string>
@@ -168,7 +160,6 @@
<!-- Home: search --> <!-- Home: search -->
<string name="home_search_hint">Search tasks</string> <string name="home_search_hint">Search tasks</string>
<string name="home_search_clear">Clear search</string> <string name="home_search_clear">Clear search</string>
<string name="home_search_close">Close search</string>
<string name="home_search_empty">No tasks match “%1$s”</string> <string name="home_search_empty">No tasks match “%1$s”</string>
<!-- Reminders --> <!-- Reminders -->
@@ -182,8 +173,6 @@
<string name="onboarding_permission_title">Allow access to your tasks</string> <string name="onboarding_permission_title">Allow access to your tasks</string>
<string name="onboarding_permission_body">Agendula needs permission to read and write your tasks. That\'s the only thing it ever asks for.</string> <string name="onboarding_permission_body">Agendula needs permission to read and write your tasks. That\'s the only thing it ever asks for.</string>
<string name="onboarding_permission_button">Grant task access</string> <string name="onboarding_permission_button">Grant task access</string>
<string name="onboarding_install_opentasks">Install OpenTasks</string>
<string name="onboarding_install_tasksorg">Install tasks.org</string>
<!-- Reminder onboarding (one-time, after the provider grant) --> <!-- Reminder onboarding (one-time, after the provider grant) -->
<string name="reminder_onboarding_title">Never miss what\'s due</string> <string name="reminder_onboarding_title">Never miss what\'s due</string>
@@ -238,7 +227,6 @@
<string name="settings_exact_alarms">Exact timing</string> <string name="settings_exact_alarms">Exact timing</string>
<string name="settings_exact_alarms_allowed">Reminders fire at the exact time</string> <string name="settings_exact_alarms_allowed">Reminders fire at the exact time</string>
<string name="settings_exact_alarms_blocked">Blocked — tap to allow exact reminders</string> <string name="settings_exact_alarms_blocked">Blocked — tap to allow exact reminders</string>
<string name="settings_section_tasks">Tasks</string>
<string name="settings_default_list">Default list</string> <string name="settings_default_list">Default list</string>
<string name="settings_default_list_first">First available list</string> <string name="settings_default_list_first">First available list</string>
<string name="settings_add_subtask_row">Show \"add a subtask\" row</string> <string name="settings_add_subtask_row">Show \"add a subtask\" row</string>
+28 -37
View File
@@ -1,53 +1,42 @@
[versions] [versions]
agp = "9.2.1" agp = "9.2.1"
kotlin = "2.3.21" kotlin = "2.3.21"
ksp = "2.3.9" ksp = "2.3.11"
hilt = "2.59.2" hilt = "2.60.1"
coreKtx = "1.19.0" coreKtx = "1.19.0"
appcompat = "1.7.1" appcompat = "1.7.1"
lifecycleRuntime = "2.10.0" lifecycleRuntime = "2.11.0"
activityCompose = "1.13.0" activityCompose = "1.13.0"
composeBom = "2026.05.01" composeBom = "2026.05.01"
# Material 3 Expressive APIs currently live only in the 1.5 alpha line. # Material 3 Expressive APIs currently live only in the 1.5 alpha line.
# Pin explicitly to override the BOM (which ships stable 1.4.0). # Pin explicitly to override the BOM (which ships stable 1.4.0).
# Re-evaluate when 1.5.0 stable lands. # Re-evaluate when 1.5.0 stable lands.
material3 = "1.5.0-alpha21" material3 = "1.5.0-alpha26"
datastore = "1.2.1" datastore = "1.2.1"
# Room — Agendula's own task store (docs/OWN-STORE.md). # Room — Agendula's own task store (docs/OWN-STORE.md).
room = "2.8.4" room = "2.8.4"
kotlinxSerialization = "1.8.1" kotlinxSerialization = "1.8.1"
# SAF directory writing for export/backup (DocumentFile). # SAF directory writing for export/backup (DocumentFile).
documentfile = "1.1.0" documentfile = "1.1.0"
junit = "6.1.0" junit = "6.1.3"
junitPlatform = "6.1.0" junitPlatform = "6.1.3"
truth = "1.4.5" truth = "1.4.5"
androidxJunit = "1.3.0" androidxJunit = "1.3.0"
espressoCore = "3.7.0" espressoCore = "3.7.0"
kotlinxDatetime = "0.7.0" kotlinxDatetime = "0.7.0"
kotlinxCoroutines = "1.10.2" kotlinxCoroutines = "1.10.2"
turbine = "1.2.0" turbine = "1.2.0"
hiltNavigationCompose = "1.3.0" hiltNavigationCompose = "1.4.0"
navigationCompose = "2.9.0" navigationCompose = "2.9.0"
lifecycleCompose = "2.10.0" lifecycleCompose = "2.11.0"
androidxTestRules = "1.7.0" androidxTestRules = "1.7.0"
# Glance: 1.1.1 is the latest stable (1.2.0 is still rc, 1.3.0 alpha).
glance = "1.1.1"
# --- :provider (vendored dmfs task provider) --------------------------------- # RFC 5545 recurrence expansion. The last of the dmfs dependencies: the vendored
# Versions the upstream 1.4.2 source was written against. These are its runtime # `:provider` module that needed the rest was deleted (docs/STORAGE-DECISION.md),
# dependencies, not ours — nothing above the data layer touches them, and they # and lib-recur stayed because our own store expands a series in process.
# only move when we deliberately resync the fork. See provider/PROVENANCE.md. # Pinned at 0.12.2 — 0.16.0 removed RecurrenceSet. rfc5545-datetime arrives with
dmfsJems = "1.43" # it as part of its API surface, so it is not declared separately.
dmfsRfc5545Datetime = "0.2.4"
dmfsLibRecur = "0.12.2" dmfsLibRecur = "0.12.2"
# The provider's own test suite is JUnit 4 + Robolectric, unlike the app's
# JUnit 5. Kept as upstream wrote it (rewriting ~13 test classes would forfeit
# the regression coverage that makes vendoring safe), but on current versions:
# upstream pins Robolectric 3.5.1, which predates AGP's resource handling.
robolectric = "4.16"
junit4 = "4.13.2"
hamcrest = "3.0"
mockito = "5.20.0"
[libraries] [libraries]
# AndroidX core # AndroidX core
@@ -107,8 +96,11 @@ kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-cor
# Test - Flow assertions # Test - Flow assertions
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" } turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
# Hilt navigation-compose (for hiltViewModel() in Composables) # Hilt navigation-compose (hiltViewModel() moved out of it in 1.4.0, into
# hilt-lifecycle-viewmodel-compose — which it still brings in transitively, but
# we call that API directly, so it is declared here rather than inherited)
androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
androidx-hilt-lifecycle-viewmodel-compose = { group = "androidx.hilt", name = "hilt-lifecycle-viewmodel-compose", version.ref = "hiltNavigationCompose" }
# Navigation-compose (the NavHost / back stack) # Navigation-compose (the NavHost / back stack)
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
@@ -118,22 +110,21 @@ androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lif
# ProcessLifecycleOwner — the WAL checkpoint hangs off ON_STOP. # ProcessLifecycleOwner — the WAL checkpoint hangs off ON_STOP.
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleRuntime" } androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleRuntime" }
# Glance — Jetpack home-screen widgets (Compose-like RemoteViews)
androidx-glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
androidx-glance-material3 = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }
# Android tests - GrantPermissionRule # Android tests - GrantPermissionRule
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" } androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
# :provider — vendored dmfs task provider (see provider/PROVENANCE.md) # Recurrence expansion for our own store
dmfs-jems = { group = "org.dmfs", name = "jems", version.ref = "dmfsJems" }
dmfs-jems-testing = { group = "org.dmfs", name = "jems-testing", version.ref = "dmfsJems" }
dmfs-rfc5545-datetime = { group = "org.dmfs", name = "rfc5545-datetime", version.ref = "dmfsRfc5545Datetime" }
dmfs-lib-recur = { group = "org.dmfs", name = "lib-recur", version.ref = "dmfsLibRecur" } dmfs-lib-recur = { group = "org.dmfs", name = "lib-recur", version.ref = "dmfsLibRecur" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
junit4 = { group = "junit", name = "junit", version.ref = "junit4" } # floret-kit — the shared house library, an included build (see settings.gradle.kts).
hamcrest = { group = "org.hamcrest", name = "hamcrest", version.ref = "hamcrest" } # Deliberately version-less: the composite build substitutes these coordinates
mockito-core = { group = "org.mockito", name = "mockito-core", version.ref = "mockito" } # with the submodule's own projects, so a version here would be fiction.
floret-core-time = { group = "de.jeanlucmakiola.floret", name = "core-time" }
floret-core-reminders = { group = "de.jeanlucmakiola.floret", name = "core-reminders" }
floret-core-locale = { group = "de.jeanlucmakiola.floret", name = "core-locale" }
floret-core-crash = { group = "de.jeanlucmakiola.floret", name = "core-crash" }
floret-identity = { group = "de.jeanlucmakiola.floret", name = "identity" }
floret-components = { group = "de.jeanlucmakiola.floret", name = "components" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }