Files
simple-notes-sync/docs/v1.6.0_OFFLINE_DELETE_RESTRICTION.md
inventory69 1d010d0034 Release v1.6.0: Configurable Sync Triggers + Offline Mode
- NEW: Configurable sync triggers (onSave, onResume, WiFi, Periodic, Boot)
- NEW: Offline mode toggle to disable all network features
- Various fixes and UI improvements
- Version bumped to 1.6.0 (code 14)
2026-01-19 23:31:25 +01:00

11 KiB

v1.6.0 Feature: Server-Lösch-Einschränkung im Offline-Modus

📋 Übersicht

Problem: Im Offline-Modus kann der Benutzer immer noch "Überall löschen (auch Server)" auswählen, was zu Netzwerkverkehr führt (auch wenn die Anfrage fehlschlägt).

Ziel: Die "Überall löschen"-Option im Offline-Modus subtil aber intuitiv deaktivieren, um echte Offline-Nutzung zu gewährleisten.


🔍 Analyse der betroffenen Komponenten

1. DeleteConfirmationDialog

Datei: DeleteConfirmationDialog.kt

Aktueller Zustand:

  • Zeigt zwei Optionen: "Überall löschen" und "Nur lokal löschen"
  • Keine Berücksichtigung des Offline-Modus
  • Verwendet in: MainScreen.kt (Batch-Delete) und NoteEditorScreen.kt (Einzelne Notiz)

Änderungen:

  • Neuer Parameter: isOfflineMode: Boolean = false
  • "Überall löschen" Button: enabled = !isOfflineMode
  • Subtile visuelle Kennzeichnung wenn deaktiviert

2. Verwendungsstellen

Datei Verwendung ViewModel-Zugriff
MainScreen.kt Batch-Löschung MainViewModel.isOfflineMode bereits vorhanden
NoteEditorScreen.kt Einzelne Notiz NoteEditorViewModel benötigt Erweiterung

3. NoteEditorViewModel

Datei: NoteEditorViewModel.kt

Aktueller Zustand:

  • Prüft Offline-Status nur für triggerOnSaveSync() inline via prefs.getString(KEY_SERVER_URL, null)
  • Kein reaktiver State für Offline-Modus

Änderungen:

  • Neuer StateFlow: isOfflineMode: StateFlow<Boolean>

📐 Technische Design-Entscheidungen

UI/UX Design für deaktivierte Option

Option A: Grayed-out mit Tooltip-Hinweis EMPFOHLEN

┌─────────────────────────────────────────┐
│  Notiz löschen?                         │
│                                         │
│  Wie möchtest du diese Notiz löschen?   │
│                                         │
│  ┌─────────────────────────────────────┐│
│  │ Überall löschen (auch Server)       ││ ← Grau, nicht anklickbar
│  │ 📴 Nicht verfügbar im Offline-Modus ││ ← Subtiler Hinweis
│  └─────────────────────────────────────┘│
│                                         │
│  ┌─────────────────────────────────────┐│
│  │ ✓ Nur lokal löschen                 ││ ← Normal, anklickbar
│  └─────────────────────────────────────┘│
│                                         │
│  ┌─────────────────────────────────────┐│
│  │     Abbrechen                       ││
│  └─────────────────────────────────────┘│
└─────────────────────────────────────────┘

Vorteile:

  • Konsistent mit bestehendem v1.6.0 Pattern (BackupSettingsScreen, MarkdownSettingsScreen)
  • Benutzer sieht sofort warum Option nicht verfügbar
  • Keine Verwirrung - klare Ursache angegeben

Option B: Button komplett ausblenden

Nachteile:

  • Verwirrend für Benutzer die den Button sonst sehen
  • Inkonsistent mit v1.6.0 Design-Pattern

Option C: Button mit Toast-Feedback

Nachteile:

  • Schlechte UX - warum klickbar wenn nicht möglich?
  • Frustierend für Benutzer

📝 Implementierungs-Plan

