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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 -->
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
|
|||||||
@@ -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 -->
|
||||||
<!-- ============================= -->
|
<!-- ============================= -->
|
||||||
|
|||||||
Reference in New Issue
Block a user