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:
@@ -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"
|
||||||
|
|||||||
@@ -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`):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
14
README.md
14
README.md
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -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 =
|
||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.jeanlucmakiola.floret.data.di
|
package de.jeanlucmakiola.agendula.data.di
|
||||||
|
|
||||||
import javax.inject.Qualifier
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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).
|
||||||
*/
|
*/
|
||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.jeanlucmakiola.floret.data.tasks
|
package de.jeanlucmakiola.agendula.data.tasks
|
||||||
|
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
|
||||||
@@ -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 :
|
||||||
@@ -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(
|
||||||
@@ -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. */
|
||||||
@@ -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 {
|
||||||
@@ -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 name→value map. Pure (no ContentValues),
|
* Turns a [TaskForm] / mutation into a name→value map. Pure (no ContentValues),
|
||||||
@@ -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"
|
||||||
@@ -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(
|
||||||
@@ -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 }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.jeanlucmakiola.floret.domain
|
package de.jeanlucmakiola.agendula.domain
|
||||||
|
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@@ -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,
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.jeanlucmakiola.floret.domain
|
package de.jeanlucmakiola.agendula.domain
|
||||||
|
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@@ -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 {
|
||||||
@@ -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,
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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) {
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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,
|
||||||
@@ -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
|
||||||
@@ -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) },
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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()
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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].
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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).
|
||||||
*
|
*
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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 }
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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),
|
||||||
@@ -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,
|
||||||
)
|
)
|
||||||
@@ -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()
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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 {
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package de.jeanlucmakiola.floret.domain
|
package de.jeanlucmakiola.agendula.domain
|
||||||
|
|
||||||
import kotlin.time.Instant
|
import kotlin.time.Instant
|
||||||
|
|
||||||
@@ -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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
45
docs/PLAN.md
45
docs/PLAN.md
@@ -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
|
||||||
|
|||||||
@@ -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 (M0–M6 + Posture B), what's done, open decisions, how to build/verify. |
|
| [`ROADMAP.md`](ROADMAP.md) | **Status** and what's next — milestones (M0–M6 + 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. |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
10
fdroid-metadata/de.jeanlucmakiola.agendula.yml
Normal file
10
fdroid-metadata/de.jeanlucmakiola.agendula.yml
Normal 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
|
||||||
@@ -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.
|
||||||
@@ -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
|
|
||||||
@@ -23,5 +23,5 @@ dependencyResolutionManagement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "Floret"
|
rootProject.name = "Agendula"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
Reference in New Issue
Block a user