Phase 1: DeleteConfirmationDialog erweitern

Schritt 1.1: Neuer Parameter und String-Ressourcen

// DeleteConfirmationDialog.kt
@Composable
fun DeleteConfirmationDialog(
    noteCount: Int = 1,
    isOfflineMode: Boolean = false,  // 🌟 v1.6.0: NEU
    onDismiss: () -> Unit,
    onDeleteLocal: () -> Unit,
    onDeleteEverywhere: () -> Unit
)

Neue Strings:

<!-- values/strings.xml -->
<string name="delete_everywhere_offline_hint">Not available in offline mode</string>

<!-- values-de/strings.xml -->
<string name="delete_everywhere_offline_hint">Nicht verfügbar im Offline-Modus</string>

Schritt 1.2: UI-Anpassung für deaktivierten Button

// Delete everywhere (server + local) - primary action
// 🌟 v1.6.0: Disabled in offline mode
Column {
    TextButton(
        onClick = onDeleteEverywhere,
        modifier = Modifier.fillMaxWidth(),
        enabled = !isOfflineMode,  // 🌟 NEU
        colors = ButtonDefaults.textButtonColors(
            contentColor = MaterialTheme.colorScheme.error,
            disabledContentColor = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.38f)
        )
    ) {
        Text(stringResource(R.string.delete_everywhere))
    }
    
    // 🌟 v1.6.0: Show hint when offline
    if (isOfflineMode) {
        Text(
            text = stringResource(R.string.delete_everywhere_offline_hint),
            style = MaterialTheme.typography.bodySmall,
            color = MaterialTheme.colorScheme.tertiary,
            modifier = Modifier.padding(start = 16.dp, end = 16.dp, bottom = 8.dp)
        )
    }
}

Phase 2: MainScreen anpassen (Batch-Löschung)

Datei: MainScreen.kt

Aktuelle Verwendung (Zeile ~218):

DeleteConfirmationDialog(
    noteCount = selectedNotes.size,
    onDismiss = { showBatchDeleteDialog = false },
    onDeleteLocal = { ... },
    onDeleteEverywhere = { ... }
)

Änderung:

DeleteConfirmationDialog(
    noteCount = selectedNotes.size,
    isOfflineMode = isOfflineMode,  // 🌟 v1.6.0: NEU - bereits als State vorhanden
    onDismiss = { showBatchDeleteDialog = false },
    onDeleteLocal = { ... },
    onDeleteEverywhere = { ... }
)

Phase 3: NoteEditorScreen + NoteEditorViewModel anpassen

Schritt 3.1: NoteEditorViewModel erweitern

// NoteEditorViewModel.kt

// 🌟 v1.6.0: Offline Mode State
private val _isOfflineMode = MutableStateFlow(
    prefs.getBoolean(Constants.KEY_OFFLINE_MODE, true)
)
val isOfflineMode: StateFlow<Boolean> = _isOfflineMode.asStateFlow()

Schritt 3.2: NoteEditorScreen anpassen

// NoteEditorScreen.kt

// State abrufen
val isOfflineMode by viewModel.isOfflineMode.collectAsState()  // 🌟 NEU

// Dialog anpassen
if (showDeleteDialog) {
    DeleteConfirmationDialog(
        noteCount = 1,
        isOfflineMode = isOfflineMode,  // 🌟 v1.6.0: NEU
        onDismiss = { showDeleteDialog = false },
        onDeleteLocal = { ... },
        onDeleteEverywhere = { ... }
    )
}

🔧 Detaillierte Änderungs-Matrix

Datei Änderungstyp Beschreibung
strings.xml String hinzufügen delete_everywhere_offline_hint (EN)
strings.xml (de) String hinzufügen delete_everywhere_offline_hint (DE)
DeleteConfirmationDialog.kt Parameter + UI isOfflineMode Parameter, grayed-out Button + Hint
MainScreen.kt Parameter übergeben isOfflineMode = isOfflineMode an Dialog
NoteEditorViewModel.kt StateFlow hinzufügen isOfflineMode: StateFlow<Boolean>
NoteEditorScreen.kt State abrufen + übergeben collectAsState + an Dialog übergeben

