Files
agendula/gradle/libs.versions.toml
T
Jean-Luc Makiolaandmakiolaj aa2aade930
Beta — Codeberg pre-release / detect (push) Successful in 6s
Beta — Codeberg pre-release / beta (push) Skipped
feat: tags (#35) (#46)
Adds tags, stored as the standard `CATEGORIES` property so they stay compatible with Nextcloud Tasks, tasks.org and DAVx⁵.

## What changed

- **Storage and sync:** tags are a new `categories` column (database v2). The migration lifts `CATEGORIES` out of the stored unknown properties for tasks that were synced before, so existing tags show up without a re-download. A line Agendula can't write back exactly as it came (e.g. a duplicated value, or the property split across several lines) stays where it was and still syncs byte for byte.
- **Provider mode (OpenTasks / tasks.org):** tags are read and written as the provider's category properties. Writes are a single batch, so a refused tag can't leave half the tags behind or create a task twice.
- **Imports:** the copy from OpenTasks/tasks.org and the legacy one-shot import both bring tags across.
- **Editor:** a new optional Tags field. Type a tag and press Done or type a comma; tags already in use are suggested while typing, and text left in the field is still saved.
- **Display:** tags show as pills on the detail screen and in each row's info line in task lists.
- **Using tags:** the home screen has a Tags section listing every tag on an open task with its count. Tapping one opens that tag's tasks across all lists, and "New task" there starts with the tag set. Every task list's sort button is now a "Sort and filter" picker, where you can tick one or more tags to narrow the list.

## Also in here

- **Keyboard:** a focused input now scrolls fully above the keyboard, not just its text line. This uses floret-kit's new `revealOnFocus` (jlmakiola/floret-kit#12), and the pointer moves to floret-kit main.
- **Edit screen:** once the title scrolls out of view, it shows in the top bar.
- **main merged in:** the branch brings main's #40 and #44 (floret-kit on its shared main, Gradle 9.8), which `release/v1.1.0` doesn't have yet.

## Where this differs from the issue

- **Filtering by several tags** shows tasks that carry *all* of them, since ticking more tags is meant to narrow the list.
- **Tags are case-sensitive:** "Work" and "work" are two tags, matching how they are stored and synced.
- **The overview** lists tags on open tasks only, the same set the editor suggests.
- **Widgets** can't be set to a tag yet.

Closes #35

Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/46
2026-10-09 12:20:21 +02:00

188 lines
10 KiB
TOML

[versions]
agp = "9.4.1"
kotlin = "2.4.20"
ksp = "2.3.11"
hilt = "2.60.1"
coreKtx = "1.19.0"
appcompat = "1.7.1"
lifecycleRuntime = "2.11.0"
activityCompose = "1.13.0"
composeBom = "2026.05.01"
# 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).
# Re-evaluate when 1.5.0 stable lands.
material3 = "1.5.0-alpha26"
datastore = "1.2.1"
# Glance: kept on the same version as Calendula's widgets.
glance = "1.2.0"
# Room — Agendula's own task store.
room = "2.8.4"
kotlinxSerialization = "1.8.1"
# SAF directory writing for export/backup (DocumentFile).
documentfile = "1.1.0"
junit = "6.1.3"
junitPlatform = "6.1.3"
truth = "1.4.5"
androidxJunit = "1.3.0"
espressoCore = "3.7.0"
kotlinxDatetime = "0.7.0"
kotlinxCoroutines = "1.10.2"
turbine = "1.2.0"
# androidx.hilt — one version for the whole group. hilt-work and
# hilt-navigation-compose splitting versions inside a group is the kind of thing
# that resolves fine and then fails at runtime.
androidxHilt = "1.4.0"
# WorkManager: sync runs here, triggered *through* the sync-adapter framework.
work = "2.11.2"
# Custom Tabs, for Nextcloud Login Flow v2.
browser = "1.10.0"
navigationCompose = "2.9.0"
lifecycleCompose = "2.11.0"
androidxTestRules = "1.7.0"
# --- :dav (vendored dav4jvm) -------------------------------------------------
# dav4jvm 2.2.1 is the last OkHttp release; 3.0.0 deleted the OkHttp package for
# Ktor and 4.x additionally requires Java 21 bytecode, which we do not target.
# See dav/PROVENANCE.md. OkHttp 4.12.0 is what the vendored tree was written
# against and what DAVx5 ships.
okhttp = "4.12.0"
# org.xmlpull.v1 ships in the Android framework, so xpp3 is compileOnly +
# testImplementation and never reaches the APK.
xpp3 = "1.1.6"
# --- :caldav (our CalDAV protocol layer) -------------------------------------
# SRV/TXT lookups for RFC 6764 discovery. Android's resolver exposes no SRV API
# below API 29's DnsResolver (which is callback-only and cannot do TXT paths),
# and JNDI's DNS provider does not exist on Android at all. dnsjava is what DAVx5
# uses. BSD-3 — it goes on the attribution screen with the rest.
dnsjava = "3.6.3"
# WebDAV-Push delivery. The connector only: DAVx5 also ships the embedded FCM
# distributor, which pulls in Play Services and would get us flagged on F-Droid.
unifiedpush = "3.3.5"
# The vendored dav4jvm tests and :caldav's are JUnit 4, unlike the app's JUnit 5.
junit4 = "4.13.2"
# RFC 5545 recurrence expansion. The last of the dmfs dependencies: the vendored
# `:provider` module that needed the rest was deleted,
# and lib-recur stayed because our own store expands a series in process.
# Pinned at 0.12.2 — 0.16.0 removed RecurrenceSet. rfc5545-datetime arrives with
# it as part of its API surface, so it is not declared separately.
dmfsLibRecur = "0.12.2"
[libraries]
# AndroidX core
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntime" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
# Compose BOM + libs
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
# Material 3 (Expressive lives in this artifact for 1.5+)
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
androidx-compose-material-icons-core = { group = "androidx.compose.material", name = "material-icons-core" }
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
# Hilt
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" }
# Room — the own-store database
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
androidx-room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
# Glance — the home-screen task widget
androidx-glance-appwidget = { group = "androidx.glance", name = "glance-appwidget", version.ref = "glance" }
androidx-glance-material3 = { group = "androidx.glance", name = "glance-material3", version.ref = "glance" }
# DataStore
androidx-datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
# SAF — writing the export into a user-chosen folder
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "documentfile" }
# Unit tests
junit-jupiter-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" }
junit-jupiter-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine", version.ref = "junit" }
junit-platform-launcher = { group = "org.junit.platform", name = "junit-platform-launcher", version.ref = "junitPlatform" }
truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }
# Android tests
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
# Domain time
kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" }
# Coroutines (transitively pulled by hilt-android, pinned explicit)
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
# Test - Flow assertions
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
# 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 = "androidxHilt" }
androidx-hilt-lifecycle-viewmodel-compose = { group = "androidx.hilt", name = "hilt-lifecycle-viewmodel-compose", version.ref = "androidxHilt" }
androidx-hilt-work = { group = "androidx.hilt", name = "hilt-work", version.ref = "androidxHilt" }
androidx-hilt-compiler = { group = "androidx.hilt", name = "hilt-compiler", version.ref = "androidxHilt" }
androidx-work-runtime-ktx = { group = "androidx.work", name = "work-runtime-ktx", version.ref = "work" }
androidx-browser = { group = "androidx.browser", name = "browser", version.ref = "browser" }
# Navigation-compose (the NavHost / back stack)
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
# Lifecycle compose (for collectAsStateWithLifecycle)
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleCompose" }
# ProcessLifecycleOwner — the WAL checkpoint hangs off ON_STOP.
androidx-lifecycle-process = { group = "androidx.lifecycle", name = "lifecycle-process", version.ref = "lifecycleRuntime" }
# Android tests - GrantPermissionRule
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "androidxTestRules" }
unifiedpush-connector = { group = "org.unifiedpush.android", name = "connector", version.ref = "unifiedpush" }
# :dav — vendored dav4jvm (see dav/PROVENANCE.md)
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
okhttp-mockwebserver = { group = "com.squareup.okhttp3", name = "mockwebserver", version.ref = "okhttp" }
# Test-only: MockWebServer over TLS, so an HTTPS-only code path can be exercised.
okhttp-tls = { group = "com.squareup.okhttp3", name = "okhttp-tls", version.ref = "okhttp" }
xpp3 = { group = "org.ogce", name = "xpp3", version.ref = "xpp3" }
# :caldav — RFC 6764 service discovery
dnsjava = { group = "dnsjava", name = "dnsjava", version.ref = "dnsjava" }
junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
# Recurrence expansion for our own store
dmfs-lib-recur = { group = "org.dmfs", name = "lib-recur", version.ref = "dmfsLibRecur" }
# floret-kit — the shared house library, an included build (see settings.gradle.kts).
# Deliberately version-less: the composite build substitutes these coordinates
# 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" }
floret-glance = { group = "de.jeanlucmakiola.floret", name = "glance" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }