diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml
index e672869..eb7ad29 100644
--- a/.gitea/workflows/ci.yaml
+++ b/.gitea/workflows/ci.yaml
@@ -21,6 +21,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: recursive
- name: Setup Java
uses: actions/setup-java@v4
diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml
index 2ea0bc3..0fef70f 100644
--- a/.gitea/workflows/release.yaml
+++ b/.gitea/workflows/release.yaml
@@ -15,6 +15,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: recursive
- name: Setup Java
uses: actions/setup-java@v4
@@ -73,6 +75,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: recursive
- name: Setup Java
uses: actions/setup-java@v4
@@ -322,6 +326,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
+ with:
+ submodules: recursive
- name: Extract changelog section for this tag
run: |
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index a142ec1..84b2285 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -120,6 +120,8 @@ dependencies {
implementation(libs.kotlinx.datetime)
implementation(libs.kotlinx.coroutines.core)
+ implementation("de.jeanlucmakiola.floret:core-time")
+ implementation("de.jeanlucmakiola.floret:core-crash")
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ca41302..6d37b60 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -52,6 +52,14 @@
+
+
+
(null)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ // If the app keeps crashing as it starts, the main UI can't be trusted
+ // to come up. Route to the standalone report screen instead of
+ // re-entering the crashing graph.
+ if (CrashReporter.isCrashLoop(this)) {
+ startActivity(Intent(this, CrashReportActivity::class.java))
+ finish()
+ return
+ }
+
enableEdgeToEdge()
+ // Surface a single captured crash as a dialog on the next launch.
+ if (CrashReporter.shouldPrompt(this)) pendingCrashReport = CrashReporter.pendingReport(this)
+
// Debug-only sample data: `am start ... --ez agendula_seed true`. Seeds a
// local (non-syncing) demo list once; no-op without the extra.
if (BuildConfig.DEBUG && intent.getBooleanExtra(EXTRA_SEED, false)) {
@@ -51,10 +75,31 @@ class MainActivity : ComponentActivity() {
}
AgendulaTheme(darkTheme = darkTheme, dynamicColor = ui.settings.dynamicColor) {
RootScreen(modifier = Modifier.fillMaxSize())
+ pendingCrashReport?.let { report ->
+ CrashReportDialog(
+ report = report,
+ onSend = {
+ submitCrashReport(this@MainActivity, report)
+ CrashReporter.clearReport(this@MainActivity)
+ pendingCrashReport = null
+ },
+ onDismiss = {
+ CrashReporter.dismissPrompt(this@MainActivity)
+ pendingCrashReport = null
+ },
+ )
+ }
}
}
}
+ override fun onResume() {
+ super.onResume()
+ // A successful start breaks any loop; reset the timing trail so a later
+ // ordinary crash isn't mistaken for a loop.
+ CrashReporter.markHealthy(this)
+ }
+
companion object {
const val EXTRA_TASK_ID = "de.jeanlucmakiola.agendula.extra.TASK_ID"
private const val EXTRA_SEED = "agendula_seed"
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/data/demo/DemoSeeder.kt b/app/src/main/java/de/jeanlucmakiola/agendula/data/demo/DemoSeeder.kt
index a97ec03..f3234ff 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/data/demo/DemoSeeder.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/data/demo/DemoSeeder.kt
@@ -2,7 +2,7 @@ package de.jeanlucmakiola.agendula.data.demo
import de.jeanlucmakiola.agendula.data.di.IoDispatcher
import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
-import de.jeanlucmakiola.agendula.domain.DayWindow
+import de.jeanlucmakiola.floret.time.DayWindow
import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.agendula.domain.TaskForm
import kotlinx.coroutines.CoroutineDispatcher
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/data/tasks/TasksRepositoryImpl.kt b/app/src/main/java/de/jeanlucmakiola/agendula/data/tasks/TasksRepositoryImpl.kt
index 0cc8aa5..4a4f80a 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/data/tasks/TasksRepositoryImpl.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/data/tasks/TasksRepositoryImpl.kt
@@ -1,7 +1,7 @@
package de.jeanlucmakiola.agendula.data.tasks
import de.jeanlucmakiola.agendula.data.di.IoDispatcher
-import de.jeanlucmakiola.agendula.domain.DayWindow
+import de.jeanlucmakiola.floret.time.DayWindow
import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.agendula.domain.TaskFilter
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeField.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeField.kt
index cfb6a1e..493f41f 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeField.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/common/DateTimeField.kt
@@ -1,5 +1,6 @@
package de.jeanlucmakiola.agendula.ui.common
+import de.jeanlucmakiola.floret.time.formatDateTime
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/crash/CrashReportActivity.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/crash/CrashReportActivity.kt
new file mode 100644
index 0000000..eb67637
--- /dev/null
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/crash/CrashReportActivity.kt
@@ -0,0 +1,61 @@
+package de.jeanlucmakiola.agendula.ui.crash
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.ui.Modifier
+import de.jeanlucmakiola.agendula.ui.theme.AgendulaTheme
+import de.jeanlucmakiola.floret.crash.CrashReportDialog
+import de.jeanlucmakiola.floret.crash.CrashReporter
+import de.jeanlucmakiola.floret.crash.submitCrashReport
+
+/**
+ * Standalone surface for a captured crash report. [MainActivity] routes here
+ * when it detects a startup crash-loop (see [CrashReporter.isCrashLoop]): the
+ * main UI can't be trusted to start, so this screen stays clear of the app's
+ * Hilt graph, DataStore-backed theme and Compose content — it only reads the
+ * report file and shows the kit's report dialog. Plain [AgendulaTheme] defaults
+ * (follow-system, dynamic colour) avoid touching anything that might be the
+ * cause of the crash. The reusable machinery lives in floret-kit's core-crash.
+ */
+class CrashReportActivity : ComponentActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val report = CrashReporter.pendingReport(this)
+ if (report == null) {
+ finish()
+ return
+ }
+ enableEdgeToEdge()
+ setContent {
+ AgendulaTheme {
+ // Opaque backdrop so the dialog doesn't float over a bare task.
+ Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.surface) {}
+ CrashReportDialog(
+ report = report,
+ onSend = {
+ submitCrashReport(this, report)
+ CrashReporter.clearReport(this)
+ finish()
+ },
+ onDismiss = {
+ CrashReporter.clearReport(this)
+ finish()
+ },
+ )
+ }
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ // Reaching this screen breaks the loop; reset the timing trail so a
+ // later ordinary crash isn't mistaken for a loop.
+ CrashReporter.markHealthy(this)
+ }
+}
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt
index ae5c97f..7cf3a8f 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/detail/TaskDetailScreen.kt
@@ -72,8 +72,8 @@ import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.agendula.domain.TaskDetail
import de.jeanlucmakiola.agendula.ui.common.GroupedSurface
import de.jeanlucmakiola.agendula.ui.common.PriorityChip
-import de.jeanlucmakiola.agendula.ui.common.formatDate
-import de.jeanlucmakiola.agendula.ui.common.formatTime
+import de.jeanlucmakiola.floret.time.formatDate
+import de.jeanlucmakiola.floret.time.formatTime
import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.agendula.ui.common.positionOf
import de.jeanlucmakiola.agendula.ui.tasklist.priorityLabel
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt
index 7afa13a..03730db 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/edit/TaskEditScreen.kt
@@ -86,7 +86,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R
-import de.jeanlucmakiola.agendula.domain.DayWindow
+import de.jeanlucmakiola.floret.time.DayWindow
import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.agendula.domain.TaskFormError
@@ -98,8 +98,8 @@ import de.jeanlucmakiola.agendula.ui.common.GroupedRow
import de.jeanlucmakiola.agendula.ui.common.InlineTextField
import de.jeanlucmakiola.agendula.ui.common.OptionCard
import de.jeanlucmakiola.agendula.ui.common.priorityFill
-import de.jeanlucmakiola.agendula.ui.common.formatDate
-import de.jeanlucmakiola.agendula.ui.common.formatTime
+import de.jeanlucmakiola.floret.time.formatDate
+import de.jeanlucmakiola.floret.time.formatTime
import de.jeanlucmakiola.agendula.ui.common.localToInstant
import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.agendula.ui.common.positionOf
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsViewModel.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsViewModel.kt
index 5af32fd..9fc2786 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsViewModel.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/lists/ListsViewModel.kt
@@ -4,7 +4,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import de.jeanlucmakiola.agendula.data.tasks.TasksRepository
-import de.jeanlucmakiola.agendula.domain.DayWindow
+import de.jeanlucmakiola.floret.time.DayWindow
import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.agendula.domain.Task
import de.jeanlucmakiola.agendula.domain.TaskFilter
diff --git a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt
index 9ccf40d..e2a7016 100644
--- a/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt
+++ b/app/src/main/java/de/jeanlucmakiola/agendula/ui/tasklist/TaskListScreen.kt
@@ -81,7 +81,7 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.agendula.R
-import de.jeanlucmakiola.agendula.domain.DayWindow
+import de.jeanlucmakiola.floret.time.DayWindow
import de.jeanlucmakiola.agendula.domain.Priority
import de.jeanlucmakiola.agendula.domain.SmartList
import de.jeanlucmakiola.agendula.domain.Task
@@ -91,7 +91,7 @@ import de.jeanlucmakiola.agendula.domain.TaskSections
import de.jeanlucmakiola.agendula.ui.common.ListNameChip
import de.jeanlucmakiola.agendula.ui.common.Position
import de.jeanlucmakiola.agendula.ui.common.PriorityChip
-import de.jeanlucmakiola.agendula.ui.common.formatDateTime
+import de.jeanlucmakiola.floret.time.formatDateTime
import de.jeanlucmakiola.agendula.ui.common.pastelize
import de.jeanlucmakiola.agendula.ui.common.positionOf
import java.time.ZoneId
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6f54d66..6e4f52d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -176,6 +176,7 @@
Report a problem
Open the issue tracker
https://gitea.jeanlucmakiola.de/makiolaj/agendula
+ Crash report
https://gitea.jeanlucmakiola.de/makiolaj/agendula/issues/new
https://gitea.jeanlucmakiola.de/makiolaj/agendula/src/branch/main/LICENSE
https://ko-fi.com/jeanlucmakiola
diff --git a/floret-kit b/floret-kit
index d3ae83f..19c2f8a 160000
--- a/floret-kit
+++ b/floret-kit
@@ -1 +1 @@
-Subproject commit d3ae83f1a49ebe8f5e5e5ae65894e78e0f93c08b
+Subproject commit 19c2f8a6778ba1971c56be4248dc6f6ac98f7dec
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 7b570ed..da21983 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -25,3 +25,4 @@ dependencyResolutionManagement {
rootProject.name = "Agendula"
include(":app")
+includeBuild("floret-kit")