feat(v1.8.0): IMPL_021 Sync Status Legend

- New SyncStatusLegendDialog.kt showing all 5 sync status icons with descriptions
- Help button (?) in MainScreen TopAppBar (only visible when sync available)
- Localized strings (English + German) for all 5 status explanations
- Material You design with consistent colors matching NoteCard icons
- Dialog shows: Synced, Pending, Conflict, Local only, Deleted on server

IMPL_021_SYNC_STATUS_LEGEND.md
This commit is contained in:
inventory69
2026-02-09 09:24:04 +01:00
parent 40d7c83c84
commit 07607fc095
4 changed files with 203 additions and 0 deletions

View File

@@ -17,6 +17,7 @@ import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.SelectAll import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
// FabPosition nicht mehr benötigt - FAB wird manuell platziert // FabPosition nicht mehr benötigt - FAB wird manuell platziert
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -53,6 +54,7 @@ import dev.dettmer.simplenotes.ui.main.components.NoteTypeFAB
import dev.dettmer.simplenotes.ui.main.components.NotesList import dev.dettmer.simplenotes.ui.main.components.NotesList
import dev.dettmer.simplenotes.ui.main.components.NotesStaggeredGrid import dev.dettmer.simplenotes.ui.main.components.NotesStaggeredGrid
import dev.dettmer.simplenotes.ui.main.components.SyncStatusBanner import dev.dettmer.simplenotes.ui.main.components.SyncStatusBanner
import dev.dettmer.simplenotes.ui.main.components.SyncStatusLegendDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
private const val TIMESTAMP_UPDATE_INTERVAL_MS = 30_000L private const val TIMESTAMP_UPDATE_INTERVAL_MS = 30_000L
@@ -92,6 +94,9 @@ fun MainScreen(
// Delete confirmation dialog state // Delete confirmation dialog state
var showBatchDeleteDialog by remember { mutableStateOf(false) } var showBatchDeleteDialog by remember { mutableStateOf(false) }
// 🆕 v1.8.0: Sync status legend dialog
var showSyncLegend by remember { mutableStateOf(false) }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val listState = rememberLazyListState() val listState = rememberLazyListState()
@@ -170,6 +175,8 @@ fun MainScreen(
) { ) {
MainTopBar( MainTopBar(
syncEnabled = canSync, syncEnabled = canSync,
showSyncLegend = isSyncAvailable, // 🆕 v1.8.0: Nur wenn Sync verfügbar
onSyncLegendClick = { showSyncLegend = true }, // 🆕 v1.8.0
onSyncClick = { viewModel.triggerManualSync("toolbar") }, onSyncClick = { viewModel.triggerManualSync("toolbar") },
onSettingsClick = onOpenSettings onSettingsClick = onOpenSettings
) )
@@ -276,6 +283,13 @@ fun MainScreen(
} }
) )
} }
// 🆕 v1.8.0: Sync Status Legend Dialog
if (showSyncLegend) {
SyncStatusLegendDialog(
onDismiss = { showSyncLegend = false }
)
}
} }
} }
@@ -283,6 +297,8 @@ fun MainScreen(
@Composable @Composable
private fun MainTopBar( private fun MainTopBar(
syncEnabled: Boolean, syncEnabled: Boolean,
showSyncLegend: Boolean, // 🆕 v1.8.0: Ob der Hilfe-Button sichtbar sein soll
onSyncLegendClick: () -> Unit, // 🆕 v1.8.0
onSyncClick: () -> Unit, onSyncClick: () -> Unit,
onSettingsClick: () -> Unit onSettingsClick: () -> Unit
) { ) {
@@ -294,6 +310,15 @@ private fun MainTopBar(
) )
}, },
actions = { actions = {
// 🆕 v1.8.0: Sync Status Legend Button (nur wenn Sync verfügbar)
if (showSyncLegend) {
IconButton(onClick = onSyncLegendClick) {
Icon(
imageVector = Icons.AutoMirrored.Outlined.HelpOutline,
contentDescription = stringResource(R.string.sync_legend_button)
)
}
}
IconButton( IconButton(
onClick = onSyncClick, onClick = onSyncClick,
enabled = syncEnabled enabled = syncEnabled

View File

@@ -0,0 +1,148 @@
package dev.dettmer.simplenotes.ui.main.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material.icons.outlined.CloudDone
import androidx.compose.material.icons.outlined.CloudOff
import androidx.compose.material.icons.outlined.CloudSync
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
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
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.res.stringResource
import androidx.compose.ui.unit.dp
import dev.dettmer.simplenotes.R
/**
* 🆕 v1.8.0: Dialog showing the sync status icon legend
*
* Displays all 5 SyncStatus values with their icons, colors,
* and descriptions. Helps users understand what each icon means.
*/
@Composable
fun SyncStatusLegendDialog(
onDismiss: () -> Unit
) {
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(R.string.sync_legend_title),
style = MaterialTheme.typography.headlineSmall
)
},
text = {
Column(
verticalArrangement = Arrangement.spacedBy(12.dp)
) {
// Optional: Kurze Einleitung
Text(
text = stringResource(R.string.sync_legend_description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
HorizontalDivider()
// ☁️✓ SYNCED
LegendRow(
icon = Icons.Outlined.CloudDone,
tint = MaterialTheme.colorScheme.primary,
label = stringResource(R.string.sync_legend_synced_label),
description = stringResource(R.string.sync_legend_synced_desc)
)
// ☁️↻ PENDING
LegendRow(
icon = Icons.Outlined.CloudSync,
tint = MaterialTheme.colorScheme.outline,
label = stringResource(R.string.sync_legend_pending_label),
description = stringResource(R.string.sync_legend_pending_desc)
)
// ⚠️ CONFLICT
LegendRow(
icon = Icons.Default.Warning,
tint = MaterialTheme.colorScheme.error,
label = stringResource(R.string.sync_legend_conflict_label),
description = stringResource(R.string.sync_legend_conflict_desc)
)
// ☁️✗ LOCAL_ONLY
LegendRow(
icon = Icons.Outlined.CloudOff,
tint = MaterialTheme.colorScheme.outline,
label = stringResource(R.string.sync_legend_local_only_label),
description = stringResource(R.string.sync_legend_local_only_desc)
)
// ☁️✗ DELETED_ON_SERVER
LegendRow(
icon = Icons.Outlined.CloudOff,
tint = MaterialTheme.colorScheme.outline.copy(alpha = 0.5f),
label = stringResource(R.string.sync_legend_deleted_label),
description = stringResource(R.string.sync_legend_deleted_desc)
)
}
},
confirmButton = {
TextButton(onClick = onDismiss) {
Text(stringResource(R.string.ok))
}
}
)
}
/**
* Single row in the sync status legend
* Shows icon + label + description
*/
@Composable
private fun LegendRow(
icon: ImageVector,
tint: Color,
label: String,
description: String
) {
Row(
verticalAlignment = Alignment.Top,
modifier = Modifier.fillMaxWidth()
) {
Icon(
imageVector = icon,
contentDescription = null, // Dekorativ, Label reicht
tint = tint,
modifier = Modifier
.size(20.dp)
.padding(top = 2.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = label,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurface
)
Text(
text = description,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}

View File

@@ -58,6 +58,21 @@
<string name="sync_status_error">Synchronisierung fehlgeschlagen</string> <string name="sync_status_error">Synchronisierung fehlgeschlagen</string>
<string name="sync_already_running">Synchronisierung läuft bereits</string> <string name="sync_already_running">Synchronisierung läuft bereits</string>
<!-- 🆕 v1.8.0: Sync-Status Legende Dialog -->
<string name="sync_legend_button">Sync-Status Hilfe</string>
<string name="sync_legend_title">Sync-Status Icons</string>
<string name="sync_legend_description">Jede Notiz zeigt ein kleines Icon, das den Sync-Status anzeigt:</string>
<string name="sync_legend_synced_label">Synchronisiert</string>
<string name="sync_legend_synced_desc">Diese Notiz ist auf allen Geräten aktuell.</string>
<string name="sync_legend_pending_label">Ausstehend</string>
<string name="sync_legend_pending_desc">Diese Notiz hat lokale Änderungen, die noch synchronisiert werden müssen.</string>
<string name="sync_legend_conflict_label">Konflikt</string>
<string name="sync_legend_conflict_desc">Diese Notiz wurde auf mehreren Geräten gleichzeitig geändert. Die neueste Version wurde beibehalten.</string>
<string name="sync_legend_local_only_label">Nur lokal</string>
<string name="sync_legend_local_only_desc">Diese Notiz wurde noch nie mit dem Server synchronisiert.</string>
<string name="sync_legend_deleted_label">Auf Server gelöscht</string>
<string name="sync_legend_deleted_desc">Diese Notiz wurde auf einem anderen Gerät oder direkt auf dem Server gelöscht. Sie existiert noch lokal.</string>
<!-- ============================= --> <!-- ============================= -->
<!-- DELETE DIALOGS --> <!-- DELETE DIALOGS -->
<!-- ============================= --> <!-- ============================= -->

View File

@@ -65,6 +65,21 @@
<string name="sync_status_local_only">Not yet synced</string> <string name="sync_status_local_only">Not yet synced</string>
<string name="sync_status_deleted_on_server">Deleted on server</string> <string name="sync_status_deleted_on_server">Deleted on server</string>
<!-- 🆕 v1.8.0: Sync Status Legend Dialog -->
<string name="sync_legend_button">Sync status help</string>
<string name="sync_legend_title">Sync Status Icons</string>
<string name="sync_legend_description">Each note shows a small icon indicating its sync status:</string>
<string name="sync_legend_synced_label">Synced</string>
<string name="sync_legend_synced_desc">This note is up to date on all devices.</string>
<string name="sync_legend_pending_label">Pending</string>
<string name="sync_legend_pending_desc">This note has local changes waiting to be synced.</string>
<string name="sync_legend_conflict_label">Conflict</string>
<string name="sync_legend_conflict_desc">This note was changed on multiple devices simultaneously. The latest version was kept.</string>
<string name="sync_legend_local_only_label">Local only</string>
<string name="sync_legend_local_only_desc">This note has never been synced to the server yet.</string>
<string name="sync_legend_deleted_label">Deleted on server</string>
<string name="sync_legend_deleted_desc">This note was deleted on another device or directly on the server. It still exists locally.</string>
<!-- ============================= --> <!-- ============================= -->
<!-- DELETE DIALOGS --> <!-- DELETE DIALOGS -->
<!-- ============================= --> <!-- ============================= -->