Files
agendula/app/src/main/java/de/jeanlucmakiola/agendula/ui/RootScreen.kt
Jean-Luc Makiola 76a0139fda
All checks were successful
CI / ci (push) Successful in 8m33s
Rename app Floret → Agendula
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>
2026-06-27 23:10:24 +02:00

101 lines
3.9 KiB
Kotlin

package de.jeanlucmakiola.agendula.ui
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
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.data.tasks.ProviderStatus
import de.jeanlucmakiola.agendula.ui.navigation.AgendulaNavHost
import de.jeanlucmakiola.agendula.ui.permission.PermissionViewModel
import de.jeanlucmakiola.agendula.ui.permission.ReminderOnboardingScreen
import de.jeanlucmakiola.agendula.ui.permission.ReminderOnboardingViewModel
/**
* App root: gates on the tasks-provider permission, then hands off to
* [AgendulaNavHost] (lists → task list → detail / edit).
*/
@Composable
fun RootScreen(
modifier: Modifier = Modifier,
permissionViewModel: PermissionViewModel = hiltViewModel(),
) {
val permission by permissionViewModel.state.collectAsStateWithLifecycle()
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
) { permissionViewModel.refresh() }
when (permission.status) {
ProviderStatus.NO_PROVIDER -> Gate(
modifier = modifier,
title = stringResource(R.string.onboarding_no_provider_title),
body = stringResource(R.string.onboarding_no_provider_body),
)
ProviderStatus.NEEDS_PERMISSION -> Gate(
modifier = modifier,
title = stringResource(R.string.onboarding_permission_title),
body = stringResource(R.string.onboarding_permission_body),
action = stringResource(R.string.onboarding_permission_button),
onAction = { launcher.launch(permission.permissionsToRequest.toTypedArray()) },
)
ProviderStatus.READY -> ReadyGate(modifier = modifier)
}
}
/**
* Second one-time gate after the provider grant: the reminder onboarding step.
* [ReminderOnboardingViewModel.onboardingDone] is null until DataStore's first
* emission — render nothing for that frame rather than flash the wrong screen.
* A cross-fade eases the hand-off to the app instead of snapping.
*/
@Composable
private fun ReadyGate(
modifier: Modifier = Modifier,
onboardingViewModel: ReminderOnboardingViewModel = hiltViewModel(),
) {
val done by onboardingViewModel.onboardingDone.collectAsStateWithLifecycle()
Crossfade(targetState = done, label = "reminderOnboardingGate") { state ->
when (state) {
true -> AgendulaNavHost(modifier = modifier)
false -> ReminderOnboardingScreen(
onFinished = onboardingViewModel::finish,
modifier = modifier,
)
null -> {}
}
}
}
@Composable
private fun Gate(
title: String,
body: String,
modifier: Modifier = Modifier,
action: String? = null,
onAction: () -> Unit = {},
) {
Column(
modifier = modifier.fillMaxSize().padding(24.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterVertically),
) {
Text(title, style = MaterialTheme.typography.headlineSmall)
Text(body, style = MaterialTheme.typography.bodyMedium)
if (action != null) Button(onClick = onAction) { Text(action) }
}
}