release: v2.14.0 — Day view on date-header tap #61

Merged
makiolaj merged 36 commits from release/v2.14.0 into main 2026-07-07 14:42:30 +00:00
Showing only changes of commit f25f308326 - Show all commits

View File

@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.Button
import androidx.compose.material3.CircularProgressIndicator
@@ -38,8 +39,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import de.jeanlucmakiola.calendula.R
import de.jeanlucmakiola.calendula.domain.EventForm
import de.jeanlucmakiola.calendula.domain.ics.IcsParseWarning
import de.jeanlucmakiola.calendula.ui.common.CalendarColorChip
import de.jeanlucmakiola.calendula.ui.common.GroupedRow
import de.jeanlucmakiola.calendula.ui.common.positionOf
import de.jeanlucmakiola.calendula.ui.common.predictiveBack
import de.jeanlucmakiola.calendula.ui.common.OptionCard
/**
* Handles an opened/received `.ics` file. A single event is handed straight to
@@ -106,33 +109,73 @@ private fun ManyContent(state: ImportUiState.Many, onImport: (Long) -> Unit) {
}
var selected by rememberSaveable { mutableStateOf(state.calendars.first().id) }
// Group by owning account, the same shape the event editor's calendar picker
// uses — coloured chip per calendar, a check on the selected one.
val groups = remember(state.calendars) {
state.calendars
.groupBy {
it.accountName.takeIf(String::isNotBlank)
?: it.accountType.takeIf(String::isNotBlank)
?: it.displayName
}
.toList()
}
Column(
Modifier.fillMaxSize().verticalScroll(rememberScrollState())
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
.padding(vertical = 8.dp),
) {
Text(
pluralStringResource(R.plurals.import_event_count, state.events.size, state.events.size),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(vertical = 8.dp),
modifier = Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
)
Text(
stringResource(R.string.import_target_header),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier.padding(start = 24.dp, end = 24.dp, bottom = 4.dp),
)
state.calendars.forEach { calendar ->
OptionCard(
label = calendar.displayName,
onClick = { selected = calendar.id },
selected = calendar.id == selected,
icon = null,
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 == selected
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 = { selected = calendar.id },
)
}
}
if (state.warnings.isNotEmpty()) {
Column(
Modifier.padding(horizontal = 24.dp, vertical = 8.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
state.warnings.forEach { WarningText(it) }
}
}
state.warnings.forEach { WarningText(it) }
Button(
onClick = { onImport(selected) },
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 16.dp, top = 8.dp),
) {
Text(pluralStringResource(R.plurals.import_action, state.events.size, state.events.size))
}