Compare commits
4 Commits
3863e74857
...
5e470baf4e
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e470baf4e | |||
| c8a4f90404 | |||
| 0a89ff9d0e | |||
| 0718afd5a6 |
@@ -28,6 +28,7 @@ import androidx.compose.material3.MaterialTheme
|
|||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.rememberTopAppBarState
|
import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -57,10 +58,16 @@ fun positionOf(index: Int, count: Int): Position = when {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The app's standard full-screen list scaffold: a collapsing [LargeTopAppBar]
|
* The app's standard full-screen list scaffold. By default a collapsing
|
||||||
* whose title shrinks into the bar (next to the back button) as the content
|
* [LargeTopAppBar] whose title shrinks into the bar (next to the back button) as
|
||||||
* scrolls. Content is a scrollable column that feeds the toolbar via nested
|
* the content scrolls — used by Settings and the calendar manager, where the
|
||||||
* scroll. Used by Settings and the calendar manager so they share one shell.
|
* large header sets the page. Content is a scrollable column that feeds the
|
||||||
|
* toolbar via nested scroll.
|
||||||
|
*
|
||||||
|
* Set [largeTopBar] to false for a pinned, single-line [TopAppBar] instead: the
|
||||||
|
* title sits in the bar from the start, with no expanded header to scroll past.
|
||||||
|
* Preferred for selection pickers, where the tall header is just wasted space
|
||||||
|
* above the options.
|
||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -68,12 +75,16 @@ fun CollapsingScaffold(
|
|||||||
title: String,
|
title: String,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
largeTopBar: Boolean = true,
|
||||||
snackbarHost: @Composable () -> Unit = {},
|
snackbarHost: @Composable () -> Unit = {},
|
||||||
actions: @Composable RowScope.() -> Unit = {},
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
content: @Composable ColumnScope.() -> Unit,
|
content: @Composable ColumnScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val scrollBehavior =
|
val scrollBehavior = if (largeTopBar) {
|
||||||
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberTopAppBarState())
|
||||||
|
} else {
|
||||||
|
TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
|
}
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.predictiveBack(onBack = onBack)
|
.predictiveBack(onBack = onBack)
|
||||||
@@ -81,22 +92,34 @@ fun CollapsingScaffold(
|
|||||||
.background(MaterialTheme.colorScheme.surface)
|
.background(MaterialTheme.colorScheme.surface)
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
topBar = {
|
topBar = {
|
||||||
LargeTopAppBar(
|
val navigationIcon = @Composable {
|
||||||
title = { Text(title) },
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(
|
Icon(
|
||||||
Icons.AutoMirrored.Filled.ArrowBack,
|
Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = stringResource(R.string.settings_back),
|
contentDescription = stringResource(R.string.settings_back),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
val colors = TopAppBarDefaults.topAppBarColors(
|
||||||
|
scrolledContainerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
)
|
||||||
|
if (largeTopBar) {
|
||||||
|
LargeTopAppBar(
|
||||||
|
title = { Text(title) },
|
||||||
|
navigationIcon = navigationIcon,
|
||||||
actions = actions,
|
actions = actions,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
colors = TopAppBarDefaults.topAppBarColors(
|
colors = colors,
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.surface,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
TopAppBar(
|
||||||
|
title = { Text(title) },
|
||||||
|
navigationIcon = navigationIcon,
|
||||||
|
actions = actions,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
colors = colors,
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
snackbarHost = snackbarHost,
|
snackbarHost = snackbarHost,
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@@ -46,8 +46,9 @@ import de.jeanlucmakiola.calendula.ui.agenda.AgendaRange
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that
|
* Shared full-screen scaffold for selection pickers: a full-bleed [Dialog] that
|
||||||
* reuses the app's [CollapsingScaffold] (collapsing title + back button), so a
|
* reuses the app's [CollapsingScaffold] (back button + full width), but with a
|
||||||
* picker is visually identical to a Settings sub-page and uses the full width.
|
* pinned single-line title rather than the large collapsing header — a picker is
|
||||||
|
* a short list, so the tall header would only be empty space to scroll past.
|
||||||
* [content] places the connected grouped rows; selecting one calls [onDismiss].
|
* [content] places the connected grouped rows; selecting one calls [onDismiss].
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@@ -72,7 +73,12 @@ fun FullScreenPicker(
|
|||||||
(view.parent as? DialogWindowProvider)?.window
|
(view.parent as? DialogWindowProvider)?.window
|
||||||
?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
||||||
}
|
}
|
||||||
CollapsingScaffold(title = title, onBack = onDismiss, content = content)
|
CollapsingScaffold(
|
||||||
|
title = title,
|
||||||
|
onBack = onDismiss,
|
||||||
|
largeTopBar = false,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import androidx.compose.material.icons.automirrored.filled.Notes
|
|||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.ArrowDropDown
|
import androidx.compose.material.icons.filled.ArrowDropDown
|
||||||
import androidx.compose.material.icons.filled.CalendarMonth
|
import androidx.compose.material.icons.filled.CalendarMonth
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.Contacts
|
import androidx.compose.material.icons.filled.Contacts
|
||||||
import androidx.compose.material.icons.filled.EventAvailable
|
import androidx.compose.material.icons.filled.EventAvailable
|
||||||
@@ -120,12 +121,15 @@ import de.jeanlucmakiola.calendula.ui.common.calendarCollapseExit
|
|||||||
import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter
|
import de.jeanlucmakiola.calendula.ui.common.calendarExpandEnter
|
||||||
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
|
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE
|
import de.jeanlucmakiola.calendula.ui.common.CALENDAR_COLOR_PALETTE
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip
|
||||||
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
import de.jeanlucmakiola.calendula.ui.common.CalendarDatePickerDialog
|
||||||
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
import de.jeanlucmakiola.calendula.ui.common.ColorSwatchRow
|
||||||
import de.jeanlucmakiola.calendula.ui.common.DialogAmountField
|
import de.jeanlucmakiola.calendula.ui.common.DialogAmountField
|
||||||
import de.jeanlucmakiola.calendula.ui.common.DialogUnitDropdown
|
import de.jeanlucmakiola.calendula.ui.common.DialogUnitDropdown
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon
|
import de.jeanlucmakiola.calendula.ui.common.eventFormFieldIcon
|
||||||
import de.jeanlucmakiola.calendula.ui.common.eventFormFieldLabel
|
import de.jeanlucmakiola.calendula.ui.common.eventFormFieldLabel
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.FullScreenPicker
|
||||||
|
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
|
||||||
import de.jeanlucmakiola.calendula.ui.common.MILLIS_PER_DAY
|
import de.jeanlucmakiola.calendula.ui.common.MILLIS_PER_DAY
|
||||||
import de.jeanlucmakiola.calendula.ui.common.InlineTextField
|
import de.jeanlucmakiola.calendula.ui.common.InlineTextField
|
||||||
import de.jeanlucmakiola.calendula.ui.common.OptionCard
|
import de.jeanlucmakiola.calendula.ui.common.OptionCard
|
||||||
@@ -943,7 +947,7 @@ private fun EventEditContent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showCalendarPicker) {
|
if (showCalendarPicker) {
|
||||||
CalendarPickerDialog(
|
CalendarPicker(
|
||||||
calendars = state.calendars,
|
calendars = state.calendars,
|
||||||
selectedId = form.calendarId,
|
selectedId = form.calendarId,
|
||||||
onSelect = {
|
onSelect = {
|
||||||
@@ -1931,34 +1935,64 @@ private fun ScheduleRow(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full-screen calendar picker: a scrollable list of every writable calendar,
|
||||||
|
* grouped under its owning account like the visibility filter and calendar
|
||||||
|
* manager. Replaces the former fixed-height [AlertDialog], which clipped the
|
||||||
|
* list past ~9 rows so accounts with many calendars had unreachable entries
|
||||||
|
* (#29). Selecting a row calls [onSelect]; the caller closes the picker.
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
private fun CalendarPickerDialog(
|
private fun CalendarPicker(
|
||||||
calendars: List<CalendarSource>,
|
calendars: List<CalendarSource>,
|
||||||
selectedId: Long?,
|
selectedId: Long?,
|
||||||
onSelect: (Long) -> Unit,
|
onSelect: (Long) -> Unit,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val dark = isSystemInDarkTheme()
|
// Group by owning account (name, else type, else the calendar's own name),
|
||||||
AlertDialog(
|
// preserving the provider's order within and across groups — the same
|
||||||
onDismissRequest = onDismiss,
|
// grouping [groupByAccount] applies for the drawer filter.
|
||||||
title = { Text(stringResource(R.string.event_detail_calendar)) },
|
val groups = remember(calendars) {
|
||||||
text = {
|
calendars
|
||||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
.groupBy {
|
||||||
calendars.forEach { calendar ->
|
it.accountName.takeIf(String::isNotBlank)
|
||||||
OptionCard(
|
?: it.accountType.takeIf(String::isNotBlank)
|
||||||
label = calendar.displayName,
|
?: it.displayName
|
||||||
|
}
|
||||||
|
.toList()
|
||||||
|
}
|
||||||
|
FullScreenPicker(
|
||||||
|
title = stringResource(R.string.event_detail_calendar),
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
) {
|
||||||
|
groups.forEach { (account, cals) ->
|
||||||
|
Text(
|
||||||
|
text = account,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.padding(start = 24.dp, end = 24.dp, top = 16.dp, bottom = 4.dp),
|
||||||
|
)
|
||||||
|
cals.forEachIndexed { index, calendar ->
|
||||||
|
val isSelected = calendar.id == selectedId
|
||||||
|
GroupedRow(
|
||||||
|
title = calendar.displayName,
|
||||||
|
position = positionOf(index, cals.size),
|
||||||
|
selected = isSelected,
|
||||||
|
leading = { CalendarColorChip(calendar.color) },
|
||||||
|
trailing = if (isSelected) {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
},
|
||||||
onClick = { onSelect(calendar.id) },
|
onClick = { onSelect(calendar.id) },
|
||||||
icon = Icons.Default.CalendarMonth,
|
|
||||||
// The calendar's own colour carries its identity.
|
|
||||||
iconTint = pastelize(calendar.color, dark),
|
|
||||||
supportingText = calendar.accountName.takeIf { it.isNotBlank() },
|
|
||||||
selected = calendar.id == selectedId,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = onDismiss) { Text(stringResource(R.string.dialog_cancel)) }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user