Files
simple-notes-sync/docs/DOCS.de.md
inventory69 cf54f44377 docs(v1.8.1): comprehensive metadata & documentation update for F-Droid release
Complete overhaul of all metadata and documentation for v1.8.1:

F-Droid Critical:
- Fix broken umlauts in de-DE/10.txt changelog (für, hinzugefügt, aufgeräumt)
- Shorten de-DE/7.txt changelog for brevity
- Remove trailing newline from en-US/title.txt

Version & Accuracy:
- Update README badges (Android 7.0+) and footer (v1.8.1)
- Add widgets, sorting, parallel sync to highlights
- Fix APK names in workflows (removed "universal")
- Update cache action v3 → v4 in PR workflow
- Fix CONTRIBUTING.md filename references
- Update QUICKSTART guides (APK name, typo, Android version, docs links)

Feature Documentation:
- Update full_description.txt (both locales) - remove NEW/NEU labels, add v1.8.0+ features
- Major FEATURES.md update - add Views & Layout, Widgets sections, updated tech stack
- Add UPCOMING.md v1.7.0-1.8.1 as released sections
- Update DOCS.md roadmap references and dates to Feb 2026
- Create missing en-US changelogs 1.txt, 2.txt

Supplementary Fixes:
- Update BACKUP.md - add encryption docs, fix cross-references
- Add CHANGELOG.md reference links v1.2.1-v1.8.1 (15 missing)
- Fix DEBUG_APK.md duplicate header
- Rewrite fastlane/README.md with both locales and limits table
- Create SELF_SIGNED_SSL.de.md (full German translation)

Affects: 26 files across READMEs, docs/, fastlane/, workflows
2026-02-11 15:45:51 +01:00