Akzeptanzkriterien

  1. Offline-Modus aktiv:

    • "Überall löschen" Button ist grau/deaktiviert
    • Subtiler Hinweis-Text erscheint unter dem Button
    • Button ist nicht anklickbar
    • "Nur lokal löschen" funktioniert normal
    • Kein Netzwerkverkehr bei Lösch-Aktionen
  2. Online-Modus (Offline-Modus deaktiviert):

    • Beide Buttons funktionieren normal
    • Kein Hinweis-Text
    • Verhalten unverändert
  3. Konsistenz:

    • UI-Pattern konsistent mit anderen v1.6.0 Offline-Einschränkungen
    • Farbgebung nutzt MaterialTheme.colorScheme.tertiary für Hints
  4. Stellen:

    • MainScreen (Batch-Löschung mit Multi-Select)
    • NoteEditorScreen (Einzelne Notiz löschen)

📊 Geschätzter Aufwand

Phase Aufwand Dateien
Phase 1: Dialog ~30 Min 3 Dateien
Phase 2: MainScreen ~10 Min 1 Datei
Phase 3: NoteEditor ~20 Min 2 Dateien
Gesamt ~1 Stunde 6 Dateien

🧪 Test-Szenarien

Szenario 1: Einzelne Notiz löschen (Editor)

  1. Offline-Modus aktivieren
  2. Bestehende Notiz öffnen
  3. Löschen-Button klicken
  4. Erwartung: "Überall löschen" grau, Hint sichtbar
  5. "Nur lokal löschen" funktioniert

Szenario 2: Batch-Löschung (Main Screen)

  1. Offline-Modus aktivieren
  2. Mehrere Notizen auswählen (Long-Press + Tap)
  3. Papierkorb-Icon klicken
  4. Erwartung: "Überall löschen" grau, Hint sichtbar
  5. "Nur lokal löschen" funktioniert

Szenario 3: Wechsel zwischen Modi

  1. Im Offline-Modus Dialog öffnen → Button deaktiviert
  2. Abbrechen, in Einstellungen Offline-Modus deaktivieren
  3. Zurück, Dialog erneut öffnen → Button aktiviert

📌 Implementierungs-Reihenfolge

1. ┌─────────────────────────────────┐
   │ String-Ressourcen hinzufügen   │  ← Start hier
   └───────────────┬─────────────────┘
                   ▼
2. ┌─────────────────────────────────┐
   │ DeleteConfirmationDialog.kt    │  ← Kern-Änderung
   └───────────────┬─────────────────┘
                   ▼
3. ┌─────────────────────────────────┐
   │ MainScreen.kt                  │  ← Einfach (State vorhanden)
   └───────────────┬─────────────────┘
                   ▼
4. ┌─────────────────────────────────┐
   │ NoteEditorViewModel.kt         │  ← StateFlow hinzufügen
   └───────────────┬─────────────────┘
                   ▼
5. ┌─────────────────────────────────┐
   │ NoteEditorScreen.kt            │  ← State abrufen + übergeben
   └─────────────────────────────────┘

🔗 Abhängigkeiten

  • Keine externen Abhängigkeiten
  • Nutzt bestehende v1.6.0 Offline-Mode Infrastruktur
  • Konsistent mit Design-Pattern aus MarkdownSettingsScreen.kt und BackupSettingsScreen.kt

📝 Hinweise

  • Der isOfflineMode State in MainViewModel wird bereits reaktiv via StateFlow verwaltet
  • refreshOfflineModeState() wird in ComposeMainActivity.onResume() aufgerufen
  • Das gleiche Pattern wird in NoteEditorViewModel repliziert (einmalige Initialisierung ausreichend, da Editor-Lebenszyklus kurz ist)