From cce831a2ad4135a3b6dd3e63906dfe48740086dc Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Thu, 15 Oct 2026 17:35:30 +0200 Subject: [PATCH] Use floret's info card, onboarding headline and language row --- .../calendula/ui/detail/EventDetailScreen.kt | 50 ++------- .../calendula/ui/edit/EventEditScreen.kt | 104 +++--------------- .../calendula/ui/onboarding/BackupStep.kt | 31 +----- .../calendula/ui/onboarding/CalendarsStep.kt | 54 ++------- .../calendula/ui/onboarding/DoneStep.kt | 37 ++----- .../ui/onboarding/HiddenCalendarsStep.kt | 29 ++--- .../ui/onboarding/PermissionScreen.kt | 71 +++--------- .../calendula/ui/onboarding/ReminderStep.kt | 31 +----- .../calendula/ui/onboarding/VisibilityStep.kt | 37 ++----- .../calendula/ui/settings/SettingsScreen.kt | 77 ++++--------- 10 files changed, 106 insertions(+), 415 deletions(-) diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt index eb8fcc45..1e26b19b 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/detail/EventDetailScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row @@ -17,7 +16,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape @@ -58,7 +56,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString @@ -92,6 +89,7 @@ import de.jeanlucmakiola.calendula.domain.RichText import de.jeanlucmakiola.calendula.domain.TimeZoneOption import de.jeanlucmakiola.calendula.domain.timeZoneOptionOf import de.jeanlucmakiola.calendula.domain.zoneDescriptor +import de.jeanlucmakiola.floret.components.IconInfoCard import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme import de.jeanlucmakiola.floret.identity.predictiveBack import de.jeanlucmakiola.calendula.ui.common.CalendarFailure @@ -384,7 +382,7 @@ private fun EventDetailContent( // "When" — date/all-day plus the time range. val (whenPrimary, whenSecondary) = rememberWhen(instance, TimeZone.currentSystemDefault(), locale) - DetailCard(icon = Icons.Default.Schedule, iconContentDescription = null) { + IconInfoCard(icon = Icons.Default.Schedule, iconContentDescription = null) { Text(text = whenPrimary, style = MaterialTheme.typography.titleMedium) if (whenSecondary != null) { Spacer(Modifier.height(2.dp)) @@ -409,7 +407,7 @@ private fun EventDetailContent( } foreignZone?.let { zoneOption -> Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.Public, iconContentDescription = stringResource(R.string.event_detail_timezone), ) { @@ -441,7 +439,7 @@ private fun EventDetailContent( // Calendar — icon tinted in the calendar colour conveys identity, so no // separate colour dot is needed. Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.CalendarMonth, iconTint = accent, iconContentDescription = stringResource(R.string.event_detail_calendar), @@ -459,7 +457,7 @@ private fun EventDetailContent( val context = LocalContext.current val locationLabel = stringResource(R.string.event_detail_location) Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.Place, iconContentDescription = locationLabel, ) { @@ -486,7 +484,7 @@ private fun EventDetailContent( state.description?.let { description -> val descriptionLabel = stringResource(R.string.event_detail_description) Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.AutoMirrored.Filled.Notes, iconContentDescription = descriptionLabel, // The gesture sits on the card so the icon and padding answer @@ -505,7 +503,7 @@ private fun EventDetailContent( // each attendee with their role and reply. detail.attendees.takeIf { it.isNotEmpty() }?.let { attendees -> Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.People, iconContentDescription = stringResource(R.string.event_detail_attendees), ) { @@ -527,7 +525,7 @@ private fun EventDetailContent( // Reminders (conditional) — list each lead time before the event. detail.reminders.takeIf { it.isNotEmpty() }?.let { reminders -> Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.Notifications, iconContentDescription = stringResource(R.string.event_detail_reminders), ) { @@ -547,7 +545,7 @@ private fun EventDetailContent( // Recurrence (conditional) — humanised from the RRULE. detail.rrule?.takeIf { it.isNotBlank() }?.let { rrule -> Spacer(Modifier.height(gap)) - DetailCard( + IconInfoCard( icon = Icons.Default.Repeat, iconContentDescription = stringResource(R.string.event_detail_recurrence), ) { @@ -560,36 +558,6 @@ private fun EventDetailContent( } } -/** One info card: tonal container, leading icon in the gutter, value to the right. */ -@Composable -private fun DetailCard( - icon: ImageVector, - iconContentDescription: String?, - modifier: Modifier = Modifier, - iconTint: Color = MaterialTheme.colorScheme.onSurfaceVariant, - content: @Composable ColumnScope.() -> Unit, -) { - Surface( - color = MaterialTheme.colorScheme.surfaceContainerHigh, - shape = RoundedCornerShape(16.dp), - modifier = modifier.fillMaxWidth(), - ) { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - imageVector = icon, - contentDescription = iconContentDescription, - tint = iconTint, - modifier = Modifier.size(24.dp), - ) - Spacer(Modifier.width(16.dp)) - Column(modifier = Modifier.weight(1f), content = content) - } - } -} - @Composable private fun AttendeeRow(attendee: Attendee) { Row( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt index 9c99bb78..f1b7a1e0 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/edit/EventEditScreen.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.Notes import androidx.compose.material.icons.filled.Add +import de.jeanlucmakiola.floret.components.IconInfoCard import de.jeanlucmakiola.floret.identity.LocalFloretDarkTheme import kotlinx.coroutines.launch import de.jeanlucmakiola.calendula.ui.common.sendEventShare @@ -843,7 +844,7 @@ private fun EventEditContent( // "When" card: the icon centres on the all-day row (header); the // start/end rows continue below in the same text column. - EditCard( + IconInfoCard( icon = Icons.Default.Schedule, iconContentDescription = null, header = { @@ -929,7 +930,7 @@ private fun EventEditContent( // different one while editing *moves* the event (copy+delete on save, // since CALENDAR_ID can't be updated in place). Managed special-dates // events stay locked: the contact sync owns their calendar. - EditCard( + IconInfoCard( icon = Icons.Default.CalendarMonth, iconContentDescription = stringResource(R.string.event_detail_calendar), iconTint = accent, @@ -960,7 +961,7 @@ private fun EventEditContent( // rest unfold behind the "more fields" button below. OptionalFormSection(visible = EventFormField.Timezone in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.Default.Public, iconContentDescription = null, onClick = { showTimezonePicker = true }, @@ -1008,10 +1009,11 @@ private fun EventEditContent( OptionalFormSection(visible = EventFormField.Location in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.Default.Place, iconContentDescription = stringResource(R.string.event_detail_location), - iconAtTop = true, + iconAlignment = Alignment.Top, + iconTopPadding = ((firstLineHeight() - 24.dp) / 2).coerceAtLeast(0.dp), ) { Row(verticalAlignment = Alignment.Top) { Column(modifier = Modifier.weight(1f)) { @@ -1077,10 +1079,11 @@ private fun EventEditContent( OptionalFormSection(visible = EventFormField.Description in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.AutoMirrored.Filled.Notes, iconContentDescription = stringResource(R.string.event_detail_description), - iconAtTop = true, + iconAlignment = Alignment.Top, + iconTopPadding = ((firstLineHeight() - 24.dp) / 2).coerceAtLeast(0.dp), ) { InlineField( value = form.description, @@ -1116,7 +1119,7 @@ private fun EventEditContent( OptionalFormSection(visible = EventFormField.Recurrence in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.Default.Repeat, iconContentDescription = null, onClick = if (locked) null else ({ showRecurrencePicker = true }), @@ -1156,7 +1159,7 @@ private fun EventEditContent( OptionalFormSection(visible = EventFormField.Availability in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.Default.EventAvailable, iconContentDescription = null, header = { @@ -1184,7 +1187,7 @@ private fun EventEditContent( OptionalFormSection(visible = EventFormField.Visibility in state.visibleFields) { Spacer(Modifier.height(gap)) - EditCard( + IconInfoCard( icon = Icons.Default.Lock, iconContentDescription = null, onClick = { showVisibilityPicker = true }, @@ -1218,7 +1221,7 @@ private fun EventEditContent( // The swatch the event will paint with: its own colour, else the // calendar's. The Palette icon takes that colour as a preview. val swatch = form.color ?: selectedCalendar?.color - EditCard( + IconInfoCard( icon = Icons.Default.Palette, iconContentDescription = stringResource(R.string.event_edit_color), iconTint = if (colorSupported && swatch != null) { @@ -2344,85 +2347,6 @@ private fun accessLevelSummary(level: AccessLevel): Int = when (level) { @Composable private fun reminderLabel(minutes: Int): String = reminderLeadTimeLabel(minutes) -/** - * One info card mirroring the detail screen's DetailCard: tonal container, - * leading icon in the gutter, value to the right. Optionally clickable as a - * whole (calendar picker). - */ -@Composable -private fun EditCard( - icon: ImageVector, - iconContentDescription: String?, - iconTint: Color = MaterialTheme.colorScheme.onSurfaceVariant, - onClick: (() -> Unit)? = null, - // Multiline-input cards align the icon with the field's first text line. - iconAtTop: Boolean = false, - // Tall cards pass their first row here: the icon centres on it, and - // [content] continues below, indented to the same text column. - header: (@Composable ColumnScope.() -> Unit)? = null, - content: @Composable ColumnScope.() -> Unit, -) { - val shape = RoundedCornerShape(16.dp) - val color = MaterialTheme.colorScheme.surfaceContainerHigh - val inner: @Composable () -> Unit = { - if (header != null) { - Column(modifier = Modifier.padding(16.dp)) { - Row(verticalAlignment = Alignment.CenterVertically) { - Icon( - imageVector = icon, - contentDescription = iconContentDescription, - tint = iconTint, - modifier = Modifier.size(24.dp), - ) - Spacer(Modifier.width(16.dp)) - Column(modifier = Modifier.weight(1f), content = header) - } - Column( - modifier = Modifier.padding(start = 40.dp), - content = content, - ) - } - } else { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = if (iconAtTop) Alignment.Top else Alignment.CenterVertically, - ) { - // A top-aligned icon centres on the first text line, whatever - // that line measures at the user's font scale. - val iconOffset = if (iconAtTop) { - ((firstLineHeight() - 24.dp) / 2).coerceAtLeast(0.dp) - } else { - 0.dp - } - Icon( - imageVector = icon, - contentDescription = iconContentDescription, - tint = iconTint, - modifier = Modifier - .padding(top = iconOffset) - .size(24.dp), - ) - Spacer(Modifier.width(16.dp)) - Column(modifier = Modifier.weight(1f), content = content) - } - } - } - if (onClick != null) { - Surface( - onClick = onClick, - color = color, - shape = shape, - modifier = Modifier.fillMaxWidth(), - ) { inner() } - } else { - Surface( - color = color, - shape = shape, - modifier = Modifier.fillMaxWidth(), - ) { inner() } - } -} - /** * "2:00 PM – 3:00 PM" in [zone], honouring the user's 12/24-hour setting. Collapses * to one time for an instant event, which shouldn't read as a range against diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/BackupStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/BackupStep.kt index 87627f12..65001ed1 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/BackupStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/BackupStep.kt @@ -10,17 +10,14 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.CloudOff import androidx.compose.material.icons.filled.FolderOpen import androidx.compose.material.icons.filled.Schedule -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R import de.jeanlucmakiola.floret.components.BenefitRow +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -53,15 +50,10 @@ internal fun BackupStep( topSpacing = OnboardingSpace.lg, hero = { IconHero(Icons.Filled.CloudOff) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.onboarding_backup_enable_button), onClick = { runCatching { pickFolder.launch(null) } }, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.onboarding_backup_enable_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) TextButton( onClick = onSkip, modifier = Modifier.fillMaxWidth(), @@ -69,20 +61,9 @@ internal fun BackupStep( Text(stringResource(R.string.onboarding_backup_skip_button)) } }, + headline = stringResource(R.string.onboarding_backup_title), + supporting = stringResource(R.string.onboarding_backup_body), ) { - Text( - text = stringResource(R.string.onboarding_backup_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.onboarding_backup_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(OnboardingSpace.lg)) BenefitRow( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/CalendarsStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/CalendarsStep.kt index 97984eb4..ab37d675 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/CalendarsStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/CalendarsStep.kt @@ -9,7 +9,6 @@ import androidx.compose.material.icons.filled.CheckCircle import androidx.compose.material.icons.filled.CloudSync import androidx.compose.material.icons.filled.PhoneAndroid import androidx.compose.material.icons.filled.Settings -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Text @@ -30,6 +29,7 @@ import de.jeanlucmakiola.calendula.data.calendar.CalendarColorPalette import de.jeanlucmakiola.calendula.ui.calendars.CalendarEditor import de.jeanlucmakiola.calendula.ui.settings.openUrl import de.jeanlucmakiola.floret.components.BenefitRow +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -108,18 +108,13 @@ internal fun CalendarsStep( topSpacing = OnboardingSpace.lg, hero = { IconHero(Icons.Filled.CalendarMonth) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.onboarding_calendars_local_button), onClick = { onClearCreation() creating = true }, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.onboarding_calendars_local_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) OutlinedButton( onClick = { openUrl(context, DAVX5_URL) }, modifier = Modifier.fillMaxWidth().height(56.dp), @@ -133,20 +128,9 @@ internal fun CalendarsStep( Text(stringResource(R.string.onboarding_calendars_skip_button)) } }, + headline = stringResource(R.string.onboarding_calendars_title), + supporting = stringResource(R.string.onboarding_calendars_body), ) { - Text( - text = stringResource(R.string.onboarding_calendars_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.onboarding_calendars_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - if (creation is CalendarCreation.Failed) { Spacer(Modifier.height(12.dp)) Text( @@ -189,30 +173,14 @@ private fun CalendarCreated( topSpacing = OnboardingSpace.lg, hero = { IconHero(Icons.Filled.CheckCircle) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.onboarding_calendars_created_continue), onClick = onContinue, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.onboarding_calendars_created_continue), - style = MaterialTheme.typography.titleMedium, - ) - } + ) }, + headline = stringResource(R.string.onboarding_calendars_created_title, name), + supporting = stringResource(R.string.onboarding_calendars_created_body), ) { - Text( - text = stringResource(R.string.onboarding_calendars_created_title, name), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.onboarding_calendars_created_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(OnboardingSpace.lg)) BenefitRow( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/DoneStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/DoneStep.kt index 59c5154b..78e78638 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/DoneStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/DoneStep.kt @@ -1,17 +1,10 @@ package de.jeanlucmakiola.calendula.ui.onboarding -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -34,28 +27,12 @@ internal fun DoneStep( topSpacing = OnboardingSpace.lg, hero = { BrandHero(HeroBadge.Done) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.onboarding_done_button), onClick = onFinished, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.onboarding_done_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) }, - ) { - Text( - text = stringResource(R.string.onboarding_done_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.onboarding_done_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - } + headline = stringResource(R.string.onboarding_done_title), + supporting = stringResource(R.string.onboarding_done_body), + ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt index 472d336e..86b57758 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/HiddenCalendarsStep.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.Button import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch import androidx.compose.material3.Text @@ -25,6 +24,7 @@ import de.jeanlucmakiola.calendula.domain.hasVisibilitySwitch import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip import de.jeanlucmakiola.floret.components.GroupedListInset import de.jeanlucmakiola.floret.components.GroupedRow +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace import de.jeanlucmakiola.floret.components.Position @@ -65,24 +65,17 @@ internal fun HiddenCalendarsStep( scrollingActions = true, hero = { IconHero(Icons.Filled.VisibilityOff) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource( + if (anyVisible) { + R.string.onboarding_view_continue_button + } else { + R.string.onboarding_hidden_switch_all_button + }, + ), onClick = if (anyVisible) onFinished else onSwitchAllOn, - modifier = Modifier - .padding(horizontal = GroupedListInset) - .fillMaxWidth() - .height(56.dp), - ) { - Text( - text = stringResource( - if (anyVisible) { - R.string.onboarding_view_continue_button - } else { - R.string.onboarding_hidden_switch_all_button - }, - ), - style = MaterialTheme.typography.titleMedium, - ) - } + modifier = Modifier.padding(horizontal = GroupedListInset), + ) // Only while nothing is on: once something is, the step has been // answered and the one button left to press says so. if (!anyVisible) { diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/PermissionScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/PermissionScreen.kt index 0df89a93..7df5f0a4 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/PermissionScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/PermissionScreen.kt @@ -1,6 +1,7 @@ package de.jeanlucmakiola.calendula.ui.onboarding import de.jeanlucmakiola.floret.components.BenefitRow +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -13,16 +14,10 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.filled.CalendarMonth import androidx.compose.material.icons.filled.Lock -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable @@ -33,8 +28,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R private val CALENDAR_PERMISSIONS = arrayOf( @@ -93,37 +86,15 @@ private fun RationaleContent( topSpacing = OnboardingSpace.lg, hero = { BrandHero() }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.permission_request_button), onClick = onRequest, - modifier = Modifier.fillMaxWidth().height(56.dp), - contentPadding = ButtonDefaults.ButtonWithIconContentPadding, - ) { - Text( - text = stringResource(R.string.permission_request_button), - style = MaterialTheme.typography.titleMedium, - ) - Spacer(Modifier.width(OnboardingSpace.xs)) - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowForward, - contentDescription = null, - modifier = Modifier.size(20.dp), - ) - } + trailingIcon = Icons.AutoMirrored.Filled.ArrowForward, + ) }, + headline = stringResource(R.string.permission_rationale_title), + supporting = stringResource(R.string.permission_rationale_body), ) { - Text( - text = stringResource(R.string.permission_rationale_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.permission_rationale_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(OnboardingSpace.lg)) BenefitRow( @@ -152,7 +123,8 @@ private fun DeniedContent( progress = progress, hero = { BrandHero(HeroBadge.Denied) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.permission_open_settings_button), onClick = { val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.fromParts("package", context.packageName, null) @@ -160,13 +132,7 @@ private fun DeniedContent( } context.startActivity(intent) }, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.permission_open_settings_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) TextButton( onClick = onRetry, modifier = Modifier.fillMaxWidth(), @@ -174,18 +140,7 @@ private fun DeniedContent( Text(stringResource(R.string.permission_retry_button)) } }, - ) { - Text( - text = stringResource(R.string.permission_denied_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.permission_denied_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - } + headline = stringResource(R.string.permission_denied_title), + supporting = stringResource(R.string.permission_denied_body), + ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ReminderStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ReminderStep.kt index ef53c5a7..f5f121a9 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ReminderStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/ReminderStep.kt @@ -1,6 +1,7 @@ package de.jeanlucmakiola.calendula.ui.onboarding import de.jeanlucmakiola.floret.components.BenefitRow +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -14,15 +15,11 @@ import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.NotificationsActive import androidx.compose.material.icons.filled.ContentCopy -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R /** @@ -53,7 +50,8 @@ fun ReminderStep( topSpacing = OnboardingSpace.lg, hero = { IconHero(Icons.Filled.NotificationsActive) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.reminder_onboarding_enable_button), onClick = { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { launcher.launch(Manifest.permission.POST_NOTIFICATIONS) @@ -61,13 +59,7 @@ fun ReminderStep( onFinished(true) } }, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.reminder_onboarding_enable_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) TextButton( onClick = { onFinished(false) }, modifier = Modifier.fillMaxWidth(), @@ -75,20 +67,9 @@ fun ReminderStep( Text(stringResource(R.string.reminder_onboarding_skip_button)) } }, + headline = stringResource(R.string.reminder_onboarding_title), + supporting = stringResource(R.string.reminder_onboarding_body), ) { - Text( - text = stringResource(R.string.reminder_onboarding_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.reminder_onboarding_body), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(OnboardingSpace.lg)) BenefitRow( diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/VisibilityStep.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/VisibilityStep.kt index d509766d..54dfe9d7 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/VisibilityStep.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/onboarding/VisibilityStep.kt @@ -1,19 +1,12 @@ package de.jeanlucmakiola.calendula.ui.onboarding -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.VisibilityOff -import androidx.compose.material3.Button -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp import de.jeanlucmakiola.calendula.R +import de.jeanlucmakiola.floret.components.OnboardingPrimaryButton import de.jeanlucmakiola.floret.components.OnboardingScaffold import de.jeanlucmakiola.floret.components.OnboardingSpace @@ -38,28 +31,12 @@ internal fun VisibilityStep( topSpacing = OnboardingSpace.lg, hero = { IconHero(Icons.Filled.VisibilityOff) }, actions = { - Button( + OnboardingPrimaryButton( + text = stringResource(R.string.onboarding_visibility_button), onClick = onFinished, - modifier = Modifier.fillMaxWidth().height(56.dp), - ) { - Text( - text = stringResource(R.string.onboarding_visibility_button), - style = MaterialTheme.typography.titleMedium, - ) - } + ) }, - ) { - Text( - text = stringResource(R.string.calendars_visibility_notice_title), - style = MaterialTheme.typography.headlineMedium, - textAlign = TextAlign.Center, - ) - Spacer(Modifier.height(12.dp)) - Text( - text = stringResource(R.string.calendars_visibility_notice_message), - style = MaterialTheme.typography.bodyLarge, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.Center, - ) - } + headline = stringResource(R.string.calendars_visibility_notice_title), + supporting = stringResource(R.string.calendars_visibility_notice_message), + ) } diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt index a8e5b863..2a6c1f70 100644 --- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/settings/SettingsScreen.kt @@ -59,14 +59,13 @@ import de.jeanlucmakiola.floret.components.CollapsingScaffold import de.jeanlucmakiola.floret.components.GroupedListInset import de.jeanlucmakiola.floret.components.GroupedRow import de.jeanlucmakiola.floret.components.GroupedSectionHeader -import de.jeanlucmakiola.floret.components.OptionPicker +import de.jeanlucmakiola.floret.components.LanguagePickerRow import de.jeanlucmakiola.floret.components.Position import de.jeanlucmakiola.floret.crash.CrashReportDialog import de.jeanlucmakiola.floret.crash.CrashReporter import de.jeanlucmakiola.floret.crash.openIssueTracker import de.jeanlucmakiola.floret.crash.submitCrashReport import de.jeanlucmakiola.floret.identity.predictiveBackExit -import de.jeanlucmakiola.floret.locale.AppLanguage /** The settings sub-screens reached from the hub's category rows. */ private enum class SettingsSection { @@ -256,7 +255,27 @@ private fun SettingsHub( leading = { CategoryIcon(Icons.Default.Widgets, ChipAccent.Tertiary) }, onClick = { onOpenSection(SettingsSection.Widgets) }, ) - LanguageRow(position = Position.Middle) + LanguagePickerRow( + position = Position.Middle, + title = stringResource(R.string.settings_language), + autoLabel = stringResource(R.string.settings_language_auto), + localesConfig = R.xml.locales_config, + predictiveBack = true, + // Invite contributions right where users pick their language. + header = { + val context = LocalContext.current + val translateUrl = stringResource(R.string.about_translate_url) + GroupedRow( + title = stringResource(R.string.settings_translate), + summary = stringResource(R.string.settings_translate_hint), + position = Position.Alone, + leading = { CategoryIcon(Icons.Default.Translate, ChipAccent.Neutral) }, + onClick = { openUrl(context, translateUrl) }, + ) + Spacer(Modifier.height(16.dp)) + }, + leading = { CategoryIcon(Icons.Default.Language, ChipAccent.Secondary) }, + ) ReportProblemRow(position = Position.Bottom) // Source, licence and privacy sit at the bottom as reference material @@ -328,58 +347,6 @@ private fun ReportProblemRow(position: Position) { } } -@Composable -private fun LanguageRow(position: Position) { - val context = LocalContext.current - val supported = remember { AppLanguage.supportedTags(context, R.xml.locales_config) } - // Setting a locale recreates the activity; mirror the choice locally so the - // row updates instantly even before the recreation lands. - var current by remember { mutableStateOf(AppLanguage.currentTag(supported)) } - var showDialog by remember { mutableStateOf(false) } - - // null = follow the system; the rest are BCP-47 tags from locales_config.xml. - val options = remember(supported) { listOf(null) + supported } - - GroupedRow( - title = stringResource(R.string.settings_language), - summary = languageLabel(current), - position = position, - leading = { CategoryIcon(Icons.Default.Language, ChipAccent.Secondary) }, - onClick = { showDialog = true }, - ) - - if (showDialog) { - OptionPicker( - title = stringResource(R.string.settings_language), - predictiveBack = true, - options = options, - selected = current, - label = { languageLabel(it) }, - onSelect = { - current = it - AppLanguage.apply(it) - }, - onDismiss = { showDialog = false }, - // Invite contributions right where users pick their language. - header = { - val translateUrl = stringResource(R.string.about_translate_url) - GroupedRow( - title = stringResource(R.string.settings_translate), - summary = stringResource(R.string.settings_translate_hint), - position = Position.Alone, - leading = { CategoryIcon(Icons.Default.Translate, ChipAccent.Neutral) }, - onClick = { openUrl(context, translateUrl) }, - ) - Spacer(Modifier.height(16.dp)) - }, - ) - } -} - -@Composable -private fun languageLabel(tag: String?): String = - if (tag == null) stringResource(R.string.settings_language_auto) else AppLanguage.displayName(tag) - @Composable private fun AboutCard() { // Layout lives in floret-kit (components.AboutCard); Calendula supplies