diff --git a/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt b/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt
index cf72b82..0eb0140 100644
--- a/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt
+++ b/app/src/main/java/de/jeanlucmakiola/calendula/ui/imports/ImportScreen.kt
@@ -1,15 +1,27 @@
package de.jeanlucmakiola.calendula.ui.imports
import android.net.Uri
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.Spring
+import androidx.compose.animation.core.spring
+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.Row
+import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.Spacer
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.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
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
@@ -18,6 +30,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
@@ -30,6 +43,11 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
@@ -139,41 +157,122 @@ private fun ManyContent(state: ImportUiState.Many, onImport: (Long) -> Unit) {
@Composable
private fun DoneContent(state: ImportUiState.Done, onClose: () -> Unit) {
+ // A little expressive pop on the success badge — springs in on first show.
+ val badgeScale = remember { Animatable(0.7f) }
+ LaunchedEffect(Unit) {
+ badgeScale.animateTo(
+ targetValue = 1f,
+ animationSpec = spring(
+ dampingRatio = Spring.DampingRatioMediumBouncy,
+ stiffness = Spring.StiffnessLow,
+ ),
+ )
+ }
+
Column(
Modifier.fillMaxSize().padding(24.dp),
- verticalArrangement = Arrangement.spacedBy(12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
+ Spacer(Modifier.weight(1f))
+ Box(
+ Modifier
+ .size(112.dp)
+ .graphicsLayer {
+ scaleX = badgeScale.value
+ scaleY = badgeScale.value
+ }
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.primaryContainer),
+ contentAlignment = Alignment.Center,
+ ) {
+ Icon(
+ Icons.Default.Check,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier.size(56.dp),
+ )
+ }
+ Spacer(Modifier.height(24.dp))
Text(
stringResource(R.string.import_done_title),
style = MaterialTheme.typography.headlineSmall,
- modifier = Modifier.padding(top = 24.dp),
+ color = MaterialTheme.colorScheme.onSurface,
)
- Text(
- pluralStringResource(
- R.plurals.import_done_imported,
- state.summary.imported,
- state.summary.imported,
- ),
- style = MaterialTheme.typography.bodyLarge,
- )
- if (state.summary.skippedDuplicate > 0) {
- Text(
- pluralStringResource(
- R.plurals.import_done_skipped,
- state.summary.skippedDuplicate,
- state.summary.skippedDuplicate,
+ Spacer(Modifier.height(24.dp))
+ Row(
+ Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ ) {
+ ImportStatCard(
+ count = state.summary.imported,
+ label = stringResource(R.string.import_done_added_label),
+ contentDescription = pluralStringResource(
+ R.plurals.import_done_imported,
+ state.summary.imported,
+ state.summary.imported,
),
- style = MaterialTheme.typography.bodyMedium,
- color = MaterialTheme.colorScheme.onSurfaceVariant,
+ container = MaterialTheme.colorScheme.secondaryContainer,
+ onContainer = MaterialTheme.colorScheme.onSecondaryContainer,
)
+ if (state.summary.skippedDuplicate > 0) {
+ ImportStatCard(
+ count = state.summary.skippedDuplicate,
+ label = stringResource(R.string.import_done_skipped_label),
+ contentDescription = pluralStringResource(
+ R.plurals.import_done_skipped,
+ state.summary.skippedDuplicate,
+ state.summary.skippedDuplicate,
+ ),
+ container = MaterialTheme.colorScheme.surfaceContainerHighest,
+ onContainer = MaterialTheme.colorScheme.onSurfaceVariant,
+ )
+ }
}
- Button(onClick = onClose, modifier = Modifier.padding(top = 12.dp)) {
+ Spacer(Modifier.weight(1f))
+ Button(
+ onClick = onClose,
+ modifier = Modifier.fillMaxWidth(),
+ ) {
Text(stringResource(R.string.import_close))
}
}
}
+/** A big-number tonal tile summarising one import outcome (added / skipped). */
+@Composable
+private fun RowScope.ImportStatCard(
+ count: Int,
+ label: String,
+ contentDescription: String,
+ container: Color,
+ onContainer: Color,
+) {
+ Surface(
+ modifier = Modifier
+ .weight(1f)
+ .clearAndSetSemantics { this.contentDescription = contentDescription },
+ shape = RoundedCornerShape(24.dp),
+ color = container,
+ ) {
+ Column(
+ Modifier.padding(vertical = 20.dp, horizontal = 12.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(2.dp),
+ ) {
+ Text(
+ count.toString(),
+ style = MaterialTheme.typography.displaySmall,
+ color = onContainer,
+ )
+ Text(
+ label,
+ style = MaterialTheme.typography.labelLarge,
+ color = onContainer.copy(alpha = 0.85f),
+ )
+ }
+ }
+}
+
@Composable
private fun WarningText(warning: IcsParseWarning) {
val text = when (warning) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index d3272e7..c5ff303 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -462,6 +462,8 @@
Couldn\'t read this file.
No writable calendar to import into. Create a local calendar first.
Import complete
+ Added
+ Duplicates
Close
Some changed occurrences of recurring events were skipped.
An event without a start time was skipped.