Rename app Floret → Agendula
All checks were successful
CI / ci (push) Successful in 8m33s

Floret is promoted to the family / design-language (shared-kit) name; the
tasks app itself becomes Agendula (de.jeanlucmakiola.agendula) — agenda
('things to be done') + Calendula's -ula, a twin of the Calendula name.

Renames the package, namespace, applicationId, rootProject.name, app_name,
FloretApp/FloretNavHost/FloretTransitions classes, theme, F-Droid metadata
dir, CI artifact name, and docs. The botanical word 'florets' is preserved in
the name-origin prose, which is rewritten to Agendula's etymology. Clean
build + unit tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-27 23:10:24 +02:00
parent d84ac60757
commit 76a0139fda
93 changed files with 408 additions and 403 deletions

View File

@@ -225,7 +225,7 @@ jobs:
if [ -z "$SAFE_REF_NAME" ]; then if [ -z "$SAFE_REF_NAME" ]; then
SAFE_REF_NAME="${GITHUB_SHA:-manual}" SAFE_REF_NAME="${GITHUB_SHA:-manual}"
fi fi
cp app/build/outputs/apk/release/app-release.apk "fdroid/repo/floret_${SAFE_REF_NAME}.apk" cp app/build/outputs/apk/release/app-release.apk "fdroid/repo/agendula_${SAFE_REF_NAME}.apk"
- name: Copy metadata to F-Droid repo - name: Copy metadata to F-Droid repo
run: | run: |
@@ -248,7 +248,7 @@ jobs:
if [ ! -s /tmp/changelog.txt ]; then if [ ! -s /tmp/changelog.txt ]; then
echo "See CHANGELOG.md for $VERSION." > /tmp/changelog.txt echo "See CHANGELOG.md for $VERSION." > /tmp/changelog.txt
fi fi
CL_DIR="fdroid/metadata/de.jeanlucmakiola.floret/en-US/changelogs" CL_DIR="fdroid/metadata/de.jeanlucmakiola.agendula/en-US/changelogs"
mkdir -p "$CL_DIR" mkdir -p "$CL_DIR"
cp /tmp/changelog.txt "$CL_DIR/${VERSION_CODE}.txt" cp /tmp/changelog.txt "$CL_DIR/${VERSION_CODE}.txt"
echo "Wrote $CL_DIR/${VERSION_CODE}.txt" echo "Wrote $CL_DIR/${VERSION_CODE}.txt"

View File

