Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62423f5a5b | ||
|
|
9eabc9a5f0 |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -6,6 +6,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
---
|
||||
|
||||
## [1.2.2] - TBD
|
||||
|
||||
### Fixed
|
||||
- **Backward Compatibility for v1.2.0 Users (Critical)**
|
||||
- App now reads BOTH old (Root) AND new (`/notes/`) folder structures
|
||||
- Users upgrading from v1.2.0 no longer lose their existing notes
|
||||
- Server-Restore now finds notes from v1.2.0 stored in Root folder
|
||||
- Automatic deduplication prevents loading the same note twice
|
||||
- Graceful error handling if Root folder is not accessible
|
||||
|
||||
### Technical
|
||||
- `WebDavSyncService.downloadRemoteNotes()` - Dual-mode download (Root + /notes/)
|
||||
- `WebDavSyncService.restoreFromServer()` - Now uses dual-mode download
|
||||
- Migration happens naturally: new uploads go to `/notes/`, old notes stay readable
|
||||
|
||||
---
|
||||
|
||||
## [1.2.1] - 2026-01-05
|
||||
|
||||
### Fixed
|
||||
|
||||
15
README.en.md
15
README.en.md
@@ -6,7 +6,7 @@
|
||||
[](https://m3.material.io/)
|
||||
[](LICENSE)
|
||||
|
||||
**📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Documentation](DOCS.en.md)** · **🚀 [Quick Start](QUICKSTART.en.md)**
|
||||
**📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Documentation](docs/DOCS.en.md)** · **🚀 [Quick Start](QUICKSTART.en.md)**
|
||||
|
||||
**🌍 Languages:** [Deutsch](README.md) · **English**
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
- 🔋 **Battery-friendly** - ~0.2-0.8% per day
|
||||
- 🎨 **Material Design 3** - Dark mode & dynamic colors
|
||||
|
||||
➡️ **Complete feature list:** [FEATURES.en.md](FEATURES.en.md)
|
||||
➡️ **Complete feature list:** [FEATURES.en.md](docs/FEATURES.en.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -71,12 +71,9 @@ docker compose up -d
|
||||
| Document | Content |
|
||||
|----------|---------|
|
||||
| **[QUICKSTART.en.md](QUICKSTART.en.md)** | Step-by-step installation |
|
||||
| **[FEATURES.en.md](FEATURES.en.md)** | Complete feature list |
|
||||
| **[BACKUP.en.md](BACKUP.en.md)** | Backup & restore guide |
|
||||
| **[DESKTOP.en.md](DESKTOP.en.md)** | Desktop integration (Markdown) |
|
||||
| **[DOCS.en.md](DOCS.en.md)** | Technical details & troubleshooting |
|
||||
| **[CHANGELOG.md](CHANGELOG.md)** | Version history |
|
||||
|
||||
| **[FEATURES.en.md](docs/FEATURES.en.md)** | Complete feature list |
|
||||
| **[BACKUP.en.md](docs/BACKUP.en.md)** | Backup & restore guide |
|
||||
| **[DESKTOP.en.md](docs/DESKTOP.en.md)** | Desktop integration (Markdown) |
|
||||
---
|
||||
|
||||
## 🛠️ Development
|
||||
@@ -86,7 +83,7 @@ cd android
|
||||
./gradlew assembleStandardRelease
|
||||
```
|
||||
|
||||
➡️ **Build guide:** [DOCS.en.md](DOCS.en.md)
|
||||
➡️ **Build guide:** [DOCS.en.md](docs/DOCS.en.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -6,7 +6,7 @@
|
||||
[](https://m3.material.io/)
|
||||
[](LICENSE)
|
||||
|
||||
**📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Dokumentation](DOCS.md)** · **🚀 [Quick Start](QUICKSTART.md)**
|
||||
**📱 [APK Download](https://github.com/inventory69/simple-notes-sync/releases/latest)** · **📖 [Dokumentation](docs/DOCS.md)** · **🚀 [Quick Start](QUICKSTART.md)**
|
||||
|
||||
**🌍 Sprachen:** **Deutsch** · [English](README.en.md)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
- 🔋 **Akkuschonend** - ~0.2-0.8% pro Tag
|
||||
- 🎨 **Material Design 3** - Dark Mode & Dynamic Colors
|
||||
|
||||
➡️ **Vollständige Feature-Liste:** [FEATURES.md](FEATURES.md)
|
||||
➡️ **Vollständige Feature-Liste:** [FEATURES.md](docs/FEATURES.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -71,10 +71,10 @@ docker compose up -d
|
||||
| Dokument | Inhalt |
|
||||
|----------|--------|
|
||||
| **[QUICKSTART.md](QUICKSTART.md)** | Schritt-für-Schritt Installation |
|
||||
| **[FEATURES.md](FEATURES.md)** | Vollständige Feature-Liste |
|
||||
| **[BACKUP.md](BACKUP.md)** | Backup & Wiederherstellung |
|
||||
| **[DESKTOP.md](DESKTOP.md)** | Desktop-Integration (Markdown) |
|
||||
| **[DOCS.md](DOCS.md)** | Technische Details & Troubleshooting |
|
||||
| **[FEATURES.md](docs/FEATURES.md)** | Vollständige Feature-Liste |
|
||||
| **[BACKUP.md](docs/BACKUP.md)** | Backup & Wiederherstellung |
|
||||
| **[DESKTOP.md](docs/DESKTOP.md)** | Desktop-Integration (Markdown) |
|
||||
| **[DOCS.md](docs/DOCS.md)** | Technische Details & Troubleshooting |
|
||||
| **[CHANGELOG.md](CHANGELOG.md)** | Versionshistorie |
|
||||
|
||||
---
|
||||
@@ -86,7 +86,7 @@ cd android
|
||||
./gradlew assembleStandardRelease
|
||||
```
|
||||
|
||||
➡️ **Build-Anleitung:** [DOCS.md](DOCS.md)
|
||||
➡️ **Build-Anleitung:** [DOCS.md](docs/DOCS.md)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ android {
|
||||
applicationId = "dev.dettmer.simplenotes"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 6 // 🐛 v1.2.1: Markdown Initial Export Bugfix
|
||||
versionName = "1.2.1" // 🐛 v1.2.1: Markdown Initial Export Bugfix
|
||||
versionCode = 7 // 🔧 v1.2.2: Backward compatibility for v1.2.0 migration
|
||||
versionName = "1.2.2" // 🔧 v1.2.2: Dual-mode download (Root + /notes/)
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
|
||||
@@ -675,49 +675,144 @@ class WebDavSyncService(private val context: Context) {
|
||||
val conflictCount: Int
|
||||
)
|
||||
|
||||
private fun downloadRemoteNotes(sardine: Sardine, serverUrl: String): DownloadResult {
|
||||
private fun downloadRemoteNotes(
|
||||
sardine: Sardine,
|
||||
serverUrl: String,
|
||||
includeRootFallback: Boolean = false // 🆕 v1.2.2: Only for restore from server
|
||||
): DownloadResult {
|
||||
var downloadedCount = 0
|
||||
var conflictCount = 0
|
||||
val processedIds = mutableSetOf<String>() // 🆕 v1.2.2: Track already loaded notes
|
||||
|
||||
try {
|
||||
// 🆕 PHASE 1: Download from /notes/ (new structure v1.2.1+)
|
||||
val notesUrl = getNotesUrl(serverUrl)
|
||||
val resources = sardine.list(notesUrl)
|
||||
Logger.d(TAG, "🔍 Phase 1: Checking /notes/ at: $notesUrl")
|
||||
|
||||
for (resource in resources) {
|
||||
if (resource.isDirectory || !resource.name.endsWith(".json")) {
|
||||
continue
|
||||
}
|
||||
if (sardine.exists(notesUrl)) {
|
||||
Logger.d(TAG, " ✅ /notes/ exists, scanning...")
|
||||
val resources = sardine.list(notesUrl)
|
||||
|
||||
val noteUrl = resource.href.toString()
|
||||
val jsonContent = sardine.get(noteUrl).bufferedReader().use { it.readText() }
|
||||
val remoteNote = Note.fromJson(jsonContent) ?: continue
|
||||
|
||||
val localNote = storage.loadNote(remoteNote.id)
|
||||
|
||||
when {
|
||||
localNote == null -> {
|
||||
// New note from server
|
||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||
downloadedCount++
|
||||
for (resource in resources) {
|
||||
if (resource.isDirectory || !resource.name.endsWith(".json")) {
|
||||
continue
|
||||
}
|
||||
localNote.updatedAt < remoteNote.updatedAt -> {
|
||||
// Remote is newer
|
||||
if (localNote.syncStatus == SyncStatus.PENDING) {
|
||||
// Conflict detected
|
||||
storage.saveNote(localNote.copy(syncStatus = SyncStatus.CONFLICT))
|
||||
conflictCount++
|
||||
} else {
|
||||
// Safe to overwrite
|
||||
|
||||
// 🔧 Fix: Build full URL instead of using href directly
|
||||
val noteUrl = notesUrl.trimEnd('/') + "/" + resource.name
|
||||
val jsonContent = sardine.get(noteUrl).bufferedReader().use { it.readText() }
|
||||
val remoteNote = Note.fromJson(jsonContent) ?: continue
|
||||
|
||||
processedIds.add(remoteNote.id) // 🆕 Mark as processed
|
||||
|
||||
val localNote = storage.loadNote(remoteNote.id)
|
||||
|
||||
when {
|
||||
localNote == null -> {
|
||||
// New note from server
|
||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||
downloadedCount++
|
||||
Logger.d(TAG, " ✅ Downloaded from /notes/: ${remoteNote.id}")
|
||||
}
|
||||
localNote.updatedAt < remoteNote.updatedAt -> {
|
||||
// Remote is newer
|
||||
if (localNote.syncStatus == SyncStatus.PENDING) {
|
||||
// Conflict detected
|
||||
storage.saveNote(localNote.copy(syncStatus = SyncStatus.CONFLICT))
|
||||
conflictCount++
|
||||
} else {
|
||||
// Safe to overwrite
|
||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||
downloadedCount++
|
||||
Logger.d(TAG, " ✅ Updated from /notes/: ${remoteNote.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.d(TAG, " 📊 Phase 1 complete: $downloadedCount notes from /notes/")
|
||||
} else {
|
||||
Logger.w(TAG, " ⚠️ /notes/ does not exist, skipping Phase 1")
|
||||
}
|
||||
|
||||
// 🆕 PHASE 2: BACKWARD-COMPATIBILITY - Download from Root (old structure v1.2.0)
|
||||
// ⚠️ ONLY for restore from server! Normal sync should NOT scan Root
|
||||
if (includeRootFallback) {
|
||||
val rootUrl = serverUrl.trimEnd('/')
|
||||
Logger.d(TAG, "🔍 Phase 2: Checking ROOT at: $rootUrl (Restore mode)")
|
||||
|
||||
try {
|
||||
val rootResources = sardine.list(rootUrl)
|
||||
Logger.d(TAG, " 📂 Found ${rootResources.size} resources in ROOT")
|
||||
|
||||
val oldNotes = rootResources.filter { resource ->
|
||||
!resource.isDirectory &&
|
||||
resource.name.endsWith(".json") &&
|
||||
!resource.path.contains("/notes/") && // Not from /notes/ subdirectory
|
||||
!resource.path.contains("/notes-md/") // Not from /notes-md/
|
||||
}
|
||||
|
||||
Logger.d(TAG, " 🔎 Filtered to ${oldNotes.size} .json files (excluding /notes/ and /notes-md/)")
|
||||
|
||||
if (oldNotes.isNotEmpty()) {
|
||||
Logger.w(TAG, "⚠️ Found ${oldNotes.size} notes in ROOT (old v1.2.0 structure)")
|
||||
|
||||
for (resource in oldNotes) {
|
||||
// 🔧 Fix: Build full URL instead of using href directly
|
||||
val noteUrl = rootUrl.trimEnd('/') + "/" + resource.name
|
||||
Logger.d(TAG, " 📄 Processing: ${resource.name} from ${resource.path}")
|
||||
|
||||
val jsonContent = sardine.get(noteUrl).bufferedReader().use { it.readText() }
|
||||
val remoteNote = Note.fromJson(jsonContent) ?: continue
|
||||
|
||||
// Skip if already loaded from /notes/
|
||||
if (processedIds.contains(remoteNote.id)) {
|
||||
Logger.d(TAG, " ⏭️ Skipping ${remoteNote.id} (already loaded from /notes/)")
|
||||
continue
|
||||
}
|
||||
|
||||
processedIds.add(remoteNote.id)
|
||||
val localNote = storage.loadNote(remoteNote.id)
|
||||
|
||||
when {
|
||||
localNote == null -> {
|
||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||
downloadedCount++
|
||||
Logger.d(TAG, " ✅ Downloaded from ROOT: ${remoteNote.id}")
|
||||
}
|
||||
localNote.updatedAt < remoteNote.updatedAt -> {
|
||||
if (localNote.syncStatus == SyncStatus.PENDING) {
|
||||
storage.saveNote(localNote.copy(syncStatus = SyncStatus.CONFLICT))
|
||||
conflictCount++
|
||||
} else {
|
||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||
downloadedCount++
|
||||
Logger.d(TAG, " ✅ Updated from ROOT: ${remoteNote.id}")
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// Local is newer - do nothing
|
||||
Logger.d(TAG, " ⏭️ Local is newer: ${remoteNote.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Logger.d(TAG, " 📊 Phase 2 complete: downloaded ${oldNotes.size} notes from ROOT")
|
||||
} else {
|
||||
Logger.d(TAG, " ℹ️ No old notes found in ROOT")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.e(TAG, "⚠️ Failed to scan ROOT directory: ${e.message}", e)
|
||||
Logger.e(TAG, " Stack trace: ${e.stackTraceToString()}")
|
||||
// Not fatal - new users may not have root access
|
||||
}
|
||||
} else {
|
||||
Logger.d(TAG, "⏭️ Skipping Phase 2 (Root scan) - only enabled for restore from server")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
// Log error but don't fail entire sync
|
||||
Logger.e(TAG, "❌ downloadRemoteNotes failed", e)
|
||||
}
|
||||
|
||||
Logger.d(TAG, "📊 Total download result: $downloadedCount notes, $conflictCount conflicts")
|
||||
return DownloadResult(downloadedCount, conflictCount)
|
||||
}
|
||||
|
||||
@@ -757,38 +852,18 @@ class WebDavSyncService(private val context: Context) {
|
||||
|
||||
Logger.d(TAG, "🔄 Starting restore from server...")
|
||||
|
||||
val notesUrl = getNotesUrl(serverUrl)
|
||||
// Clear local storage FIRST
|
||||
Logger.d(TAG, "🗑️ Clearing local storage...")
|
||||
storage.deleteAllNotes()
|
||||
|
||||
// List all files on server
|
||||
val resources = sardine.list(notesUrl)
|
||||
val jsonFiles = resources.filter {
|
||||
!it.isDirectory && it.name.endsWith(".json")
|
||||
}
|
||||
// 🆕 v1.2.2: Use downloadRemoteNotes() with Root fallback enabled
|
||||
val result = downloadRemoteNotes(
|
||||
sardine = sardine,
|
||||
serverUrl = serverUrl,
|
||||
includeRootFallback = true // ✅ Enable backward compatibility for restore
|
||||
)
|
||||
|
||||
Logger.d(TAG, "📂 Found ${jsonFiles.size} files on server")
|
||||
|
||||
val restoredNotes = mutableListOf<Note>()
|
||||
|
||||
// Download and parse each file
|
||||
for (resource in jsonFiles) {
|
||||
try {
|
||||
val fileUrl = notesUrl.trimEnd('/') + "/" + resource.name
|
||||
val content = sardine.get(fileUrl).bufferedReader().use { it.readText() }
|
||||
|
||||
val note = Note.fromJson(content)
|
||||
if (note != null) {
|
||||
restoredNotes.add(note)
|
||||
Logger.d(TAG, "✅ Downloaded: ${note.title}")
|
||||
} else {
|
||||
Logger.e(TAG, "❌ Failed to parse ${resource.name}: Note.fromJson returned null")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Logger.e(TAG, "❌ Failed to download ${resource.name}", e)
|
||||
// Continue with other files
|
||||
}
|
||||
}
|
||||
|
||||
if (restoredNotes.isEmpty()) {
|
||||
if (result.downloadedCount == 0) {
|
||||
return@withContext RestoreResult(
|
||||
isSuccess = false,
|
||||
errorMessage = "Keine Notizen auf Server gefunden",
|
||||
@@ -796,22 +871,14 @@ class WebDavSyncService(private val context: Context) {
|
||||
)
|
||||
}
|
||||
|
||||
// Clear local storage
|
||||
Logger.d(TAG, "🗑️ Clearing local storage...")
|
||||
storage.deleteAllNotes()
|
||||
saveLastSyncTimestamp()
|
||||
|
||||
// Save all restored notes
|
||||
Logger.d(TAG, "💾 Saving ${restoredNotes.size} notes...")
|
||||
restoredNotes.forEach { note ->
|
||||
storage.saveNote(note.copy(syncStatus = SyncStatus.SYNCED))
|
||||
}
|
||||
|
||||
Logger.d(TAG, "✅ Restore completed: ${restoredNotes.size} notes")
|
||||
Logger.d(TAG, "✅ Restore completed: ${result.downloadedCount} notes")
|
||||
|
||||
RestoreResult(
|
||||
isSuccess = true,
|
||||
errorMessage = null,
|
||||
restoredCount = restoredNotes.size
|
||||
restoredCount = result.downloadedCount
|
||||
)
|
||||
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -317,7 +317,7 @@ Step-by-step:
|
||||
---
|
||||
|
||||
**📚 See also:**
|
||||
- [QUICKSTART.en.md](QUICKSTART.en.md) - App installation and setup
|
||||
- [QUICKSTART.en.md](../QUICKSTART.en.md) - App installation and setup
|
||||
- [FEATURES.en.md](FEATURES.en.md) - Complete feature list
|
||||
- [DESKTOP.en.md](DESKTOP.en.md) - Desktop integration with Markdown
|
||||
|
||||
@@ -317,7 +317,7 @@ Schritt-für-Schritt:
|
||||
---
|
||||
|
||||
**📚 Siehe auch:**
|
||||
- [QUICKSTART.md](QUICKSTART.md) - App-Installation und Einrichtung
|
||||
- [QUICKSTART.md](../QUICKSTART.md) - App-Installation und Einrichtung
|
||||
- [FEATURES.md](FEATURES.md) - Vollständige Feature-Liste
|
||||
- [DESKTOP.md](DESKTOP.md) - Desktop-Integration mit Markdown
|
||||
|
||||
@@ -498,7 +498,7 @@ Planned for v1.3.0+:
|
||||
---
|
||||
|
||||
**📚 See also:**
|
||||
- [QUICKSTART.en.md](QUICKSTART.en.md) - App setup
|
||||
- [QUICKSTART.en.md](../QUICKSTART.en.md) - App setup
|
||||
- [FEATURES.en.md](FEATURES.en.md) - Complete feature list
|
||||
- [BACKUP.en.md](BACKUP.en.md) - Backup & restore
|
||||
|
||||
@@ -498,7 +498,7 @@ Geplant für v1.3.0+:
|
||||
---
|
||||
|
||||
**📚 Siehe auch:**
|
||||
- [QUICKSTART.md](QUICKSTART.md) - App-Einrichtung
|
||||
- [QUICKSTART.md](../QUICKSTART.md) - App-Einrichtung
|
||||
- [FEATURES.md](FEATURES.md) - Vollständige Feature-Liste
|
||||
- [BACKUP.md](BACKUP.md) - Backup & Wiederherstellung
|
||||
|
||||
@@ -181,9 +181,10 @@
|
||||
- ✅ **OkHttp** - HTTP client (via Sardine)
|
||||
|
||||
### Build Variants
|
||||
- ✅ **Standard** - Google Play version (with Google services prepared)
|
||||
- ✅ **F-Droid** - FOSS version (no Google dependencies)
|
||||
- ✅ **Standard** - Universal APK (100% FOSS, no Google dependencies)
|
||||
- ✅ **F-Droid** - Identical to Standard (100% FOSS)
|
||||
- ✅ **Debug/Release** - Development and production
|
||||
- ✅ **No Google Services** - Completely FOSS, no proprietary libraries
|
||||
|
||||
---
|
||||
|
||||
@@ -209,7 +210,11 @@
|
||||
|
||||
Planned for upcoming versions (see [TODO.md](project-docs/simple-notes-sync/planning/TODO.md)):
|
||||
|
||||
### v1.3.0 - Advanced Organization
|
||||
### v1.3.0 - Web Editor & Organization
|
||||
- ⏳ **Browser-based editor** - Edit notes in web browser
|
||||
- ⏳ **WebDAV access via browser** - No mount needed
|
||||
- ⏳ **Mobile-optimized** - Responsive design
|
||||
- ⏳ **Offline-capable** - Progressive Web App (PWA)
|
||||
- ⏳ **Tags/labels** - Categorize notes
|
||||
- ⏳ **Search** - Full-text search in all notes
|
||||
- ⏳ **Sorting** - By date, title, tags
|
||||
@@ -181,9 +181,10 @@
|
||||
- ✅ **OkHttp** - HTTP Client (via Sardine)
|
||||
|
||||
### Build-Varianten
|
||||
- ✅ **Standard** - Google Play Version (mit Google-Services vorbereitet)
|
||||
- ✅ **F-Droid** - FOSS Version (keine Google-Dependencies)
|
||||
- ✅ **Standard** - Universal APK (100% FOSS, keine Google-Dependencies)
|
||||
- ✅ **F-Droid** - Identisch mit Standard (100% FOSS)
|
||||
- ✅ **Debug/Release** - Entwicklung und Production
|
||||
- ✅ **Keine Google Services** - Komplett FOSS, keine proprietären Bibliotheken
|
||||
|
||||
---
|
||||
|
||||
@@ -209,7 +210,11 @@
|
||||
|
||||
Geplant für kommende Versionen (siehe [TODO.md](project-docs/simple-notes-sync/planning/TODO.md)):
|
||||
|
||||
### v1.3.0 - Erweiterte Organisation
|
||||
### v1.3.0 - Web Editor & Organisation
|
||||
- ⏳ **Browser-basierter Editor** - Notizen im Webbrowser bearbeiten
|
||||
- ⏳ **WebDAV-Zugriff via Browser** - Kein Mount nötig
|
||||
- ⏳ **Mobile-optimiert** - Responsive Design
|
||||
- ⏳ **Offline-fähig** - Progressive Web App (PWA)
|
||||
- ⏳ **Tags/Labels** - Kategorisierung von Notizen
|
||||
- ⏳ **Suche** - Volltextsuche in allen Notizen
|
||||
- ⏳ **Sortierung** - Nach Datum, Titel, Tags
|
||||
12
fastlane/metadata/android/de-DE/changelogs/7.txt
Normal file
12
fastlane/metadata/android/de-DE/changelogs/7.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
v1.2.2 - Rückwärtskompatibilität für v1.2.0 User
|
||||
|
||||
Kritische Fehlerbehebung
|
||||
• Server-Wiederherstellung findet jetzt ALLE Notizen (Root + /notes/)
|
||||
• User die von v1.2.0 upgraden verlieren keine Daten mehr
|
||||
• Alte Notizen aus Root-Ordner werden beim Restore gefunden
|
||||
|
||||
Technische Details
|
||||
• Dual-Mode Download nur bei Server-Restore aktiv
|
||||
• Normale Syncs bleiben schnell (scannen nur /notes/)
|
||||
• Automatische Deduplication verhindert Duplikate
|
||||
• Sanfte Migration: Neue Uploads gehen in /notes/, alte bleiben lesbar
|
||||
12
fastlane/metadata/android/en-US/changelogs/7.txt
Normal file
12
fastlane/metadata/android/en-US/changelogs/7.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
v1.2.2 - Backward Compatibility for v1.2.0 Users
|
||||
|
||||
Critical Bugfix
|
||||
• Server restore now finds ALL notes (Root + /notes/)
|
||||
• Users upgrading from v1.2.0 no longer lose data
|
||||
• Old notes from Root folder are found during restore
|
||||
|
||||
Technical Details
|
||||
• Dual-mode download only active for server restore
|
||||
• Normal syncs remain fast (scan only /notes/)
|
||||
• Automatic deduplication prevents duplicates
|
||||
• Smooth migration: New uploads go to /notes/, old ones remain readable
|
||||
Reference in New Issue
Block a user