All checks were successful
CI / ci (push) Successful in 8m33s
Floret is promoted to the family / design-language (shared-kit) name; the
tasks app itself becomes Agendula (de.jeanlucmakiola.agendula) — agenda
('things to be done') + Calendula's -ula, a twin of the Calendula name.
Renames the package, namespace, applicationId, rootProject.name, app_name,
FloretApp/FloretNavHost/FloretTransitions classes, theme, F-Droid metadata
dir, CI artifact name, and docs. The botanical word 'florets' is preserved in
the name-origin prose, which is rewritten to Agendula's etymology. Clean
build + unit tests green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
101 lines
3.9 KiB
Kotlin
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) }
|
|
}
|
|
}
|