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")