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)
This commit is contained in:
@@ -174,30 +174,68 @@ suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
|
||||
|
||||
## 🔋 Akku-Optimierung
|
||||
|
||||
### Verbrauchsanalyse
|
||||
### v1.6.0: Konfigurierbare Sync-Trigger
|
||||
|
||||
Seit v1.6.0 kann jeder Sync-Trigger einzeln aktiviert/deaktiviert werden. Das gibt Nutzern feine Kontrolle über den Akkuverbrauch.
|
||||
|
||||
#### Sync-Trigger Übersicht
|
||||
|
||||
| Trigger | Standard | Akku-Impact | Beschreibung |
|
||||
|---------|----------|-------------|--------------|
|
||||
| **Manueller Sync** | Immer an | 0 (nutzer-getriggert) | Toolbar-Button / Pull-to-Refresh |
|
||||
| **onSave Sync** | ✅ AN | ~0.5 mAh/Speichern | Sync sofort nach Speichern einer Notiz |
|
||||
| **onResume Sync** | ✅ AN | ~0.3 mAh/Öffnen | Sync beim App-Öffnen (60s Throttle) |
|
||||
| **WiFi-Connect** | ✅ AN | ~0.5 mAh/Verbindung | Sync bei WiFi-Verbindung |
|
||||
| **Periodic Sync** | ❌ AUS | 0.2-0.8%/Tag | Hintergrund-Sync alle 15/30/60 Min |
|
||||
| **Boot Sync** | ❌ AUS | ~0.1 mAh/Boot | Start Hintergrund-Sync nach Neustart |
|
||||
|
||||
#### Akku-Verbrauchsberechnung
|
||||
|
||||
**Typisches Nutzungsszenario (Standardeinstellungen):**
|
||||
- onSave: ~5 Speichern/Tag × 0.5 mAh = **~2.5 mAh**
|
||||
- onResume: ~10 Öffnen/Tag × 0.3 mAh = **~3 mAh**
|
||||
- WiFi-Connect: ~2 Verbindungen/Tag × 0.5 mAh = **~1 mAh**
|
||||
- **Gesamt: ~6.5 mAh/Tag (~0.2% bei 3000mAh Akku)**
|
||||
|
||||
**Mit aktiviertem Periodic Sync (15/30/60 Min):**
|
||||
|
||||
| Intervall | Syncs/Tag | Akku/Tag | Gesamt (mit Standards) |
|
||||
|-----------|-----------|----------|------------------------|
|
||||
| **15 Min** | ~96 | ~23 mAh | ~30 mAh (~1.0%) |
|
||||
| **30 Min** | ~48 | ~12 mAh | ~19 mAh (~0.6%) |
|
||||
| **60 Min** | ~24 | ~6 mAh | ~13 mAh (~0.4%) |
|
||||
|
||||
#### Komponenten-Aufschlüsselung
|
||||
|
||||
| Komponente | Frequenz | Verbrauch | Details |
|
||||
|------------|----------|-----------|---------|
|
||||
| WorkManager Wakeup | Alle 30 Min | ~0.15 mAh | System wacht auf |
|
||||
| Network Check | 48x/Tag | ~0.03 mAh | Gateway IP check |
|
||||
| WebDAV Sync | 2-3x/Tag | ~1.5 mAh | Nur bei Änderungen |
|
||||
| **Total** | - | **~12 mAh/Tag** | **~0.4%** bei 3000mAh |
|
||||
| WorkManager Wakeup | Pro Sync | ~0.15 mAh | System wacht auf |
|
||||
| Network Check | Pro Sync | ~0.03 mAh | Gateway IP Check |
|
||||
| WebDAV Sync | Nur bei Änderungen | ~0.25 mAh | HTTP PUT/GET |
|
||||
| **Pro-Sync Gesamt** | - | **~0.25 mAh** | Optimiert |
|
||||
|
||||
### Optimierungen
|
||||
|
||||
1. **IP Caching**
|
||||
1. **Pre-Checks vor Sync**
|
||||
```kotlin
|
||||
// Reihenfolge wichtig! Günstigste Checks zuerst
|
||||
if (!hasUnsyncedChanges()) return // Lokaler Check (günstig)
|
||||
if (!isServerReachable()) return // Netzwerk Check (teuer)
|
||||
performSync() // Nur wenn beide bestehen
|
||||
```
|
||||
|
||||
2. **Throttling**
|
||||
- onResume: 60 Sekunden Mindestabstand
|
||||
- onSave: 5 Sekunden Mindestabstand
|
||||
- Periodic: 15/30/60 Minuten Intervalle
|
||||
|
||||
3. **IP Caching**
|
||||
```kotlin
|
||||
private var cachedServerIP: String? = null
|
||||
// DNS lookup nur 1x beim Start, nicht bei jedem Check
|
||||
```
|
||||
|
||||
2. **Throttling**
|
||||
```kotlin
|
||||
private var lastSyncTime = 0L
|
||||
private const val MIN_SYNC_INTERVAL_MS = 60_000L // Max 1 Sync/Min
|
||||
```
|
||||
|
||||
3. **Conditional Logging**
|
||||
4. **Conditional Logging**
|
||||
```kotlin
|
||||
object Logger {
|
||||
fun d(tag: String, msg: String) {
|
||||
@@ -206,7 +244,7 @@ suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
|
||||
}
|
||||
```
|
||||
|
||||
4. **Network Constraints**
|
||||
5. **Network Constraints**
|
||||
- Nur WiFi (nicht mobile Daten)
|
||||
- Nur wenn Server erreichbar
|
||||
- Keine permanenten Listeners
|
||||
|
||||
68
docs/DOCS.md
68
docs/DOCS.md
@@ -174,30 +174,68 @@ suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
|
||||
|
||||
## 🔋 Battery Optimization
|
||||
|
||||
### Usage Analysis
|
||||
### v1.6.0: Configurable Sync Triggers
|
||||
|
||||
Since v1.6.0, each sync trigger can be individually enabled/disabled. This gives users fine-grained control over battery usage.
|
||||
|
||||
#### Sync Trigger Overview
|
||||
|
||||
| Trigger | Default | Battery Impact | Description |
|
||||
|---------|---------|----------------|-------------|
|
||||
| **Manual Sync** | Always on | 0 (user-triggered) | Toolbar button / Pull-to-refresh |
|
||||
| **onSave Sync** | ✅ ON | ~0.5 mAh/save | Sync immediately after saving a note |
|
||||
| **onResume Sync** | ✅ ON | ~0.3 mAh/resume | Sync when app is opened (60s throttle) |
|
||||
| **WiFi-Connect** | ✅ ON | ~0.5 mAh/connect | Sync when WiFi is connected |
|
||||
| **Periodic Sync** | ❌ OFF | 0.2-0.8%/day | Background sync every 15/30/60 min |
|
||||
| **Boot Sync** | ❌ OFF | ~0.1 mAh/boot | Start background sync after reboot |
|
||||
|
||||
#### Battery Usage Calculation
|
||||
|
||||
**Typical usage scenario (defaults):**
|
||||
- onSave: ~5 saves/day × 0.5 mAh = **~2.5 mAh**
|
||||
- onResume: ~10 opens/day × 0.3 mAh = **~3 mAh**
|
||||
- WiFi-Connect: ~2 connects/day × 0.5 mAh = **~1 mAh**
|
||||
- **Total: ~6.5 mAh/day (~0.2% on 3000mAh battery)**
|
||||
|
||||
**With Periodic Sync enabled (15/30/60 min):**
|
||||
|
||||
| Interval | Syncs/day | Battery/day | Total (with defaults) |
|
||||
|----------|-----------|-------------|----------------------|
|
||||
| **15 min** | ~96 | ~23 mAh | ~30 mAh (~1.0%) |
|
||||
| **30 min** | ~48 | ~12 mAh | ~19 mAh (~0.6%) |
|
||||
| **60 min** | ~24 | ~6 mAh | ~13 mAh (~0.4%) |
|
||||
|
||||
#### Component Breakdown
|
||||
|
||||
| Component | Frequency | Usage | Details |
|
||||
|------------|----------|-----------|---------|
|
||||
| WorkManager Wakeup | Every 30 min | ~0.15 mAh | System wakes up |
|
||||
| Network Check | 48x/day | ~0.03 mAh | Gateway IP check |
|
||||
| WebDAV Sync | 2-3x/day | ~1.5 mAh | Only when changes |
|
||||
| **Total** | - | **~12 mAh/day** | **~0.4%** at 3000mAh |
|
||||
|-----------|-----------|-------|---------|
|
||||
| WorkManager Wakeup | Per sync | ~0.15 mAh | System wakes up |
|
||||
| Network Check | Per sync | ~0.03 mAh | Gateway IP check |
|
||||
| WebDAV Sync | Only if changes | ~0.25 mAh | HTTP PUT/GET |
|
||||
| **Per-Sync Total** | - | **~0.25 mAh** | Optimized |
|
||||
|
||||
### Optimizations
|
||||
|
||||
1. **IP Caching**
|
||||
1. **Pre-Checks before Sync**
|
||||
```kotlin
|
||||
// Order matters! Cheapest checks first
|
||||
if (!hasUnsyncedChanges()) return // Local check (cheap)
|
||||
if (!isServerReachable()) return // Network check (expensive)
|
||||
performSync() // Only if both pass
|
||||
```
|
||||
|
||||
2. **Throttling**
|
||||
- onResume: 60 second minimum interval
|
||||
- onSave: 5 second minimum interval
|
||||
- Periodic: 15/30/60 minute intervals
|
||||
|
||||
3. **IP Caching**
|
||||
```kotlin
|
||||
private var cachedServerIP: String? = null
|
||||
// DNS lookup only once at start, not every check
|
||||
```
|
||||
|
||||
2. **Throttling**
|
||||
```kotlin
|
||||
private var lastSyncTime = 0L
|
||||
private const val MIN_SYNC_INTERVAL_MS = 60_000L // Max 1 sync/min
|
||||
```
|
||||
|
||||
3. **Conditional Logging**
|
||||
4. **Conditional Logging**
|
||||
```kotlin
|
||||
object Logger {
|
||||
fun d(tag: String, msg: String) {
|
||||
@@ -206,7 +244,7 @@ suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
|
||||
}
|
||||
```
|
||||
|
||||
4. **Network Constraints**
|
||||
5. **Network Constraints**
|
||||
- WiFi only (not mobile data)
|
||||
- Only when server is reachable
|
||||
- No permanent listeners
|
||||
|
||||
@@ -169,16 +169,19 @@
|
||||
|
||||
## 🔋 Performance & Optimierung
|
||||
|
||||
### Akku-Effizienz
|
||||
- ✅ **Optimierte Sync-Intervalle** - 15/30/60 Min
|
||||
### Akku-Effizienz (v1.6.0)
|
||||
- ✅ **Konfigurierbare Sync-Trigger** - Jeden Trigger einzeln aktivieren/deaktivieren
|
||||
- ✅ **Smarte Defaults** - Nur ereignisbasierte Trigger standardmäßig aktiv
|
||||
- ✅ **Optimierte Periodische Intervalle** - 15/30/60 Min (Standard: AUS)
|
||||
- ✅ **WiFi-Only** - Kein Mobile Data Sync
|
||||
- ✅ **Smart Server-Check** - Sync nur wenn Server erreichbar
|
||||
- ✅ **WorkManager** - System-optimierte Ausführung
|
||||
- ✅ **Doze Mode kompatibel** - Sync läuft auch im Standby
|
||||
- ✅ **Gemessener Verbrauch:**
|
||||
- 15 Min: ~0.8% / Tag (~23 mAh)
|
||||
- 30 Min: ~0.4% / Tag (~12 mAh) ⭐ _Empfohlen_
|
||||
- 60 Min: ~0.2% / Tag (~6 mAh)
|
||||
- Standard (nur ereignisbasiert): ~0.2%/Tag (~6.5 mAh) ⭐ _Optimal_
|
||||
- Mit Periodic 15 Min: ~1.0%/Tag (~30 mAh)
|
||||
- Mit Periodic 30 Min: ~0.6%/Tag (~19 mAh)
|
||||
- Mit Periodic 60 Min: ~0.4%/Tag (~13 mAh)
|
||||
|
||||
### App-Performance
|
||||
- ✅ **Offline-First** - Funktioniert ohne Internet
|
||||
|
||||
@@ -169,16 +169,19 @@
|
||||
|
||||
## 🔋 Performance & Optimization
|
||||
|
||||
### Battery Efficiency
|
||||
- ✅ **Optimized sync intervals** - 15/30/60 min
|
||||
### Battery Efficiency (v1.6.0)
|
||||
- ✅ **Configurable sync triggers** - Enable/disable each trigger individually
|
||||
- ✅ **Smart defaults** - Only event-driven triggers active by default
|
||||
- ✅ **Optimized periodic intervals** - 15/30/60 min (default: OFF)
|
||||
- ✅ **WiFi-only** - No mobile data sync
|
||||
- ✅ **Smart server check** - Sync only when server is reachable
|
||||
- ✅ **WorkManager** - System-optimized execution
|
||||
- ✅ **Doze mode compatible** - Sync runs even in standby
|
||||
- ✅ **Measured consumption:**
|
||||
- 15 min: ~0.8% / day (~23 mAh)
|
||||
- 30 min: ~0.4% / day (~12 mAh) ⭐ _Recommended_
|
||||
- 60 min: ~0.2% / day (~6 mAh)
|
||||
- Default (event-driven only): ~0.2%/day (~6.5 mAh) ⭐ _Optimal_
|
||||
- With periodic 15 min: ~1.0%/day (~30 mAh)
|
||||
- With periodic 30 min: ~0.6%/day (~19 mAh)
|
||||
- With periodic 60 min: ~0.4%/day (~13 mAh)
|
||||
|
||||
### App Performance
|
||||
- ✅ **Offline-first** - Works without internet
|
||||
|
||||
@@ -33,7 +33,16 @@
|
||||
|
||||
## v1.6.0 - Technische Modernisierung
|
||||
|
||||
> **Status:** In Planung 📋
|
||||
> **Status:** In Entwicklung 🚧
|
||||
|
||||
### ⚙️ Konfigurierbare Sync-Trigger
|
||||
|
||||
- ✅ **Individuelle Trigger-Kontrolle** - Jeden Sync-Trigger einzeln aktivieren/deaktivieren
|
||||
- ✅ **Ereignisbasierte Defaults** - onSave, onResume, WiFi-Connect standardmäßig aktiv
|
||||
- ✅ **Periodischer Sync optional** - 15/30/60 Min Intervalle (Standard: AUS)
|
||||
- ✅ **Boot Sync optional** - Periodischen Sync nach Geräteneustart starten (Standard: AUS)
|
||||
- ✅ **Offline-Modus UI** - Ausgegraute Toggles wenn kein Server konfiguriert
|
||||
- ✅ **Akku-optimiert** - ~0.2%/Tag mit Defaults, bis zu ~1.0% mit Periodic
|
||||
|
||||
### 🔧 Server-Ordner Prüfung
|
||||
|
||||
|
||||
@@ -33,7 +33,16 @@
|
||||
|
||||
## v1.6.0 - Technical Modernization
|
||||
|
||||
> **Status:** Planned 📋
|
||||
> **Status:** In Development 🚧
|
||||
|
||||
### ⚙️ Configurable Sync Triggers
|
||||
|
||||
- ✅ **Individual trigger control** - Enable/disable each sync trigger separately
|
||||
- ✅ **Event-driven defaults** - onSave, onResume, WiFi-Connect active by default
|
||||
- ✅ **Periodic sync optional** - 15/30/60 min intervals (default: OFF)
|
||||
- ✅ **Boot sync optional** - Start periodic sync after device restart (default: OFF)
|
||||
- ✅ **Offline mode UI** - Grayed-out toggles when no server configured
|
||||
- ✅ **Battery optimized** - ~0.2%/day with defaults, up to ~1.0% with periodic
|
||||
|
||||
### 🔧 Server Folder Check
|
||||
|
||||
|
||||
315
docs/v1.6.0_OFFLINE_DELETE_RESTRICTION.md
Normal file
315
docs/v1.6.0_OFFLINE_DELETE_RESTRICTION.md
Normal file
@@ -0,0 +1,315 @@
|
||||
# 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](../android/app/src/main/java/dev/dettmer/simplenotes/ui/main/components/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](../android/app/src/main/java/dev/dettmer/simplenotes/ui/editor/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
|
||||
|
||||
```kotlin
|
||||
// DeleteConfirmationDialog.kt
|
||||
@Composable
|
||||
fun DeleteConfirmationDialog(
|
||||
noteCount: Int = 1,
|
||||
isOfflineMode: Boolean = false, // 🌟 v1.6.0: NEU
|
||||
onDismiss: () -> Unit,
|
||||
onDeleteLocal: () -> Unit,
|
||||
onDeleteEverywhere: () -> Unit
|
||||
)
|
||||
```
|
||||
|
||||
**Neue Strings:**
|
||||
```xml
|
||||
<!-- 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
|
||||
|
||||
```kotlin
|
||||
// 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):**
|
||||
```kotlin
|
||||
DeleteConfirmationDialog(
|
||||
noteCount = selectedNotes.size,
|
||||
onDismiss = { showBatchDeleteDialog = false },
|
||||
onDeleteLocal = { ... },
|
||||
onDeleteEverywhere = { ... }
|
||||
)
|
||||
```
|
||||
|
||||
**Änderung:**
|
||||
```kotlin
|
||||
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
|
||||
|
||||
```kotlin
|
||||
// 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
|
||||
|
||||
```kotlin
|
||||
// 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)
|
||||
Reference in New Issue
Block a user