558 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Simple Notes Sync - Technische Dokumentation
Diese Datei enthält detaillierte technische Informationen über die Implementierung, Architektur und erweiterte Funktionen.
**🌍 Sprachen:** **Deutsch** · [English](DOCS.md)
---
## 📐 Architektur
### Gesamtübersicht
```
┌─────────────────┐
│ Android App │
│ (Kotlin) │
└────────┬────────┘
│ WebDAV/HTTP
┌────────▼────────┐
│ WebDAV Server │
│ (Docker) │
└─────────────────┘
```
### Android App Architektur
```
app/
├── models/
│ ├── Note.kt # Data class für Notizen
│ └── SyncStatus.kt # Sync-Status Enum
├── storage/
│ └── NotesStorage.kt # Lokale JSON-Datei Speicherung
├── sync/
│ ├── WebDavSyncService.kt # WebDAV Sync-Logik
│ ├── NetworkMonitor.kt # WLAN-Erkennung
│ ├── SyncWorker.kt # WorkManager Background Worker
│ └── BootReceiver.kt # Device Reboot Handler
├── adapters/
│ └── NotesAdapter.kt # RecyclerView Adapter
├── utils/
│ ├── Constants.kt # App-Konstanten
│ ├── NotificationHelper.kt# Notification Management
│ └── Logger.kt # Debug/Release Logging
└── activities/
├── MainActivity.kt # Hauptansicht mit Liste
├── NoteEditorActivity.kt# Editor für Notizen
└── SettingsActivity.kt # Server-Konfiguration
```
---
## 🔄 Auto-Sync Implementierung
### WorkManager Periodic Task
Der Auto-Sync basiert auf **WorkManager** mit folgender Konfiguration:
```kotlin
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.UNMETERED) // Nur WiFi
.build()
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(
30, TimeUnit.MINUTES, // Alle 30 Minuten
10, TimeUnit.MINUTES // Flex interval
)
.setConstraints(constraints)
.build()
```
**Warum WorkManager?**
- ✅ Läuft auch wenn App geschlossen ist
- ✅ Automatischer Restart nach Device Reboot
- ✅ Battery-efficient (Android managed)
- ✅ Garantierte Ausführung bei erfüllten Constraints
### Network Detection
Wir verwenden **Gateway IP Comparison** um zu prüfen, ob der Server erreichbar ist:
```kotlin
fun isInHomeNetwork(): Boolean {
val gatewayIP = getGatewayIP() // z.B. 192.168.0.1
val serverIP = extractIPFromUrl(serverUrl) // z.B. 192.168.0.188
return isSameNetwork(gatewayIP, serverIP) // Prüft /24 Netzwerk
}
```
**Vorteile:**
- ✅ Keine Location Permissions nötig
- ✅ Funktioniert mit allen Android Versionen
- ✅ Zuverlässig und schnell
### Sync Flow
```
1. WorkManager wacht auf (alle 30 Min)
2. Check: WiFi connected?
3. Check: Same network as server?
4. Load local notes
5. Upload neue/geänderte Notes → Server
6. Download remote notes ← Server
7. Merge & resolve conflicts
8. Update local storage
9. Show notification (if changes)
```
---
## <20> Sync-Trigger Übersicht
Die App verwendet **4 verschiedene Sync-Trigger** mit unterschiedlichen Anwendungsfällen:
| Trigger | Datei | Funktion | Wann? | Pre-Check? |
|---------|-------|----------|-------|------------|
| **1. Manueller Sync** | `MainActivity.kt` | `triggerManualSync()` | User klickt auf Sync-Button im Menü | ✅ Ja |
| **2. Auto-Sync (onResume)** | `MainActivity.kt` | `triggerAutoSync()` | App wird geöffnet/fortgesetzt | ✅ Ja |
| **3. Hintergrund-Sync (Periodic)** | `SyncWorker.kt` | `doWork()` | Alle 15/30/60 Minuten (konfigurierbar) | ✅ Ja |
| **4. WiFi-Connect Sync** | `NetworkMonitor.kt``SyncWorker.kt` | `triggerWifiConnectSync()` | WiFi verbunden | ✅ Ja |
### Server-Erreichbarkeits-Check (Pre-Check)
**Alle 4 Sync-Trigger** verwenden vor dem eigentlichen Sync einen **Pre-Check**:
```kotlin
// WebDavSyncService.kt - isServerReachable()
suspend fun isServerReachable(): Boolean = withContext(Dispatchers.IO) {
return@withContext try {
Socket().use { socket ->
socket.connect(InetSocketAddress(host, port), 2000) // 2s Timeout
}
true
} catch (e: Exception) {
Logger.d(TAG, "Server not reachable: ${e.message}")
false
}
}
```
**Warum Socket-Check statt HTTP-Request?**
-**Schneller:** Socket-Connect ist instant, HTTP-Request dauert länger
- 🔋 **Akkuschonender:** Kein HTTP-Overhead (Headers, TLS Handshake, etc.)
- 🎯 **Präziser:** Prüft nur Netzwerk-Erreichbarkeit, nicht Server-Logik
- 🛡️ **Verhindert Fehler:** Erkennt fremde WiFi-Netze bevor Sync-Fehler entsteht
**Wann schlägt der Check fehl?**
- ❌ Server offline/nicht erreichbar
- ❌ Falsches WiFi-Netzwerk (z.B. öffentliches Café-WiFi)
- ❌ Netzwerk noch nicht bereit (DHCP/Routing-Delay nach WiFi-Connect)
- ❌ VPN blockiert Server-Zugriff
- ❌ Keine WebDAV-Server-URL konfiguriert
### Sync-Verhalten nach Trigger-Typ
| Trigger | Bei Server nicht erreichbar | Bei erfolgreichem Sync | Throttling |
|---------|----------------------------|----------------------|------------|
| Manueller Sync | Toast: "Server nicht erreichbar" | Toast: "✅ Gesynct: X Notizen" | Keins |
| Auto-Sync (onResume) | Silent abort (kein Toast) | Toast: "✅ Gesynct: X Notizen" | Max. 1x/Min |
| Hintergrund-Sync | Silent abort (kein Toast) | Silent (LocalBroadcast only) | 15/30/60 Min |
| WiFi-Connect Sync | Silent abort (kein Toast) | Silent (LocalBroadcast only) | WiFi-basiert |
---
## 🔋 Akku-Optimierung
### 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 | 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. **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
```
4. **Conditional Logging**
```kotlin
object Logger {
fun d(tag: String, msg: String) {
if (BuildConfig.DEBUG) Log.d(tag, msg)
}
}
```
5. **Network Constraints**
- Nur WiFi (nicht mobile Daten)
- Nur wenn Server erreichbar
- Keine permanenten Listeners
---
## 📦 WebDAV Sync Details
### Upload Flow
```kotlin
suspend fun uploadNotes(): Int {
val localNotes = storage.loadAllNotes()
var uploadedCount = 0
for (note in localNotes) {
if (note.syncStatus == SyncStatus.PENDING) {
val jsonContent = note.toJson()
val remotePath = "$serverUrl/${note.id}.json"
sardine.put(remotePath, jsonContent.toByteArray())
note.syncStatus = SyncStatus.SYNCED
storage.saveNote(note)
uploadedCount++
}
}
return uploadedCount
}
```
### Download Flow
```kotlin
suspend fun downloadNotes(): DownloadResult {
val remoteFiles = sardine.list(serverUrl)
var downloadedCount = 0
var conflictCount = 0
for (file in remoteFiles) {
if (!file.name.endsWith(".json")) continue
val content = sardine.get(file.href)
val remoteNote = Note.fromJson(content)
val localNote = storage.loadNote(remoteNote.id)
if (localNote == null) {
// Neue Note vom Server
storage.saveNote(remoteNote)
downloadedCount++
} else if (localNote.modifiedAt < remoteNote.modifiedAt) {
// Server hat neuere Version
storage.saveNote(remoteNote)
downloadedCount++
} else if (localNote.modifiedAt > remoteNote.modifiedAt) {
// Lokale Version ist neuer → Conflict
resolveConflict(localNote, remoteNote)
conflictCount++
}
}
return DownloadResult(downloadedCount, conflictCount)
}
```
### Conflict Resolution
Strategie: **Last-Write-Wins** mit **Conflict Copy**
```kotlin
fun resolveConflict(local: Note, remote: Note) {
// Remote Note umbenennen (Conflict Copy)
val conflictNote = remote.copy(
id = "${remote.id}_conflict_${System.currentTimeMillis()}",
title = "${remote.title} (Konflikt)"
)
storage.saveNote(conflictNote)
// Lokale Note bleibt
local.syncStatus = SyncStatus.SYNCED
storage.saveNote(local)
}
```
---
## 🔔 Notifications
### Notification Channels
```kotlin
val channel = NotificationChannel(
"notes_sync_channel",
"Notizen Synchronisierung",
NotificationManager.IMPORTANCE_DEFAULT
)
```
### Success Notification
```kotlin
fun showSyncSuccess(context: Context, count: Int) {
val intent = Intent(context, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(context, 0, intent, FLAGS)
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Sync erfolgreich")
.setContentText("$count Notizen synchronisiert")
.setContentIntent(pendingIntent) // Click öffnet App
.setAutoCancel(true) // Dismiss on click
.build()
notificationManager.notify(NOTIFICATION_ID, notification)
}
```
---
## 🛡️ Permissions
Die App benötigt **minimale Permissions**:
```xml
<!-- Netzwerk -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!-- Notifications -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!-- Boot Receiver -->
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Battery Optimization (optional) -->
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
```
**Keine Location Permissions!**
Wir verwenden Gateway IP Comparison statt SSID-Erkennung. Keine Standortberechtigung nötig.
---
## 🧪 Testing
### Server testen
```bash
# WebDAV Server erreichbar?
curl -u noteuser:password http://192.168.0.188:8080/
# Datei hochladen
echo '{"test":"data"}' > test.json
curl -u noteuser:password -T test.json http://192.168.0.188:8080/test.json
# Datei herunterladen
curl -u noteuser:password http://192.168.0.188:8080/test.json
```
### Android App testen
**Unit Tests:**
```bash
cd android
./gradlew test
```
**Instrumented Tests:**
```bash
./gradlew connectedAndroidTest
```
**Manual Testing Checklist:**
- [ ] Notiz erstellen → in Liste sichtbar
- [ ] Notiz bearbeiten → Änderungen gespeichert
- [ ] Notiz löschen → aus Liste entfernt
- [ ] Manueller Sync → Server Status "Erreichbar"
- [ ] Auto-Sync → Notification nach ~30 Min
- [ ] App schließen → Auto-Sync funktioniert weiter
- [ ] Device Reboot → Auto-Sync startet automatisch
- [ ] Server offline → Error Notification
- [ ] Notification Click → App öffnet sich
---
## 🚀 Build & Deployment
### Debug Build
```bash
cd android
./gradlew assembleDebug
# APK: app/build/outputs/apk/debug/app-debug.apk
```
### Release Build
```bash
./gradlew assembleRelease
# APK: app/build/outputs/apk/release/app-release-unsigned.apk
```
### Signieren (für Distribution)
```bash
# Keystore erstellen
keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-alias
# APK signieren
jarsigner -verbose -sigalg SHA256withRSA -digestalg SHA-256 \
-keystore my-release-key.jks \
app-release-unsigned.apk my-alias
# Optimieren
zipalign -v 4 app-release-unsigned.apk app-release.apk
```
---
## 🐛 Debugging
### LogCat Filter
```bash
# Nur App-Logs
adb logcat -s SimpleNotesApp NetworkMonitor SyncWorker WebDavSyncService
# Mit Timestamps
adb logcat -v time -s SyncWorker
# In Datei speichern
adb logcat -s SyncWorker > sync_debug.log
```
### Common Issues
**Problem: Auto-Sync funktioniert nicht**
```
Lösung: Akku-Optimierung deaktivieren
Settings → Apps → Simple Notes → Battery → Don't optimize
```
**Problem: Server nicht erreichbar**
```
Check:
1. Server läuft? → docker-compose ps
2. IP korrekt? → ip addr show
3. Port offen? → telnet 192.168.0.188 8080
4. Firewall? → sudo ufw allow 8080
```
**Problem: Notifications kommen nicht**
```
Check:
1. Notification Permission erteilt?
2. Do Not Disturb aktiv?
3. App im Background? → Force stop & restart
```
---
## 📚 Dependencies
```gradle
// Core
androidx.core:core-ktx:1.12.0
androidx.appcompat:appcompat:1.6.1
com.google.android.material:material:1.11.0
// Lifecycle
androidx.lifecycle:lifecycle-runtime-ktx:2.7.0
// RecyclerView
androidx.recyclerview:recyclerview:1.3.2
// Coroutines
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
// WorkManager
androidx.work:work-runtime-ktx:2.9.0
// WebDAV Client
com.github.thegrizzlylabs:sardine-android:0.8
// Broadcast (deprecated but working)
androidx.localbroadcastmanager:localbroadcastmanager:1.1.0
```
---
## 🔮 Roadmap
Siehe [UPCOMING.md](UPCOMING.md) für die vollständige Roadmap und geplante Features.
---
## 📖 Weitere Dokumentation
- [Project Docs](https://github.com/inventory69/project-docs/tree/main/simple-notes-sync)
- [Sync Architecture](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/SYNC_ARCHITECTURE.md) - **Detaillierte Sync-Trigger Dokumentation**
- [Android Guide](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/ANDROID_GUIDE.md)
- [Bugfix Documentation](https://github.com/inventory69/project-docs/blob/main/simple-notes-sync/BUGFIX_SYNC_SPAM_AND_NOTIFICATIONS.md)
---
**Letzte Aktualisierung:** Februar 2026