@@ -8,7 +8,7 @@ the source of truth for version codes (see Calendula's `docs/RELEASING.md`).
### Added ### Added
- M5 reminders onboarding & polish: a one-time reminder onboarding step after the - M5 reminders onboarding & polish: a one-time reminder onboarding step after the
provider grant explains that Floret delivers due reminders itself and requests provider grant explains that Agendula delivers due reminders itself and requests
`POST_NOTIFICATIONS` (API 33+). A new **Settings** screen (gear on the lists `POST_NOTIFICATIONS` (API 33+). A new **Settings** screen (gear on the lists
overview), structured after Calendula as a category hub with sliding sub-screens overview), structured after Calendula as a category hub with sliding sub-screens
(`CollapsingScaffold` + grouped category rows + full-screen `OptionPicker`): (`CollapsingScaffold` + grouped category rows + full-screen `OptionPicker`):

View File

@@ -1,6 +1,6 @@
# Contributing to Floret # Contributing to Agendula
Thanks for your interest in Floret — a Material 3 Expressive task app that's a Thanks for your interest in Agendula — a Material 3 Expressive task app that's a
pure front-end over the OpenTasks `TaskContract` provider, with no own database pure front-end over the OpenTasks `TaskContract` provider, with no own database
or sync stack. Before diving in, skim [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) or sync stack. Before diving in, skim [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)
(how it's built), [`docs/ROADMAP.md`](docs/ROADMAP.md) (what's next), and (how it's built), [`docs/ROADMAP.md`](docs/ROADMAP.md) (what's next), and
@@ -80,7 +80,7 @@ push, so run these locally before opening a PR. Keep CI green.
## Scope ## Scope
Floret stays true to its thesis: a front-end over **open** task backends Agendula stays true to its thesis: a front-end over **open** task backends
(CalDAV / iCalendar / DecSync via the OpenTasks provider). Proprietary backends (CalDAV / iCalendar / DecSync via the OpenTasks provider). Proprietary backends
(Google Tasks, Microsoft To Do) are out of scope by design — they'd mean owning (Google Tasks, Microsoft To Do) are out of scope by design — they'd mean owning
a sync stack. v1 targets the OpenTasks contract (OpenTasks + tasks.org); jtx's a sync stack. v1 targets the OpenTasks contract (OpenTasks + tasks.org); jtx's

View File

@@ -1,6 +1,6 @@
<div align="center"> <div align="center">
<h1>Floret</h1> <h1>Agendula</h1>
<p><strong>A modern Material 3 Expressive task app for Android.</strong><br> <p><strong>A modern Material 3 Expressive task app for Android.</strong><br>
Reads, writes, and reminds — on top of an existing tasks provider, with no own Reads, writes, and reminds — on top of an existing tasks provider, with no own
@@ -13,14 +13,16 @@ sync stack.</p>
</div> </div>
Floret is the task-list sibling to [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula). Agendula is the task-list sibling to [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula).
Where Calendula is a pure front-end over Android's `CalendarContract`, Floret is Where Calendula is a pure front-end over Android's `CalendarContract`, Agendula is
a pure front-end over the **OpenTasks `TaskContract` provider** — the store that a pure front-end over the **OpenTasks `TaskContract` provider** — the store that
DAVx5 (and SmoothSync, DecSync, …) syncs your CalDAV `VTODO` tasks into. No own DAVx5 (and SmoothSync, DecSync, …) syncs your CalDAV `VTODO` tasks into. No own
database, no reinvented sync. database, no reinvented sync.
A Calendula flower head is botanically made of many small *florets* — the The name rhymes with its sibling on purpose: **Agendula** is *agenda* — Latin for
individual items that make up the bloom. Floret is those items: your tasks. “things to be done” — given Calendula's `-ula` ending. Calendula keeps your days;
Agendula keeps your to-dos. (A Calendula flower head is botanically a cluster of
many small *florets* — so the two apps are florets of one bloom.)
> **Status: data layer done, UI in progress.** The full non-visual stack over > **Status: data layer done, UI in progress.** The full non-visual stack over
> the `TaskContract` provider — provider resolution, live-updating reads, > the `TaskContract` provider — provider resolution, live-updating reads,
@@ -32,7 +34,7 @@ individual items that make up the bloom. Floret is those items: your tasks.
## Sync sources (by design) ## Sync sources (by design)
Floret works with anything that writes to the tasks provider — **DAVx5** Agendula works with anything that writes to the tasks provider — **DAVx5**
(CalDAV), **SmoothSync**, **CalDAV-Sync**, **DecSync CC**, or any Android sync (CalDAV), **SmoothSync**, **CalDAV-Sync**, **DecSync CC**, or any Android sync
adapter — because it builds on the provider, not on any one sync app. Google adapter — because it builds on the provider, not on any one sync app. Google
Tasks / Microsoft To Do are out of scope by design (proprietary; they would mean Tasks / Microsoft To Do are out of scope by design (proprietary; they would mean

View File

@@ -16,11 +16,11 @@ val keystoreProperties = Properties().apply {
} }
android { android {
namespace = "de.jeanlucmakiola.floret" namespace = "de.jeanlucmakiola.agendula"
compileSdk = 37 compileSdk = 37
defaultConfig { defaultConfig {
applicationId = "de.jeanlucmakiola.floret" applicationId = "de.jeanlucmakiola.agendula"
minSdk = 29 minSdk = 29
targetSdk = 36 targetSdk = 36
// The git tag is the single source of truth for released builds: at // The git tag is the single source of truth for released builds: at

View File

@@ -30,7 +30,7 @@
</queries> </queries>
<application <application
android:name=".FloretApp" android:name=".AgendulaApp"
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules" android:fullBackupContent="@xml/backup_rules"
@@ -39,7 +39,7 @@
android:localeConfig="@xml/locales_config" android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.Floret" android:theme="@style/Theme.Agendula"
tools:targetApi="35"> tools:targetApi="35">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret package de.jeanlucmakiola.agendula
import android.app.Application import android.app.Application
import dagger.hilt.EntryPoint import dagger.hilt.EntryPoint
@@ -6,19 +6,19 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.android.HiltAndroidApp import dagger.hilt.android.HiltAndroidApp
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import de.jeanlucmakiola.floret.data.reminders.ReminderScheduler import de.jeanlucmakiola.agendula.data.reminders.ReminderScheduler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/** /**
* Application entry point. Registered as android:name=".FloretApp". Besides * Application entry point. Registered as android:name=".AgendulaApp". Besides
* Hilt init, it kicks off a reminder re-sync on launch (independent of any UI), * Hilt init, it kicks off a reminder re-sync on launch (independent of any UI),
* so alarms reflect tasks synced while the app was closed. * so alarms reflect tasks synced while the app was closed.
*/ */
@HiltAndroidApp @HiltAndroidApp
class FloretApp : Application() { class AgendulaApp : Application() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret package de.jeanlucmakiola.agendula
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@@ -14,11 +14,11 @@ import androidx.hilt.navigation.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
import de.jeanlucmakiola.floret.data.demo.DemoSeeder import de.jeanlucmakiola.agendula.data.demo.DemoSeeder
import de.jeanlucmakiola.floret.data.prefs.ThemeMode import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
import de.jeanlucmakiola.floret.ui.RootScreen import de.jeanlucmakiola.agendula.ui.RootScreen
import de.jeanlucmakiola.floret.ui.settings.SettingsViewModel import de.jeanlucmakiola.agendula.ui.settings.SettingsViewModel
import de.jeanlucmakiola.floret.ui.theme.FloretTheme import de.jeanlucmakiola.agendula.ui.theme.AgendulaTheme
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@@ -36,7 +36,7 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
enableEdgeToEdge() enableEdgeToEdge()
// Debug-only sample data: `am start ... --ez floret_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.seed() } }
@@ -49,15 +49,15 @@ class MainActivity : ComponentActivity() {
ThemeMode.LIGHT -> false ThemeMode.LIGHT -> false
ThemeMode.DARK -> true ThemeMode.DARK -> true
} }
FloretTheme(darkTheme = darkTheme, dynamicColor = ui.settings.dynamicColor) { AgendulaTheme(darkTheme = darkTheme, dynamicColor = ui.settings.dynamicColor) {
RootScreen(modifier = Modifier.fillMaxSize()) RootScreen(modifier = Modifier.fillMaxSize())
} }
} }
} }
companion object { companion object {
const val EXTRA_TASK_ID = "de.jeanlucmakiola.floret.extra.TASK_ID" const val EXTRA_TASK_ID = "de.jeanlucmakiola.agendula.extra.TASK_ID"
private const val EXTRA_SEED = "floret_seed" private const val EXTRA_SEED = "agendula_seed"
/** Opens the app focused on a task (reminder taps). Routing lands with the UI. */ /** Opens the app focused on a task (reminder taps). Routing lands with the UI. */
fun taskIntent(context: Context, taskId: Long): Intent = fun taskIntent(context: Context, taskId: Long): Intent =

View File

@@ -1,10 +1,10 @@
package de.jeanlucmakiola.floret.data.demo package de.jeanlucmakiola.agendula.data.demo
import de.jeanlucmakiola.floret.data.di.IoDispatcher import de.jeanlucmakiola.agendula.data.di.IoDispatcher
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.DayWindow import de.jeanlucmakiola.agendula.domain.DayWindow
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -16,10 +16,10 @@ import kotlin.time.Instant
/** /**
* Debug-only one-shot sample data. Creates a **local, device-only** list * Debug-only one-shot sample data. Creates a **local, device-only** list
* ("Floret Demo") so nothing syncs to a CalDAV server and fills it with * ("Agendula Demo") so nothing syncs to a CalDAV server and fills it with
* tasks spread across overdue / today / upcoming / no-date / completed so every * tasks spread across overdue / today / upcoming / no-date / completed so every
* smart list shows content. Idempotent: skips if the demo list already exists. * smart list shows content. Idempotent: skips if the demo list already exists.
* Triggered from [de.jeanlucmakiola.floret.MainActivity] only in debug builds. * Triggered from [de.jeanlucmakiola.agendula.MainActivity] only in debug builds.
*/ */
@Singleton @Singleton
class DemoSeeder @Inject constructor( class DemoSeeder @Inject constructor(
@@ -47,14 +47,14 @@ class DemoSeeder @Inject constructor(
repository.createTask(TaskForm(title = "Gather receipts", listId = listId, parentId = invoiceId)) repository.createTask(TaskForm(title = "Gather receipts", listId = listId, parentId = invoiceId))
repository.createTask(TaskForm(title = "Book train tickets", listId = listId, due = at(te + 6 * day), priority = Priority.LOW)) repository.createTask(TaskForm(title = "Book train tickets", listId = listId, due = at(te + 6 * day), priority = Priority.LOW))
repository.createTask(TaskForm(title = "Read Compose 1.5 release notes", listId = listId)) repository.createTask(TaskForm(title = "Read Compose 1.5 release notes", listId = listId))
repository.createTask(TaskForm(title = "Sketch the Floret app icon", listId = listId)) repository.createTask(TaskForm(title = "Sketch the Agendula app icon", listId = listId))
val done = repository.createTask(TaskForm(title = "Renew domain name", listId = listId, due = at(ts - 2 * day))) val done = repository.createTask(TaskForm(title = "Renew domain name", listId = listId, due = at(ts - 2 * day)))
repository.setCompleted(done, completed = true) repository.setCompleted(done, completed = true)
} }
private companion object { private companion object {
const val DEMO_LIST = "Floret Demo" const val DEMO_LIST = "Agendula Demo"
const val DEMO_COLOR = 0xFF7A5C6B.toInt() const val DEMO_COLOR = 0xFF7A5C6B.toInt()
} }
} }

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.di package de.jeanlucmakiola.agendula.data.di
import android.content.Context import android.content.Context
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
@@ -10,16 +10,16 @@ import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import de.jeanlucmakiola.floret.data.tasks.AndroidTasksDataSource import de.jeanlucmakiola.agendula.data.tasks.AndroidTasksDataSource
import de.jeanlucmakiola.floret.data.tasks.TasksDataSource import de.jeanlucmakiola.agendula.data.tasks.TasksDataSource
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.data.tasks.TasksRepositoryImpl import de.jeanlucmakiola.agendula.data.tasks.TasksRepositoryImpl
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import javax.inject.Singleton import javax.inject.Singleton
private val Context.floretDataStore: DataStore<Preferences> by preferencesDataStore( private val Context.agendulaDataStore: DataStore<Preferences> by preferencesDataStore(
name = "floret_prefs", name = "agendula_prefs",
) )
@Module @Module
@@ -42,7 +42,7 @@ object DataProvideModule {
@Provides @Provides
@Singleton @Singleton
fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> = fun provideDataStore(@ApplicationContext context: Context): DataStore<Preferences> =
context.floretDataStore context.agendulaDataStore
@Provides @Provides
@IoDispatcher @IoDispatcher

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.di package de.jeanlucmakiola.agendula.data.di
import javax.inject.Qualifier import javax.inject.Qualifier

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.prefs package de.jeanlucmakiola.agendula.data.prefs
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
@@ -8,7 +8,7 @@ import androidx.datastore.preferences.core.intPreferencesKey
import androidx.datastore.preferences.core.longPreferencesKey import androidx.datastore.preferences.core.longPreferencesKey
import androidx.datastore.preferences.core.stringPreferencesKey import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.core.stringSetPreferencesKey import androidx.datastore.preferences.core.stringSetPreferencesKey
import de.jeanlucmakiola.floret.domain.TaskFormField import de.jeanlucmakiola.agendula.domain.TaskFormField
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import javax.inject.Inject import javax.inject.Inject

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context

View File

@@ -1,11 +1,11 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.floret.data.tasks.TasksDataSource import de.jeanlucmakiola.agendula.data.tasks.TasksDataSource
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@@ -43,7 +43,7 @@ class DueReminderReceiver : BroadcastReceiver() {
} }
companion object { companion object {
private const val EXTRA_TASK_ID = "de.jeanlucmakiola.floret.extra.TASK_ID" private const val EXTRA_TASK_ID = "de.jeanlucmakiola.agendula.extra.TASK_ID"
fun intent(context: Context, taskId: Long): Intent = fun intent(context: Context, taskId: Long): Intent =
Intent(context, DueReminderReceiver::class.java).putExtra(EXTRA_TASK_ID, taskId) Intent(context, DueReminderReceiver::class.java).putExtra(EXTRA_TASK_ID, taskId)

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
@@ -12,7 +12,7 @@ import javax.inject.Inject
/** /**
* Re-syncs reminders whenever the tasks provider changes covers external sync * Re-syncs reminders whenever the tasks provider changes covers external sync
* (DAVx5 pulling new/edited tasks) while Floret isn't in the foreground. The * (DAVx5 pulling new/edited tasks) while Agendula isn't in the foreground. The
* manifest filter targets both known authorities; best-effort (the in-app * manifest filter targets both known authorities; best-effort (the in-app
* ContentObserver covers the foreground case regardless). * ContentObserver covers the foreground case regardless).
*/ */

View File

@@ -1,15 +1,15 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import android.app.AlarmManager import android.app.AlarmManager
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.jeanlucmakiola.floret.data.di.IoDispatcher import de.jeanlucmakiola.agendula.data.di.IoDispatcher
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.floret.data.tasks.ProviderResolver import de.jeanlucmakiola.agendula.data.tasks.ProviderResolver
import de.jeanlucmakiola.floret.data.tasks.TaskQuery import de.jeanlucmakiola.agendula.data.tasks.TaskQuery
import de.jeanlucmakiola.floret.data.tasks.TasksDataSource import de.jeanlucmakiola.agendula.data.tasks.TasksDataSource
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -18,7 +18,7 @@ import javax.inject.Singleton
/** /**
* The self-scheduled due-reminder engine. Tasks providers don't deliver * The self-scheduled due-reminder engine. Tasks providers don't deliver
* reminders, so Floret reads upcoming due tasks and arms one exact [AlarmManager] * reminders, so Agendula reads upcoming due tasks and arms one exact [AlarmManager]
* alarm each, within a rolling window. Re-run on app start, boot and provider * alarm each, within a rolling window. Re-run on app start, boot and provider
* change; it diffs against [ScheduledReminderStore] so only changed alarms move. * change; it diffs against [ScheduledReminderStore] so only changed alarms move.
*/ */

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.reminders package de.jeanlucmakiola.agendula.data.reminders
import android.Manifest import android.Manifest
import android.annotation.SuppressLint import android.annotation.SuppressLint
@@ -12,9 +12,9 @@ import android.os.Build
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import de.jeanlucmakiola.floret.MainActivity import de.jeanlucmakiola.agendula.MainActivity
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import java.time.Instant as JInstant import java.time.Instant as JInstant
import java.time.ZoneId import java.time.ZoneId

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import android.content.ContentResolver import android.content.ContentResolver
import android.content.ContentUris import android.content.ContentUris
@@ -9,12 +9,12 @@ import android.net.Uri
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Instances import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Instances
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import java.time.ZoneId import java.time.ZoneId
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import android.database.Cursor import android.database.Cursor

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
/** No tasks provider (OpenTasks / tasks.org) is installed on the device. */ /** No tasks provider (OpenTasks / tasks.org) is installed on the device. */
class ProviderUnavailableException : class ProviderUnavailableException :

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
@@ -8,7 +8,7 @@ import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
/** /**
* A tasks provider Floret can talk to. The same dmfs `TaskProvider` backs every * A tasks provider Agendula can talk to. The same dmfs `TaskProvider` backs every
* candidate, so the [TasksContract] columns apply regardless of which is present. * candidate, so the [TasksContract] columns apply regardless of which is present.
*/ */
data class TaskProvider( data class TaskProvider(

View File

@@ -1,12 +1,12 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Instances import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Instances
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import de.jeanlucmakiola.floret.domain.priorityFromICal import de.jeanlucmakiola.agendula.domain.priorityFromICal
import de.jeanlucmakiola.floret.domain.statusFromInt import de.jeanlucmakiola.agendula.domain.statusFromInt
import kotlin.time.Instant import kotlin.time.Instant
/** Maps a provider row (via [ColumnReader]) to a domain model. Pure + testable. */ /** Maps a provider row (via [ColumnReader]) to a domain model. Pure + testable. */

View File

@@ -1,8 +1,8 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Instances import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Instances
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
/** Column lists requested from the provider. Order is irrelevant; we read by name. */ /** Column lists requested from the provider. Order is irrelevant; we read by name. */
object TaskProjections { object TaskProjections {

View File

@@ -1,9 +1,9 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.toICal import de.jeanlucmakiola.agendula.domain.toICal
/** /**
* Turns a [TaskForm] / mutation into a namevalue map. Pure (no ContentValues), * Turns a [TaskForm] / mutation into a namevalue map. Pure (no ContentValues),

View File

@@ -1,9 +1,9 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import android.net.Uri import android.net.Uri
/** /**
* The subset of the OpenTasks `TaskContract` that Floret uses, vendored as * The subset of the OpenTasks `TaskContract` that Agendula uses, vendored as
* Kotlin constants. Derived from `org.dmfs.tasks.contract.TaskContract` * Kotlin constants. Derived from `org.dmfs.tasks.contract.TaskContract`
* (Apache-2.0, dmfs GmbH): * (Apache-2.0, dmfs GmbH):
* https://github.com/dmfs/opentasks/blob/master/opentasks-contract/src/main/java/org/dmfs/tasks/contract/TaskContract.java * https://github.com/dmfs/opentasks/blob/master/opentasks-contract/src/main/java/org/dmfs/tasks/contract/TaskContract.java
@@ -25,7 +25,7 @@ object TasksContract {
const val PARAM_ACCOUNT_TYPE = "account_type" const val PARAM_ACCOUNT_TYPE = "account_type"
const val LOAD_PROPERTIES = "load_properties" const val LOAD_PROPERTIES = "load_properties"
/** Account used for local, device-only task lists Floret owns. */ /** Account used for local, device-only task lists Agendula owns. */
const val LOCAL_ACCOUNT_NAME = "Local" const val LOCAL_ACCOUNT_NAME = "Local"
const val LOCAL_ACCOUNT_TYPE = "org.dmfs.account.LOCAL" const val LOCAL_ACCOUNT_TYPE = "org.dmfs.account.LOCAL"
@@ -85,7 +85,7 @@ object TasksContract {
* The `instances` view one row per task occurrence, with recurrence * The `instances` view one row per task occurrence, with recurrence
* resolved and absolute [INSTANCE_START]/[INSTANCE_DUE] timestamps. Inherits * resolved and absolute [INSTANCE_START]/[INSTANCE_DUE] timestamps. Inherits
* every [Tasks] column (title, status, ) plus the instance-only columns * every [Tasks] column (title, status, ) plus the instance-only columns
* below. This is Floret's read source. * below. This is Agendula's read source.
*/ */
object Instances { object Instances {
const val PATH = "instances" const val PATH = "instances"

View File

@@ -1,8 +1,8 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
/** What to fetch from the provider. Smart-list date logic is applied above this. */ /** What to fetch from the provider. Smart-list date logic is applied above this. */
data class TaskQuery( data class TaskQuery(

View File

@@ -1,14 +1,14 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskDetail import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlin.time.Instant import kotlin.time.Instant
/** Whether Floret can use the tasks provider right now. Drives onboarding. */ /** Whether Agendula can use the tasks provider right now. Drives onboarding. */
enum class ProviderStatus { READY, NEEDS_PERMISSION, NO_PROVIDER } enum class ProviderStatus { READY, NEEDS_PERMISSION, NO_PROVIDER }
/** /**

View File

@@ -1,14 +1,14 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.floret.data.di.IoDispatcher import de.jeanlucmakiola.agendula.data.di.IoDispatcher
import de.jeanlucmakiola.floret.domain.DayWindow import de.jeanlucmakiola.agendula.domain.DayWindow
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskDetail import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskFiltering import de.jeanlucmakiola.agendula.domain.TaskFiltering
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import de.jeanlucmakiola.floret.domain.TaskSorting import de.jeanlucmakiola.agendula.domain.TaskSorting
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import java.time.ZoneId import java.time.ZoneId
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,6 +1,6 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import de.jeanlucmakiola.floret.data.tasks.TasksContract import de.jeanlucmakiola.agendula.data.tasks.TasksContract
import kotlin.time.Instant import kotlin.time.Instant
/** A task list (the `tasklists` table). Lists group under their account. */ /** A task list (the `tasklists` table). Lists group under their account. */
@@ -14,7 +14,7 @@ data class TaskList(
val isVisible: Boolean, val isVisible: Boolean,
val owner: String?, val owner: String?,
) { ) {
/** A device-only list Floret (or another app) created locally, not synced. */ /** A device-only list Agendula (or another app) created locally, not synced. */
val isLocal: Boolean get() = accountType == TasksContract.LOCAL_ACCOUNT_TYPE val isLocal: Boolean get() = accountType == TasksContract.LOCAL_ACCOUNT_TYPE
} }

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,11 +1,11 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import kotlin.time.Instant import kotlin.time.Instant
/** /**
* A validated create/edit form. Kept free of Android types so ViewModels and * A validated create/edit form. Kept free of Android types so ViewModels and
* tests can build and validate it on the JVM. The data layer turns it into * tests can build and validate it on the JVM. The data layer turns it into
* provider ContentValues ([de.jeanlucmakiola.floret.data.tasks.TaskWriteMapper]). * provider ContentValues ([de.jeanlucmakiola.agendula.data.tasks.TaskWriteMapper]).
*/ */
data class TaskForm( data class TaskForm(
val title: String, val title: String,

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
/** Default task ordering, extracted so it's unit-testable without the repository. */ /** Default task ordering, extracted so it's unit-testable without the repository. */
object TaskSorting { object TaskSorting {

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui package de.jeanlucmakiola.agendula.ui
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
@@ -18,16 +18,16 @@ 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.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.data.tasks.ProviderStatus import de.jeanlucmakiola.agendula.data.tasks.ProviderStatus
import de.jeanlucmakiola.floret.ui.navigation.FloretNavHost import de.jeanlucmakiola.agendula.ui.navigation.AgendulaNavHost
import de.jeanlucmakiola.floret.ui.permission.PermissionViewModel import de.jeanlucmakiola.agendula.ui.permission.PermissionViewModel
import de.jeanlucmakiola.floret.ui.permission.ReminderOnboardingScreen import de.jeanlucmakiola.agendula.ui.permission.ReminderOnboardingScreen
import de.jeanlucmakiola.floret.ui.permission.ReminderOnboardingViewModel import de.jeanlucmakiola.agendula.ui.permission.ReminderOnboardingViewModel
/** /**
* App root: gates on the tasks-provider permission, then hands off to * App root: gates on the tasks-provider permission, then hands off to
* [FloretNavHost] (lists task list detail / edit). * [AgendulaNavHost] (lists task list detail / edit).
*/ */
@Composable @Composable
fun RootScreen( fun RootScreen(
@@ -70,7 +70,7 @@ private fun ReadyGate(
val done by onboardingViewModel.onboardingDone.collectAsStateWithLifecycle() val done by onboardingViewModel.onboardingDone.collectAsStateWithLifecycle()
Crossfade(targetState = done, label = "reminderOnboardingGate") { state -> Crossfade(targetState = done, label = "reminderOnboardingGate") { state ->
when (state) { when (state) {
true -> FloretNavHost(modifier = modifier) true -> AgendulaNavHost(modifier = modifier)
false -> ReminderOnboardingScreen( false -> ReminderOnboardingScreen(
onFinished = onboardingViewModel::finish, onFinished = onboardingViewModel::finish,
modifier = modifier, modifier = modifier,

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -25,7 +25,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
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 de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
/** /**
* Shared collapsing scaffold for the settings hub, its sub-screens and the * Shared collapsing scaffold for the settings hub, its sub-screens and the

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@@ -31,7 +31,7 @@ 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.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import java.time.LocalDate import java.time.LocalDate
import java.time.LocalTime import java.time.LocalTime
import java.time.ZoneId import java.time.ZoneId

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import java.time.ZoneId import java.time.ZoneId
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@@ -20,7 +20,7 @@ import androidx.compose.ui.unit.dp
/** /**
* The app's borderless text input: no underline, no outline, just the tonal * The app's borderless text input: no underline, no outline, just the tonal
* card behind it. Floret uses this inside a tonal [androidx.compose.material3.Surface] * card behind it. Agendula uses this inside a tonal [androidx.compose.material3.Surface]
* for the edit form (title + cards), so anything that takes text reads as one * for the edit form (title + cards), so anything that takes text reads as one
* family rather than a boxed Material field. Ported from Calendula so the two * family rather than a boxed Material field. Ported from Calendula so the two
* apps share one input style. * apps share one input style.

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -39,7 +39,7 @@ fun pastelize(rawArgb: Int, dark: Boolean): Color {
/** /**
* Leading avatar for a task list: a neutral round chip holding a checklist glyph * Leading avatar for a task list: a neutral round chip holding a checklist glyph
* tinted in the list's (pastelised) colour the Calendula calendar-chip pattern, * tinted in the list's (pastelised) colour the Calendula calendar-chip pattern,
* with a task icon for Floret. * with a task icon for Agendula.
*/ */
@Composable @Composable
fun ListColorChip(color: Int, modifier: Modifier = Modifier) { fun ListColorChip(color: Int, modifier: Modifier = Modifier) {

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -21,7 +21,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.isSpecified import androidx.compose.ui.graphics.isSpecified
import androidx.compose.ui.graphics.luminance import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
/** /**
* Priority has no semantic Material role (M3 only ships `error` for danger), so * Priority has no semantic Material role (M3 only ships `error` for danger), so

View File

@@ -1,10 +1,10 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
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 de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
/** Common reminder lead times offered as quick picks in the reminder pickers. */ /** Common reminder lead times offered as quick picks in the reminder pickers. */
val REMINDER_PRESETS = listOf(0, 5, 10, 30, 60, 1_440) val REMINDER_PRESETS = listOf(0, 5, 10, 30, 60, 1_440)

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically import androidx.compose.animation.expandVertically
@@ -30,8 +30,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.data.prefs.ListReminderOverride import de.jeanlucmakiola.agendula.data.prefs.ListReminderOverride
/** /**
* Reminder lead-time picker, full-screen: the grouped list of preset lead times * Reminder lead-time picker, full-screen: the grouped list of preset lead times

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.common package de.jeanlucmakiola.agendula.ui.common
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -26,7 +26,7 @@ import androidx.graphics.shapes.RoundedPolygon
/** /**
* A top-bar action whose icon sits in a tonal container clipped to one of the * A top-bar action whose icon sits in a tonal container clipped to one of the
* M3 Expressive [MaterialShapes] (cookie, clover, sunny, ) Floret's playful * M3 Expressive [MaterialShapes] (cookie, clover, sunny, ) Agendula's playful
* take on a plain [androidx.compose.material3.IconButton]. Each action passes * take on a plain [androidx.compose.material3.IconButton]. Each action passes
* its own [shape] and [containerColor], so a row of them reads as a set of * its own [shape] and [containerColor], so a row of them reads as a set of
* distinct little tokens rather than identical grey glyphs. * distinct little tokens rather than identical grey glyphs.

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.detail package de.jeanlucmakiola.agendula.ui.detail
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.background import androidx.compose.foundation.background
@@ -67,16 +67,16 @@ 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.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskDetail import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.floret.ui.common.GroupedSurface import de.jeanlucmakiola.agendula.ui.common.GroupedSurface
import de.jeanlucmakiola.floret.ui.common.PriorityChip import de.jeanlucmakiola.agendula.ui.common.PriorityChip
import de.jeanlucmakiola.floret.ui.common.formatDate import de.jeanlucmakiola.agendula.ui.common.formatDate
import de.jeanlucmakiola.floret.ui.common.formatTime import de.jeanlucmakiola.agendula.ui.common.formatTime
import de.jeanlucmakiola.floret.ui.common.pastelize import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.floret.ui.common.positionOf import de.jeanlucmakiola.agendula.ui.common.positionOf
import de.jeanlucmakiola.floret.ui.tasklist.priorityLabel import de.jeanlucmakiola.agendula.ui.tasklist.priorityLabel
/** /**
* One task's detail. Each fact is its own tonal card with a leading icon (the * One task's detail. Each fact is its own tonal card with a leading icon (the
@@ -246,7 +246,7 @@ private fun DetailBody(
} }
// Priority — a coloured status chip in the level's hue. // Priority — a coloured status chip in the level's hue.
if (task.priority != de.jeanlucmakiola.floret.domain.Priority.NONE) { if (task.priority != de.jeanlucmakiola.agendula.domain.Priority.NONE) {
add { add {
DetailCard( DetailCard(
icon = Icons.Rounded.Flag, icon = Icons.Rounded.Flag,

View File

@@ -1,12 +1,12 @@
package de.jeanlucmakiola.floret.ui.detail package de.jeanlucmakiola.agendula.ui.detail
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskDetail import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.edit package de.jeanlucmakiola.agendula.ui.edit
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically import androidx.compose.animation.expandVertically
@@ -85,27 +85,27 @@ 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.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.domain.DayWindow import de.jeanlucmakiola.agendula.domain.DayWindow
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskFormError import de.jeanlucmakiola.agendula.domain.TaskFormError
import de.jeanlucmakiola.floret.domain.TaskFormField import de.jeanlucmakiola.agendula.domain.TaskFormField
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import de.jeanlucmakiola.floret.domain.TaskSection import de.jeanlucmakiola.agendula.domain.TaskSection
import de.jeanlucmakiola.floret.domain.TaskSections import de.jeanlucmakiola.agendula.domain.TaskSections
import de.jeanlucmakiola.floret.ui.common.GroupedRow import de.jeanlucmakiola.agendula.ui.common.GroupedRow
import de.jeanlucmakiola.floret.ui.common.InlineTextField import de.jeanlucmakiola.agendula.ui.common.InlineTextField
import de.jeanlucmakiola.floret.ui.common.OptionCard import de.jeanlucmakiola.agendula.ui.common.OptionCard
import de.jeanlucmakiola.floret.ui.common.priorityFill import de.jeanlucmakiola.agendula.ui.common.priorityFill
import de.jeanlucmakiola.floret.ui.common.formatDate import de.jeanlucmakiola.agendula.ui.common.formatDate
import de.jeanlucmakiola.floret.ui.common.formatTime import de.jeanlucmakiola.agendula.ui.common.formatTime
import de.jeanlucmakiola.floret.ui.common.localToInstant import de.jeanlucmakiola.agendula.ui.common.localToInstant
import de.jeanlucmakiola.floret.ui.common.pastelize import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.floret.ui.common.positionOf import de.jeanlucmakiola.agendula.ui.common.positionOf
import de.jeanlucmakiola.floret.ui.common.toLocalDate import de.jeanlucmakiola.agendula.ui.common.toLocalDate
import de.jeanlucmakiola.floret.ui.common.toLocalTime import de.jeanlucmakiola.agendula.ui.common.toLocalTime
import de.jeanlucmakiola.floret.ui.tasklist.priorityLabel import de.jeanlucmakiola.agendula.ui.tasklist.priorityLabel
import java.time.LocalTime import java.time.LocalTime
import java.time.ZoneId import java.time.ZoneId
import java.time.ZoneOffset import java.time.ZoneOffset
@@ -119,7 +119,7 @@ import kotlin.time.Instant
* "When" card with the all-day toggle and tappable schedule rows, a tappable * "When" card with the all-day toggle and tappable schedule rows, a tappable
* list card, and the optional Description / Priority / Reminder sections that * list card, and the optional Description / Priority / Reminder sections that
* unfold from "More fields" (which ones start open is a setting). Validated * unfold from "More fields" (which ones start open is a setting). Validated
* through the M1 [TaskEditViewModel] / [de.jeanlucmakiola.floret.domain.TaskForm]; * through the M1 [TaskEditViewModel] / [de.jeanlucmakiola.agendula.domain.TaskForm];
* the VM flips `saved` once the write lands, which pops us back. * the VM flips `saved` once the write lands, which pops us back.
*/ */
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -853,7 +853,7 @@ private fun ParentPickerSheet(
item(key = "none") { item(key = "none") {
GroupedRow( GroupedRow(
title = stringResource(R.string.edit_parent_none), title = stringResource(R.string.edit_parent_none),
position = de.jeanlucmakiola.floret.ui.common.Position.Alone, position = de.jeanlucmakiola.agendula.ui.common.Position.Alone,
selected = selectedId == null, selected = selectedId == null,
minHeight = 56.dp, minHeight = 56.dp,
onClick = { choose(null) }, onClick = { choose(null) },

View File

@@ -1,20 +1,20 @@
package de.jeanlucmakiola.floret.ui.edit package de.jeanlucmakiola.agendula.ui.edit
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.floret.data.reminders.ReminderScheduler import de.jeanlucmakiola.agendula.data.reminders.ReminderScheduler
import de.jeanlucmakiola.floret.data.tasks.TaskConflictException import de.jeanlucmakiola.agendula.data.tasks.TaskConflictException
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import de.jeanlucmakiola.floret.domain.TaskFormError import de.jeanlucmakiola.agendula.domain.TaskFormError
import de.jeanlucmakiola.floret.domain.TaskFormField import de.jeanlucmakiola.agendula.domain.TaskFormField
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import de.jeanlucmakiola.floret.domain.populatedFields import de.jeanlucmakiola.agendula.domain.populatedFields
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.lists package de.jeanlucmakiola.agendula.ui.lists
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -45,14 +45,14 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.domain.SmartList import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.ui.common.ActionShapes import de.jeanlucmakiola.agendula.ui.common.ActionShapes
import de.jeanlucmakiola.floret.ui.common.GroupedRow import de.jeanlucmakiola.agendula.ui.common.GroupedRow
import de.jeanlucmakiola.floret.ui.common.ListColorChip import de.jeanlucmakiola.agendula.ui.common.ListColorChip
import de.jeanlucmakiola.floret.ui.common.ShapedActionButton import de.jeanlucmakiola.agendula.ui.common.ShapedActionButton
import de.jeanlucmakiola.floret.ui.common.positionOf import de.jeanlucmakiola.agendula.ui.common.positionOf
/** /**
* Home: smart lists (Today / Overdue / Upcoming / All) as tonal cards with live * Home: smart lists (Today / Overdue / Upcoming / All) as tonal cards with live

View File

@@ -1,15 +1,15 @@
package de.jeanlucmakiola.floret.ui.lists package de.jeanlucmakiola.agendula.ui.lists
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.DayWindow import de.jeanlucmakiola.agendula.domain.DayWindow
import de.jeanlucmakiola.floret.domain.SmartList import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskFiltering import de.jeanlucmakiola.agendula.domain.TaskFiltering
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.navigation package de.jeanlucmakiola.agendula.ui.navigation
import androidx.compose.animation.EnterTransition import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExitTransition
@@ -12,15 +12,15 @@ import androidx.hilt.navigation.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
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.ui.detail.TaskDetailScreen import de.jeanlucmakiola.agendula.ui.detail.TaskDetailScreen
import de.jeanlucmakiola.floret.ui.detail.TaskDetailViewModel import de.jeanlucmakiola.agendula.ui.detail.TaskDetailViewModel
import de.jeanlucmakiola.floret.ui.edit.TaskEditScreen import de.jeanlucmakiola.agendula.ui.edit.TaskEditScreen
import de.jeanlucmakiola.floret.ui.edit.TaskEditViewModel import de.jeanlucmakiola.agendula.ui.edit.TaskEditViewModel
import de.jeanlucmakiola.floret.ui.lists.ListsScreen import de.jeanlucmakiola.agendula.ui.lists.ListsScreen
import de.jeanlucmakiola.floret.ui.settings.SettingsScreen import de.jeanlucmakiola.agendula.ui.settings.SettingsScreen
import de.jeanlucmakiola.floret.ui.tasklist.TaskListScreen import de.jeanlucmakiola.agendula.ui.tasklist.TaskListScreen
import de.jeanlucmakiola.floret.ui.tasklist.TaskListViewModel import de.jeanlucmakiola.agendula.ui.tasklist.TaskListViewModel
/** /**
* The single in-app back stack: lists task list detail / edit. * The single in-app back stack: lists task list detail / edit.
@@ -32,7 +32,7 @@ import de.jeanlucmakiola.floret.ui.tasklist.TaskListViewModel
* unaware of the nav library. * unaware of the nav library.
*/ */
@Composable @Composable
fun FloretNavHost(modifier: Modifier = Modifier) { fun AgendulaNavHost(modifier: Modifier = Modifier) {
val nav = rememberNavController() val nav = rememberNavController()
val slide = rememberNavSlideSpec() val slide = rememberNavSlideSpec()

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.navigation package de.jeanlucmakiola.agendula.ui.navigation
import androidx.compose.animation.core.FiniteAnimationSpec import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi

View File

@@ -1,9 +1,9 @@
package de.jeanlucmakiola.floret.ui.navigation package de.jeanlucmakiola.agendula.ui.navigation
import androidx.navigation.NavType import androidx.navigation.NavType
import androidx.navigation.navArgument import androidx.navigation.navArgument
import de.jeanlucmakiola.floret.domain.SmartList import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
/** /**
* String-based route table for the app's [androidx.navigation.NavHost]. * String-based route table for the app's [androidx.navigation.NavHost].

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.permission package de.jeanlucmakiola.agendula.ui.permission
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement

View File

@@ -1,10 +1,10 @@
package de.jeanlucmakiola.floret.ui.permission package de.jeanlucmakiola.agendula.ui.permission
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.tasks.ProviderResolver import de.jeanlucmakiola.agendula.data.tasks.ProviderResolver
import de.jeanlucmakiola.floret.data.tasks.ProviderStatus import de.jeanlucmakiola.agendula.data.tasks.ProviderStatus
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.permission package de.jeanlucmakiola.agendula.ui.permission
import android.Manifest import android.Manifest
import android.os.Build import android.os.Build
@@ -21,10 +21,10 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
/** /**
* One-time onboarding step after the tasks-provider grant: explains that Floret * One-time onboarding step after the tasks-provider grant: explains that Agendula
* delivers due reminders itself (tasks providers don't broadcast reminders) and * delivers due reminders itself (tasks providers don't broadcast reminders) and
* requests `POST_NOTIFICATIONS` (a system dialog on API 33+ only). * requests `POST_NOTIFICATIONS` (a system dialog on API 33+ only).
* *

View File

@@ -1,9 +1,9 @@
package de.jeanlucmakiola.floret.ui.permission package de.jeanlucmakiola.agendula.ui.permission
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map

View File

@@ -1,9 +1,9 @@
package de.jeanlucmakiola.floret.ui.settings package de.jeanlucmakiola.agendula.ui.settings
import android.content.Context import android.content.Context
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import java.util.Locale import java.util.Locale

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.settings package de.jeanlucmakiola.agendula.ui.settings
import android.Manifest import android.Manifest
import android.app.AlarmManager import android.app.AlarmManager
@@ -83,18 +83,18 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.data.prefs.ListReminderOverride import de.jeanlucmakiola.agendula.data.prefs.ListReminderOverride
import de.jeanlucmakiola.floret.data.prefs.ThemeMode import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
import de.jeanlucmakiola.floret.domain.TaskFormField import de.jeanlucmakiola.agendula.domain.TaskFormField
import de.jeanlucmakiola.floret.ui.common.CollapsingScaffold import de.jeanlucmakiola.agendula.ui.common.CollapsingScaffold
import de.jeanlucmakiola.floret.ui.common.GroupedRow import de.jeanlucmakiola.agendula.ui.common.GroupedRow
import de.jeanlucmakiola.floret.ui.common.OptionPicker import de.jeanlucmakiola.agendula.ui.common.OptionPicker
import de.jeanlucmakiola.floret.ui.common.Position import de.jeanlucmakiola.agendula.ui.common.Position
import de.jeanlucmakiola.floret.ui.common.ReminderLeadPicker import de.jeanlucmakiola.agendula.ui.common.ReminderLeadPicker
import de.jeanlucmakiola.floret.ui.common.pastelize import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.floret.ui.common.positionOf import de.jeanlucmakiola.agendula.ui.common.positionOf
import de.jeanlucmakiola.floret.ui.common.reminderLeadTimeLabel import de.jeanlucmakiola.agendula.ui.common.reminderLeadTimeLabel
/** The settings sub-screens reached from the hub's category rows. */ /** The settings sub-screens reached from the hub's category rows. */
private enum class SettingsSection { Appearance, TaskForm, Reminders } private enum class SettingsSection { Appearance, TaskForm, Reminders }

View File

@@ -1,15 +1,15 @@
package de.jeanlucmakiola.floret.ui.settings package de.jeanlucmakiola.agendula.ui.settings
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.prefs.ListReminderOverride import de.jeanlucmakiola.agendula.data.prefs.ListReminderOverride
import de.jeanlucmakiola.floret.data.prefs.Settings import de.jeanlucmakiola.agendula.data.prefs.Settings
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.floret.data.prefs.ThemeMode import de.jeanlucmakiola.agendula.data.prefs.ThemeMode
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.TaskFormField import de.jeanlucmakiola.agendula.domain.TaskFormField
import de.jeanlucmakiola.floret.domain.TaskList import de.jeanlucmakiola.agendula.domain.TaskList
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.tasklist package de.jeanlucmakiola.agendula.ui.tasklist
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateDpAsState
@@ -80,20 +80,20 @@ 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.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.floret.R import de.jeanlucmakiola.agendula.R
import de.jeanlucmakiola.floret.domain.DayWindow import de.jeanlucmakiola.agendula.domain.DayWindow
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.SmartList import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskSection import de.jeanlucmakiola.agendula.domain.TaskSection
import de.jeanlucmakiola.floret.domain.TaskSections import de.jeanlucmakiola.agendula.domain.TaskSections
import de.jeanlucmakiola.floret.ui.common.ListNameChip import de.jeanlucmakiola.agendula.ui.common.ListNameChip
import de.jeanlucmakiola.floret.ui.common.Position import de.jeanlucmakiola.agendula.ui.common.Position
import de.jeanlucmakiola.floret.ui.common.PriorityChip import de.jeanlucmakiola.agendula.ui.common.PriorityChip
import de.jeanlucmakiola.floret.ui.common.formatDateTime import de.jeanlucmakiola.agendula.ui.common.formatDateTime
import de.jeanlucmakiola.floret.ui.common.pastelize import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.floret.ui.common.positionOf import de.jeanlucmakiola.agendula.ui.common.positionOf
import java.time.ZoneId import java.time.ZoneId
import kotlin.time.Clock import kotlin.time.Clock

View File

@@ -1,13 +1,13 @@
package de.jeanlucmakiola.floret.ui.tasklist package de.jeanlucmakiola.agendula.ui.tasklist
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.floret.data.prefs.SettingsPrefs import de.jeanlucmakiola.agendula.data.prefs.SettingsPrefs
import de.jeanlucmakiola.floret.data.tasks.TasksRepository import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
import de.jeanlucmakiola.floret.domain.Task import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.floret.domain.TaskFilter import de.jeanlucmakiola.agendula.domain.TaskFilter
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.theme package de.jeanlucmakiola.agendula.ui.theme
import androidx.compose.material3.darkColorScheme import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme import androidx.compose.material3.lightColorScheme
@@ -12,14 +12,14 @@ import androidx.compose.ui.graphics.Color
* *
* Placeholder until branding lands; refine alongside the launcher icon. * Placeholder until branding lands; refine alongside the launcher icon.
*/ */
val FloretSeed: Color = Color(0xFF7A5C6B) val AgendulaSeed: Color = Color(0xFF7A5C6B)
/** /**
* Fallback light scheme for devices without dynamic color (API < 31) or when * Fallback light scheme for devices without dynamic color (API < 31) or when
* the user disables it. Dynamic color (API 31+) is the real path; these are a * the user disables it. Dynamic color (API 31+) is the real path; these are a
* hand-picked approximation of the tonal palette generated from [FloretSeed]. * hand-picked approximation of the tonal palette generated from [AgendulaSeed].
*/ */
val FloretLightFallback = lightColorScheme( val AgendulaLightFallback = lightColorScheme(
primary = Color(0xFF6A4E5B), primary = Color(0xFF6A4E5B),
onPrimary = Color(0xFFFFFFFF), onPrimary = Color(0xFFFFFFFF),
primaryContainer = Color(0xFFF6D9E5), primaryContainer = Color(0xFFF6D9E5),
@@ -32,7 +32,7 @@ val FloretLightFallback = lightColorScheme(
onSurface = Color(0xFF1F1A1C), onSurface = Color(0xFF1F1A1C),
) )
val FloretDarkFallback = darkColorScheme( val AgendulaDarkFallback = darkColorScheme(
primary = Color(0xFFE2BAC9), primary = Color(0xFFE2BAC9),
onPrimary = Color(0xFF402533), onPrimary = Color(0xFF402533),
primaryContainer = Color(0xFF583B49), primaryContainer = Color(0xFF583B49),

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.theme package de.jeanlucmakiola.agendula.ui.theme
import android.os.Build import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
@@ -14,7 +14,7 @@ import androidx.compose.ui.platform.LocalContext
* App theme. Honors: * App theme. Honors:
* - System light/dark. * - System light/dark.
* - Dynamic Color on API 31+, else falls back to the hand-tuned scheme * - Dynamic Color on API 31+, else falls back to the hand-tuned scheme
* derived from [FloretSeed]. * derived from [AgendulaSeed].
* *
* Mirrors Calendula's theme: MaterialExpressiveTheme routes all component + * Mirrors Calendula's theme: MaterialExpressiveTheme routes all component +
* custom motion through MaterialTheme.motionScheme. STANDARD over expressive() * custom motion through MaterialTheme.motionScheme. STANDARD over expressive()
@@ -25,7 +25,7 @@ import androidx.compose.ui.platform.LocalContext
*/ */
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun FloretTheme( fun AgendulaTheme(
darkTheme: Boolean = isSystemInDarkTheme(), darkTheme: Boolean = isSystemInDarkTheme(),
dynamicColor: Boolean = true, dynamicColor: Boolean = true,
content: @Composable () -> Unit, content: @Composable () -> Unit,
@@ -35,13 +35,13 @@ fun FloretTheme(
val ctx = LocalContext.current val ctx = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx) if (darkTheme) dynamicDarkColorScheme(ctx) else dynamicLightColorScheme(ctx)
} }
darkTheme -> FloretDarkFallback darkTheme -> AgendulaDarkFallback
else -> FloretLightFallback else -> AgendulaLightFallback
} }
MaterialExpressiveTheme( MaterialExpressiveTheme(
colorScheme = colorScheme, colorScheme = colorScheme,
typography = FloretTypography, typography = AgendulaTypography,
motionScheme = MotionScheme.standard(), motionScheme = MotionScheme.standard(),
content = content, content = content,
) )

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.ui.theme package de.jeanlucmakiola.agendula.ui.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
@@ -7,4 +7,4 @@ import androidx.compose.material3.Typography
* land in a later UI-design iteration; the defaults keep the M0 foundation lean * land in a later UI-design iteration; the defaults keep the M0 foundation lean
* (same posture as Calendula's scaffolding). * (same posture as Calendula's scaffolding).
*/ */
val FloretTypography = Typography() val AgendulaTypography = Typography()

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Floret launcher icon foreground — PLACEHOLDER. Agendula launcher icon foreground — PLACEHOLDER.
A simple rounded check mark inside the 108dp adaptive-icon canvas A simple rounded check mark inside the 108dp adaptive-icon canvas
(72dp safe zone). Deliberately not Calendula's calendar mark, so the two (72dp safe zone). Deliberately not Calendula's calendar mark, so the two
apps never look alike. Replace with real branding (a stylized floret) apps never look alike. Replace with real branding (a stylized agendula)
when the design lands — see docs/PLAN.md §9. when the design lands — see docs/PLAN.md §9.
--> -->
<vector xmlns:android="http://schemas.android.com/apk/res/android" <vector xmlns:android="http://schemas.android.com/apk/res/android"

View File

@@ -1,5 +1,5 @@
<resources> <resources>
<string name="app_name">Floret</string> <string name="app_name">Agendula</string>
<string name="app_tagline">A modern Material 3 Expressive task app.\nComing into bloom.</string> <string name="app_tagline">A modern Material 3 Expressive task app.\nComing into bloom.</string>
<!-- Generic --> <!-- Generic -->
@@ -136,19 +136,19 @@
<!-- Provider / permission onboarding --> <!-- Provider / permission onboarding -->
<string name="onboarding_no_provider_title">A tasks app is needed</string> <string name="onboarding_no_provider_title">A tasks app is needed</string>
<string name="onboarding_no_provider_body">Floret shows and edits the tasks stored by OpenTasks or tasks.org, synced by DAVx5. Install one of them to get started.</string> <string name="onboarding_no_provider_body">Agendula shows and edits the tasks stored by OpenTasks or tasks.org, synced by DAVx5. Install one of them to get started.</string>
<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">Floret 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_opentasks">Install OpenTasks</string>
<string name="onboarding_install_tasksorg">Install tasks.org</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>
<string name="reminder_onboarding_body">Tasks apps don\'t send reminders themselves, so Floret delivers them for you. Turn them on to get a notification when a task is due.</string> <string name="reminder_onboarding_body">Tasks apps don\'t send reminders themselves, so Agendula delivers them for you. Turn them on to get a notification when a task is due.</string>
<string name="reminder_onboarding_enable_button">Enable reminders</string> <string name="reminder_onboarding_enable_button">Enable reminders</string>
<string name="reminder_onboarding_skip_button">Not now</string> <string name="reminder_onboarding_skip_button">Not now</string>
<string name="reminder_benefit_delivery_title">Floret reminds you</string> <string name="reminder_benefit_delivery_title">Agendula reminds you</string>
<string name="reminder_benefit_delivery_body">It schedules a notification for each task with a due date.</string> <string name="reminder_benefit_delivery_body">It schedules a notification for each task with a due date.</string>
<string name="reminder_benefit_timing_title">On your schedule</string> <string name="reminder_benefit_timing_title">On your schedule</string>
<string name="reminder_benefit_timing_body">Choose how far ahead to be reminded in Settings.</string> <string name="reminder_benefit_timing_body">Choose how far ahead to be reminded in Settings.</string>
@@ -170,14 +170,14 @@
<string name="settings_license">License</string> <string name="settings_license">License</string>
<string name="settings_about_support">Support development</string> <string name="settings_about_support">Support development</string>
<string name="settings_about_version">Version %1$s</string> <string name="settings_about_version">Version %1$s</string>
<string name="settings_about_logo_desc">Floret app icon</string> <string name="settings_about_logo_desc">Agendula app icon</string>
<string name="settings_language">App language</string> <string name="settings_language">App language</string>
<string name="settings_language_auto">System default</string> <string name="settings_language_auto">System default</string>
<string name="settings_report_problem">Report a problem</string> <string name="settings_report_problem">Report a problem</string>
<string name="settings_report_problem_hint">Open the issue tracker</string> <string name="settings_report_problem_hint">Open the issue tracker</string>
<string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/floret</string> <string name="about_source_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/agendula</string>
<string name="report_issue_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/floret/issues/new</string> <string name="report_issue_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/agendula/issues/new</string>
<string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/floret/src/branch/main/LICENSE</string> <string name="about_license_url" translatable="false">https://gitea.jeanlucmakiola.de/makiolaj/agendula/src/branch/main/LICENSE</string>
<string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string> <string name="about_support_url" translatable="false">https://ko-fi.com/jeanlucmakiola</string>
<string name="settings_theme">Theme</string> <string name="settings_theme">Theme</string>
<string name="settings_theme_system">Follow system</string> <string name="settings_theme_system">Follow system</string>

View File

@@ -1,5 +1,5 @@
<resources xmlns:tools="http://schemas.android.com/tools"> <resources xmlns:tools="http://schemas.android.com/tools">
<style name="Theme.Floret" parent="android:Theme.Material.Light.NoActionBar"> <style name="Theme.Agendula" parent="android:Theme.Material.Light.NoActionBar">
<item name="android:statusBarColor">@android:color/transparent</item> <item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item> <item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:windowLightStatusBar">true</item> <item name="android:windowLightStatusBar">true</item>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
The languages Floret ships translations for. Single source of truth: each The languages Agendula ships translations for. Single source of truth: each
entry needs a matching res/values-<tag>/strings.xml, and is surfaced in both entry needs a matching res/values-<tag>/strings.xml, and is surfaced in both
the in-app language picker (parsed at runtime by AppLanguage) and the system the in-app language picker (parsed at runtime by AppLanguage) and the system
per-app language settings (Android 13+, via android:localeConfig). To add a per-app language settings (Android 13+, via android:localeConfig). To add a

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
/** A [ColumnReader] backed by a Map, so mappers test without a real Cursor. */ /** A [ColumnReader] backed by a Map, so mappers test without a real Cursor. */
class MapColumnReader(private val values: Map<String, Any?>) : ColumnReader { class MapColumnReader(private val values: Map<String, Any?>) : ColumnReader {

View File

@@ -1,11 +1,11 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Instances import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Instances
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.TaskStatus import de.jeanlucmakiola.agendula.domain.TaskStatus
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class TaskMapperTest { class TaskMapperTest {

View File

@@ -1,10 +1,10 @@
package de.jeanlucmakiola.floret.data.tasks package de.jeanlucmakiola.agendula.data.tasks
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Lists import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Lists
import de.jeanlucmakiola.floret.data.tasks.TasksContract.Tasks import de.jeanlucmakiola.agendula.data.tasks.TasksContract.Tasks
import de.jeanlucmakiola.floret.domain.Priority import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.floret.domain.TaskForm import de.jeanlucmakiola.agendula.domain.TaskForm
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test

View File

@@ -1,4 +1,4 @@
package de.jeanlucmakiola.floret.domain package de.jeanlucmakiola.agendula.domain
import kotlin.time.Instant import kotlin.time.Instant

View File

@@ -1,6 +1,6 @@
# Floret — architecture # Agendula — architecture
This document describes how Floret is built **as it stands today**. For the This document describes how Agendula is built **as it stands today**. For the
*why* behind the big decisions and the long-term plan, see *why* behind the big decisions and the long-term plan, see
[`PLAN.md`](PLAN.md); for status and what's next, see [`ROADMAP.md`](ROADMAP.md). [`PLAN.md`](PLAN.md); for status and what's next, see [`ROADMAP.md`](ROADMAP.md).
@@ -8,10 +8,10 @@ This document describes how Floret is built **as it stands today**. For the
## 1. The thesis in one sentence ## 1. The thesis in one sentence
Floret is a Material 3 Expressive **front-end** over the OpenTasks Agendula is a Material 3 Expressive **front-end** over the OpenTasks
`TaskContract` provider — it reads, writes, and reminds on top of a tasks store `TaskContract` provider — it reads, writes, and reminds on top of a tasks store
that some other app (DAVx5, SmoothSync, DecSync CC, tasks.org, …) syncs over that some other app (DAVx5, SmoothSync, DecSync CC, tasks.org, …) syncs over
CalDAV. **Floret owns no database and no sync stack.** It is the task-list CalDAV. **Agendula owns no database and no sync stack.** It is the task-list
sibling to [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula), sibling to [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula),
which does the same thing for `CalendarContract`. which does the same thing for `CalendarContract`.
@@ -69,7 +69,7 @@ Both are bound in Hilt in `data/di/DataModule.kt`.
## 3. Module & package layout ## 3. Module & package layout
Single `:app` module (Posture A). Package root `de.jeanlucmakiola.floret`. Single `:app` module (Posture A). Package root `de.jeanlucmakiola.agendula`.
| Package | Contents | | Package | Contents |
|---|---| |---|---|
@@ -80,7 +80,7 @@ Single `:app` module (Posture A). Package root `de.jeanlucmakiola.floret`.
| `data/di/` | `DataModule` (binds + provides), `Qualifiers` (`@IoDispatcher`). | | `data/di/` | `DataModule` (binds + provides), `Qualifiers` (`@IoDispatcher`). |
| `data/demo/` | `DemoSeeder` (debug-only sample data). | | `data/demo/` | `DemoSeeder` (debug-only sample data). |
| `ui/` | `theme/`, `common/` (GroupedList, ListChip), `lists/`, `tasklist/`, `detail/`, `edit/`, `settings/`, `permission/` (each a ViewModel + UiState; `lists` also has its screen), `RootScreen`. | | `ui/` | `theme/`, `common/` (GroupedList, ListChip), `lists/`, `tasklist/`, `detail/`, `edit/`, `settings/`, `permission/` (each a ViewModel + UiState; `lists` also has its screen), `RootScreen`. |
| root | `FloretApp` (Hilt app), `MainActivity`. | | root | `AgendulaApp` (Hilt app), `MainActivity`. |
--- ---
@@ -106,7 +106,7 @@ runtime perms for the active provider.
### 4.2 `TasksContract` ### 4.2 `TasksContract`
A vendored subset of the Apache-2.0 OpenTasks `TaskContract` — column names, A vendored subset of the Apache-2.0 OpenTasks `TaskContract` — column names,
table paths, status/priority constants, the local-account type. Floret does table paths, status/priority constants, the local-account type. Agendula does
**not** take a runtime dependency on OpenTasks; the authority is injected from **not** take a runtime dependency on OpenTasks; the authority is injected from
`ProviderResolver`, never hardcoded in the contract. `ProviderResolver`, never hardcoded in the contract.
@@ -117,7 +117,7 @@ occurrence is a row with the joined list colour, account, etc.), maps each
cursor row through `ColumnReader``TaskMapper` → domain `Task`, and exposes cursor row through `ColumnReader``TaskMapper` → domain `Task`, and exposes
the result as a `Flow`. A `ContentObserver` on the active authority's the result as a `Flow`. A `ContentObserver` on the active authority's
Tasks/TaskLists URIs bridges into the Flow via `callbackFlow`, so **any change Tasks/TaskLists URIs bridges into the Flow via `callbackFlow`, so **any change
re-emits** — Floret's own writes *and* external sync (DAVx5 pulling new tasks) re-emits** — Agendula's own writes *and* external sync (DAVx5 pulling new tasks)
update the UI live, and multiple sync sources coexist in one list. update the UI live, and multiple sync sources coexist in one list.
### 4.4 Writes — repository API ### 4.4 Writes — repository API
@@ -170,7 +170,7 @@ priority / etc.). None of this touches Android, which is why it's all in
## 6. Reminders — the one subsystem that does NOT mirror Calendula ## 6. Reminders — the one subsystem that does NOT mirror Calendula
Calendula relies on the calendar provider broadcasting `EVENT_REMINDER`. **Tasks Calendula relies on the calendar provider broadcasting `EVENT_REMINDER`. **Tasks
providers broadcast nothing**, so Floret schedules its own (`data/reminders/`): providers broadcast nothing**, so Agendula schedules its own (`data/reminders/`):
- **`ReminderScheduler.sync()`** reads upcoming, non-closed, due-dated tasks - **`ReminderScheduler.sync()`** reads upcoming, non-closed, due-dated tasks
within a rolling **30-day window**, computes each trigger as `due lead` within a rolling **30-day window**, computes each trigger as `due lead`
@@ -186,7 +186,7 @@ providers broadcast nothing**, so Floret schedules its own (`data/reminders/`):
sync changed the data). The store lets each run diff like Calendula diffs sync changed the data). The store lets each run diff like Calendula diffs
reminder rows. reminder rows.
This is the single largest piece of genuinely-new code in Floret. This is the single largest piece of genuinely-new code in Agendula.
--- ---
@@ -201,7 +201,7 @@ This is the single largest piece of genuinely-new code in Floret.
domain, and `TasksRepository` do not change** — only the resolver's default and domain, and `TasksRepository` do not change** — only the resolver's default and
some manifest perms. some manifest perms.
Bundling the provider bundles **storage, not sync**Floret stays a pure Bundling the provider bundles **storage, not sync**Agendula stays a pure
front-end over open backends either way. front-end over open backends either way.
--- ---
@@ -228,8 +228,8 @@ checkbox/FAB/swipe motion).
Hilt, `SingletonComponent`. `DataModule` has a `@Binds` module Hilt, `SingletonComponent`. `DataModule` has a `@Binds` module
(`TasksDataSource``AndroidTasksDataSource`, `TasksRepository` (`TasksDataSource``AndroidTasksDataSource`, `TasksRepository`
`TasksRepositoryImpl`) and a `@Provides` module (the `floret_prefs` DataStore, `TasksRepositoryImpl`) and a `@Provides` module (the `agendula_prefs` DataStore,
the `@IoDispatcher`). `FloretApp` is the `@HiltAndroidApp` entry point; the `@IoDispatcher`). `AgendulaApp` is the `@HiltAndroidApp` entry point;
`MainActivity` is `@AndroidEntryPoint`. ViewModels get the repository injected. `MainActivity` is `@AndroidEntryPoint`. ViewModels get the repository injected.
--- ---

View File

@@ -1,23 +1,25 @@
# Floret — implementation plan # Agendula — implementation plan
> A modern Material 3 Expressive **task** app for Android. Reads, writes, and > A modern Material 3 Expressive **task** app for Android. Reads, writes, and
> reminds — on top of an existing tasks provider (synced by DAVx5 / SmoothSync / > reminds — on top of an existing tasks provider (synced by DAVx5 / SmoothSync /
> DecSync over CalDAV), with no own sync stack. > DecSync over CalDAV), with no own sync stack.
> >
> Sibling to **Calendula**. Calendula is a calendar over `CalendarContract`; > Sibling to **Calendula**. Calendula is a calendar over `CalendarContract`;
> Floret is a to-do list over the **OpenTasks `TaskContract` provider**. A > Agendula is a to-do list over the **OpenTasks `TaskContract` provider**. The
> Calendula flower head is made of many small *florets* — the individual items > name mirrors Calendula's: *agenda* (Latin, “things to be done”) + the `-ula`
> that make up the bloom. > ending — and a Calendula flower head is itself a cluster of small *florets*,
> so the two apps are florets of one bloom.
Working identifiers (placeholder, easily renamed): Identifiers: `applicationId = de.jeanlucmakiola.agendula`, app name **Agendula**
`applicationId = de.jeanlucmakiola.floret`, app name **Floret**. (renamed from the working title *Floret*, which was promoted to the shared
family / design-language name).
--- ---
## 0. The thesis this app embodies ## 0. The thesis this app embodies
A nice M3-Expressive front end over open backends, no reinvented storage or A nice M3-Expressive front end over open backends, no reinvented storage or
sync. Calendula proved the pattern against the OS calendar provider. Floret sync. Calendula proved the pattern against the OS calendar provider. Agendula
applies it to tasks. The crucial difference: **there is no OS tasks provider**, applies it to tasks. The crucial difference: **there is no OS tasks provider**,
so we depend on a tasks *provider app* being present — exactly as Calendula so we depend on a tasks *provider app* being present — exactly as Calendula
depends on a sync app like DAVx5 for CalDAV. depends on a sync app like DAVx5 for CalDAV.
@@ -46,7 +48,7 @@ string leak above the data layer.**
Calendula's layering is the template. Lift these **verbatim or near-verbatim**: Calendula's layering is the template. Lift these **verbatim or near-verbatim**:
| Area | From Calendula | Change for Floret | | Area | From Calendula | Change for Agendula |
|---|---|---| |---|---|---|
| Gradle setup | `build.gradle.kts`, `settings.gradle.kts`, `gradle/libs.versions.toml`, wrapper, `key.properties` flow, versionCode-from-tag CI | namespace/appId only | | Gradle setup | `build.gradle.kts`, `settings.gradle.kts`, `gradle/libs.versions.toml`, wrapper, `key.properties` flow, versionCode-from-tag CI | namespace/appId only |
| Build config | AGP 9.2.1, Kotlin 2.3.21, KSP, Hilt 2.59.2, compileSdk 37 / minSdk 29 / targetSdk 36, Java 17 | identical | | Build config | AGP 9.2.1, Kotlin 2.3.21, KSP, Hilt 2.59.2, compileSdk 37 / minSdk 29 / targetSdk 36, Java 17 | identical |
@@ -65,7 +67,7 @@ Calendula's layering is the template. Lift these **verbatim or near-verbatim**:
`TaskContract` data layer, the task screens, and a **self-scheduled reminder `TaskContract` data layer, the task screens, and a **self-scheduled reminder
engine** (see §6 — the one place Calendula's pattern does *not* carry over). engine** (see §6 — the one place Calendula's pattern does *not* carry over).
### What does NOT exist here (why Floret is simpler than Calendula) ### What does NOT exist here (why Agendula is simpler than Calendula)
No month/week/day grid rendering. No recurrence-scoped writes ("this & following" No month/week/day grid rendering. No recurrence-scoped writes ("this & following"
vs "whole series"). No timezone/all-day gymnastics. Tasks are a flat-or-lightly- vs "whole series"). No timezone/all-day gymnastics. Tasks are a flat-or-lightly-
@@ -76,7 +78,7 @@ nested list with a due date and a checkbox.
## 2. Module & package layout ## 2. Module & package layout
Single `:app` module for A (mirrors Calendula). Package root Single `:app` module for A (mirrors Calendula). Package root
`de.jeanlucmakiola.floret`. `de.jeanlucmakiola.agendula`.
``` ```
domain/ domain/
@@ -111,10 +113,10 @@ ui/
edit/ TaskEditScreen + VM + UiState edit/ TaskEditScreen + VM + UiState
settings/ (copy + adapt) settings/ (copy + adapt)
permission/ provider-presence + permission + POST_NOTIFICATIONS onboarding permission/ provider-presence + permission + POST_NOTIFICATIONS onboarding
FloretHost.kt nav host (mirrors CalendarHost) AgendulaHost.kt nav host (mirrors CalendarHost)
RootScreen.kt RootScreen.kt
widget/ Glance task widget (later milestone) widget/ Glance task widget (later milestone)
FloretApp.kt, MainActivity.kt AgendulaApp.kt, MainActivity.kt
``` ```
--- ---
@@ -217,9 +219,9 @@ Material 3 Expressive throughout: `MaterialExpressiveTheme`,
Three gates, in order: Three gates, in order:
1. **Provider present?** `ProviderResolver.detect()`. If none → a screen 1. **Provider present?** `ProviderResolver.detect()`. If none → a screen
explaining Floret needs a tasks provider, with one-tap links to install explaining Agendula needs a tasks provider, with one-tap links to install
**OpenTasks** (FOSS) or **tasks.org**, plus "I use DAVx5 — set its Tasks app". **OpenTasks** (FOSS) or **tasks.org**, plus "I use DAVx5 — set its Tasks app".
(This is Floret's "needs DAVx5" moment. Disappears entirely under Posture B.) (This is Agendula's "needs DAVx5" moment. Disappears entirely under Posture B.)
2. **Tasks read/write permission** — request the active provider's runtime perms 2. **Tasks read/write permission** — request the active provider's runtime perms
(`org.dmfs.permission.*` etc.). Declared in the manifest *and* requested at (`org.dmfs.permission.*` etc.). Declared in the manifest *and* requested at
runtime; resolved dynamically from the detected provider. runtime; resolved dynamically from the detected provider.
@@ -235,7 +237,7 @@ exactly like Calendula's launcher query.
Calendula relies on the **calendar provider broadcasting `EVENT_REMINDER`** and Calendula relies on the **calendar provider broadcasting `EVENT_REMINDER`** and
just posts the notification (Etar model). **Tasks providers do not broadcast just posts the notification (Etar model). **Tasks providers do not broadcast
reminders.** So Floret must schedule its own: reminders.** So Agendula must schedule its own:
- `DueReminderScheduler` (AlarmManager, exact alarms via `USE_EXACT_ALARM` / - `DueReminderScheduler` (AlarmManager, exact alarms via `USE_EXACT_ALARM` /
`SCHEDULE_EXACT_ALARM`) sets an alarm per task at `DUE` minus the chosen `SCHEDULE_EXACT_ALARM`) sets an alarm per task at `DUE` minus the chosen
@@ -273,7 +275,7 @@ receiver. **No `EVENT_REMINDER` receiver** (doesn't apply).
## 8. Milestones ## 8. Milestones
- **M0 — Skeleton.** Copy Gradle/version-catalog/theme/DI/app+activity from - **M0 — Skeleton.** Copy Gradle/version-catalog/theme/DI/app+activity from
Calendula, rename to Floret. Builds, shows themed empty scaffold. *(~½ day)* Calendula, rename to Agendula. Builds, shows themed empty scaffold. *(~½ day)*
- **M1 — Read path.** `TasksContract` + `ProviderResolver` + `OpenTasksDataSource` - **M1 — Read path.** `TasksContract` + `ProviderResolver` + `OpenTasksDataSource`
+ repository. Lists overview + task list render real synced tasks (read-only), + repository. Lists overview + task list render real synced tasks (read-only),
live-updating via ContentObserver. *(the meaty milestone)* live-updating via ContentObserver. *(the meaty milestone)*
@@ -297,21 +299,22 @@ M4 (subtasks) and M5 (reminders) are the two spots needing real thought.
## 9. Open decisions / to verify ## 9. Open decisions / to verify
1. **Name**`Floret` is the working title (florets compose a Calendula head). 1. ~~**Name**~~**Agendula** (final): *agenda* + Calendula's `-ula`. Renamed
Confirm or replace before M0 (touches appId, namespace, package). from the working title *Floret*, which was promoted to the family /
design-language name.
2. **tasks.org provider authority** — verify on a real device; OpenTasks 2. **tasks.org provider authority** — verify on a real device; OpenTasks
(`org.dmfs.tasks`) is the certain target for v1. (`org.dmfs.tasks`) is the certain target for v1.
3. **jtx Board** — support its richer contract later, or stay OpenTasks-only? 3. **jtx Board** — support its richer contract later, or stay OpenTasks-only?
4. **B authority choice** — bundling `org.dmfs.tasks` makes Floret a *replacement* 4. **B authority choice** — bundling `org.dmfs.tasks` makes Agendula a *replacement*
for OpenTasks (one authority owner per device). Intended (one app instead of for OpenTasks (one authority owner per device). Intended (one app instead of
two), but a conscious choice. two), but a conscious choice.
5. **Repo home** — sibling Gitea repo next to Calendula, MIT license, same CI. 5. **Repo home** — sibling Gitea repo next to Calendula, MIT license, same CI.
--- ---
## 10. Sync sources Floret inherits for free (README copy) ## 10. Sync sources Agendula inherits for free (README copy)
Because Floret builds on the provider, not on any one sync app, it works with Because Agendula builds on the provider, not on any one sync app, it works with
**anything that writes to the tasks provider**: DAVx5 (CalDAV), SmoothSync, **anything that writes to the tasks provider**: DAVx5 (CalDAV), SmoothSync,
CalDAV-Sync, DecSync CC, and any Android sync adapter — no per-app integration. CalDAV-Sync, DecSync CC, and any Android sync adapter — no per-app integration.
Google Tasks / Microsoft To Do are out of scope by design (proprietary, would Google Tasks / Microsoft To Do are out of scope by design (proprietary, would

View File

@@ -1,6 +1,6 @@
# Floret — documentation # Agendula — documentation
Floret is a Material 3 Expressive **task** app for Android: a pure front-end over Agendula is a Material 3 Expressive **task** app for Android: a pure front-end over
the OpenTasks `TaskContract` provider (synced by DAVx5 / SmoothSync / DecSync the OpenTasks `TaskContract` provider (synced by DAVx5 / SmoothSync / DecSync
over CalDAV), with no own database or sync stack. Sibling to over CalDAV), with no own database or sync stack. Sibling to
[Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula). See the [Calendula](https://gitea.jeanlucmakiola.de/makiolaj/calendula). See the
@@ -10,7 +10,7 @@ top-level [`../README.md`](../README.md) for the project pitch.
| Doc | What it covers | | Doc | What it covers |
|---|---| |---|---|
| [`ARCHITECTURE.md`](ARCHITECTURE.md) | How Floret is built **today** — layers, the data seam, provider resolution, the reminder engine, DI, build/tooling, manifest. Start here to work on the code. | | [`ARCHITECTURE.md`](ARCHITECTURE.md) | How Agendula is built **today** — layers, the data seam, provider resolution, the reminder engine, DI, build/tooling, manifest. Start here to work on the code. |
| [`ROADMAP.md`](ROADMAP.md) | **Status** and what's next — milestones (M0M6 + Posture B), what's done, open decisions, how to build/verify. | | [`ROADMAP.md`](ROADMAP.md) | **Status** and what's next — milestones (M0M6 + Posture B), what's done, open decisions, how to build/verify. |
| [`PLAN.md`](PLAN.md) | The original implementation plan and **design rationale** — the A-now-B-later thesis, what transfers from Calendula, the locked decisions. The "why". | | [`PLAN.md`](PLAN.md) | The original implementation plan and **design rationale** — the A-now-B-later thesis, what transfers from Calendula, the locked decisions. The "why". |
| [`RELEASING.md`](RELEASING.md) | How to cut a release — the git-tag-as-source-of-truth flow, CI jobs, F-Droid repo, required secrets. | | [`RELEASING.md`](RELEASING.md) | How to cut a release — the git-tag-as-source-of-truth flow, CI jobs, F-Droid repo, required secrets. |

View File

@@ -1,6 +1,6 @@
# Floret — releasing # Agendula — releasing
Floret is distributed through a **self-hosted F-Droid repo** (on Hetzner) with a Agendula is distributed through a **self-hosted F-Droid repo** (on Hetzner) with a
human-readable **Gitea release** per tag. Both are produced automatically by human-readable **Gitea release** per tag. Both are produced automatically by
`.gitea/workflows/release.yaml` when you push a tag. There are no APK assets on `.gitea/workflows/release.yaml` when you push a tag. There are no APK assets on
the Gitea release itself — distribution lives in the F-Droid repo; the release is the Gitea release itself — distribution lives in the F-Droid repo; the release is
@@ -94,7 +94,7 @@ Keep this green before tagging.
## F-Droid metadata ## F-Droid metadata
App store listing lives in `fdroid-metadata/` (`de.jeanlucmakiola.floret.yml` App store listing lives in `fdroid-metadata/` (`de.jeanlucmakiola.agendula.yml`
plus `en-US/` summary/description). Per-version changelogs are generated into the plus `en-US/` summary/description). Per-version changelogs are generated into the
repo's `metadata/.../en-US/changelogs/<versionCode>.txt` from `CHANGELOG.md` at repo's `metadata/.../en-US/changelogs/<versionCode>.txt` from `CHANGELOG.md` at
release time; metadata is uploaded alongside `repo/` so changelog history release time; metadata is uploaded alongside `repo/` so changelog history

View File

@@ -1,4 +1,4 @@
# Floret — roadmap # Agendula — roadmap
Where the project is and where it's going. This is the **status** view; the Where the project is and where it's going. This is the **status** view; the
design rationale behind each milestone lives in [`PLAN.md`](PLAN.md), and how the design rationale behind each milestone lives in [`PLAN.md`](PLAN.md), and how the
@@ -50,7 +50,7 @@ The real Material 3 Expressive UI, built screen by screen against the M1
ViewModels. ViewModels.
- ✅ Provider/permission onboarding gate (`RootScreen`). - ✅ Provider/permission onboarding gate (`RootScreen`).
- ✅ Lists overview (`ListsScreen`) — smart lists + user lists grouped by account. - ✅ Lists overview (`ListsScreen`) — smart lists + user lists grouped by account.
- ✅ Navigation host wiring (`FloretNavHost` + `Dest` route table: lists → task - ✅ Navigation host wiring (`AgendulaNavHost` + `Dest` route table: lists → task
list → detail / edit, each binding its M1 ViewModel from route args). list → detail / edit, each binding its M1 ViewModel from route args).
- ✅ Task list screen — checkbox rows + toggle-complete, swipe-to-complete / - ✅ Task list screen — checkbox rows + toggle-complete, swipe-to-complete /
-delete (`SwipeToDismissBox`), inline add field (`InlineAdd``quickAdd`), -delete (`SwipeToDismissBox`), inline add field (`InlineAdd``quickAdd`),
@@ -140,12 +140,12 @@ permissions; ship self-contained. UI / repository / domain untouched — see
These carry over from [`PLAN.md`](PLAN.md) §9; resolved ones are struck through. These carry over from [`PLAN.md`](PLAN.md) §9; resolved ones are struck through.
1. ~~**Name** — `Floret`~~ confirmed (appId `de.jeanlucmakiola.floret`). 1. ~~**Name** — `Agendula`~~ confirmed (appId `de.jeanlucmakiola.agendula`).
2. ~~**tasks.org provider authority**~~ verified on device: 2. ~~**tasks.org provider authority**~~ verified on device:
`org.tasks.opentasks` + `org.tasks.permission.*`. `org.tasks.opentasks` + `org.tasks.permission.*`.
3. **jtx Board** — support its richer contract later, or stay OpenTasks-only? 3. **jtx Board** — support its richer contract later, or stay OpenTasks-only?
(Not in the candidate list today.) (Not in the candidate list today.)
4. **Posture B authority choice** — bundling `org.dmfs.tasks` makes Floret a 4. **Posture B authority choice** — bundling `org.dmfs.tasks` makes Agendula a
*replacement* for OpenTasks (one authority owner per device). Intended, but a *replacement* for OpenTasks (one authority owner per device). Intended, but a
conscious choice. conscious choice.
5. **Recurring tasks** — read as occurrences today (`isRecurring` flag exists); 5. **Recurring tasks** — read as occurrences today (`isRecurring` flag exists);

View File

@@ -0,0 +1,10 @@
AuthorName: Jean-Luc Makiola
License: MIT
Name: Agendula
Summary: A modern Material 3 Expressive task app for Android.
Categories:
- Time
SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/agendula
IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/agendula/issues

View File

@@ -1,4 +1,4 @@
Floret is a modern, open-source task app for Android. It works directly on an Agendula is a modern, open-source task app for Android. It works directly on an
existing tasks provider (OpenTasks / tasks.org), so any CalDAV tasks synced to existing tasks provider (OpenTasks / tasks.org), so any CalDAV tasks synced to
your device via DAVx5, SmoothSync or DecSync show up automatically, and changes your device via DAVx5, SmoothSync or DecSync show up automatically, and changes
you make sync back the same way — no own account, no own sync. you make sync back the same way — no own account, no own sync.

View File

@@ -1,10 +0,0 @@
AuthorName: Jean-Luc Makiola
License: MIT
Name: Floret
Summary: A modern Material 3 Expressive task app for Android.
Categories:
- Time
SourceCode: https://gitea.jeanlucmakiola.de/makiolaj/floret
IssueTracker: https://gitea.jeanlucmakiola.de/makiolaj/floret/issues

View File

@@ -23,5 +23,5 @@ dependencyResolutionManagement {
} }
} }
rootProject.name = "Floret" rootProject.name = "Agendula"
include(":app") include(":app")