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
|
## [1.2.1] - 2026-01-05
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
15
README.en.md
15
README.en.md
@@ -6,7 +6,7 @@
|
|||||||
[](https://m3.material.io/)
|
[](https://m3.material.io/)
|
||||||
[](LICENSE)
|
[](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**
|
**🌍 Languages:** [Deutsch](README.md) · **English**
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
- 🔋 **Battery-friendly** - ~0.2-0.8% per day
|
- 🔋 **Battery-friendly** - ~0.2-0.8% per day
|
||||||
- 🎨 **Material Design 3** - Dark mode & dynamic colors
|
- 🎨 **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 |
|
| Document | Content |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| **[QUICKSTART.en.md](QUICKSTART.en.md)** | Step-by-step installation |
|
| **[QUICKSTART.en.md](QUICKSTART.en.md)** | Step-by-step installation |
|
||||||
| **[FEATURES.en.md](FEATURES.en.md)** | Complete feature list |
|
| **[FEATURES.en.md](docs/FEATURES.en.md)** | Complete feature list |
|
||||||
| **[BACKUP.en.md](BACKUP.en.md)** | Backup & restore guide |
|
| **[BACKUP.en.md](docs/BACKUP.en.md)** | Backup & restore guide |
|
||||||
| **[DESKTOP.en.md](DESKTOP.en.md)** | Desktop integration (Markdown) |
|
| **[DESKTOP.en.md](docs/DESKTOP.en.md)** | Desktop integration (Markdown) |
|
||||||
| **[DOCS.en.md](DOCS.en.md)** | Technical details & troubleshooting |
|
|
||||||
| **[CHANGELOG.md](CHANGELOG.md)** | Version history |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🛠️ Development
|
## 🛠️ Development
|
||||||
@@ -86,7 +83,7 @@ cd android
|
|||||||
./gradlew assembleStandardRelease
|
./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/)
|
[](https://m3.material.io/)
|
||||||
[](LICENSE)
|
[](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)
|
**🌍 Sprachen:** **Deutsch** · [English](README.en.md)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
- 🔋 **Akkuschonend** - ~0.2-0.8% pro Tag
|
- 🔋 **Akkuschonend** - ~0.2-0.8% pro Tag
|
||||||
- 🎨 **Material Design 3** - Dark Mode & Dynamic Colors
|
- 🎨 **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 |
|
| Dokument | Inhalt |
|
||||||
|----------|--------|
|
|----------|--------|
|
||||||
| **[QUICKSTART.md](QUICKSTART.md)** | Schritt-für-Schritt Installation |
|
| **[QUICKSTART.md](QUICKSTART.md)** | Schritt-für-Schritt Installation |
|
||||||
| **[FEATURES.md](FEATURES.md)** | Vollständige Feature-Liste |
|
| **[FEATURES.md](docs/FEATURES.md)** | Vollständige Feature-Liste |
|
||||||
| **[BACKUP.md](BACKUP.md)** | Backup & Wiederherstellung |
|
| **[BACKUP.md](docs/BACKUP.md)** | Backup & Wiederherstellung |
|
||||||
| **[DESKTOP.md](DESKTOP.md)** | Desktop-Integration (Markdown) |
|
| **[DESKTOP.md](docs/DESKTOP.md)** | Desktop-Integration (Markdown) |
|
||||||
| **[DOCS.md](DOCS.md)** | Technische Details & Troubleshooting |
|
| **[DOCS.md](docs/DOCS.md)** | Technische Details & Troubleshooting |
|
||||||
| **[CHANGELOG.md](CHANGELOG.md)** | Versionshistorie |
|
| **[CHANGELOG.md](CHANGELOG.md)** | Versionshistorie |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -86,7 +86,7 @@ cd android
|
|||||||
./gradlew assembleStandardRelease
|
./gradlew assembleStandardRelease
|
||||||
```
|
```
|
||||||
|
|
||||||
➡️ **Build-Anleitung:** [DOCS.md](DOCS.md)
|
➡️ **Build-Anleitung:** [DOCS.md](docs/DOCS.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId = "dev.dettmer.simplenotes"
|
applicationId = "dev.dettmer.simplenotes"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 6 // 🐛 v1.2.1: Markdown Initial Export Bugfix
|
versionCode = 7 // 🔧 v1.2.2: Backward compatibility for v1.2.0 migration
|
||||||
versionName = "1.2.1" // 🐛 v1.2.1: Markdown Initial Export Bugfix
|
versionName = "1.2.2" // 🔧 v1.2.2: Dual-mode download (Root + /notes/)
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
|||||||
@@ -675,49 +675,144 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
val conflictCount: Int
|
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 downloadedCount = 0
|
||||||
var conflictCount = 0
|
var conflictCount = 0
|
||||||
|
val processedIds = mutableSetOf<String>() // 🆕 v1.2.2: Track already loaded notes
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 🆕 PHASE 1: Download from /notes/ (new structure v1.2.1+)
|
||||||
val notesUrl = getNotesUrl(serverUrl)
|
val notesUrl = getNotesUrl(serverUrl)
|
||||||
val resources = sardine.list(notesUrl)
|
Logger.d(TAG, "🔍 Phase 1: Checking /notes/ at: $notesUrl")
|
||||||
|
|
||||||
for (resource in resources) {
|
if (sardine.exists(notesUrl)) {
|
||||||
if (resource.isDirectory || !resource.name.endsWith(".json")) {
|
Logger.d(TAG, " ✅ /notes/ exists, scanning...")
|
||||||
continue
|
val resources = sardine.list(notesUrl)
|
||||||
}
|
|
||||||
|
|
||||||
val noteUrl = resource.href.toString()
|
for (resource in resources) {
|
||||||
val jsonContent = sardine.get(noteUrl).bufferedReader().use { it.readText() }
|
if (resource.isDirectory || !resource.name.endsWith(".json")) {
|
||||||
val remoteNote = Note.fromJson(jsonContent) ?: continue
|
continue
|
||||||
|
|
||||||
val localNote = storage.loadNote(remoteNote.id)
|
|
||||||
|
|
||||||
when {
|
|
||||||
localNote == null -> {
|
|
||||||
// New note from server
|
|
||||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
|
||||||
downloadedCount++
|
|
||||||
}
|
}
|
||||||
localNote.updatedAt < remoteNote.updatedAt -> {
|
|
||||||
// Remote is newer
|
// 🔧 Fix: Build full URL instead of using href directly
|
||||||
if (localNote.syncStatus == SyncStatus.PENDING) {
|
val noteUrl = notesUrl.trimEnd('/') + "/" + resource.name
|
||||||
// Conflict detected
|
val jsonContent = sardine.get(noteUrl).bufferedReader().use { it.readText() }
|
||||||
storage.saveNote(localNote.copy(syncStatus = SyncStatus.CONFLICT))
|
val remoteNote = Note.fromJson(jsonContent) ?: continue
|
||||||
conflictCount++
|
|
||||||
} else {
|
processedIds.add(remoteNote.id) // 🆕 Mark as processed
|
||||||
// Safe to overwrite
|
|
||||||
|
val localNote = storage.loadNote(remoteNote.id)
|
||||||
|
|
||||||
|
when {
|
||||||
|
localNote == null -> {
|
||||||
|
// New note from server
|
||||||
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
storage.saveNote(remoteNote.copy(syncStatus = SyncStatus.SYNCED))
|
||||||
downloadedCount++
|
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) {
|
} 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)
|
return DownloadResult(downloadedCount, conflictCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -757,38 +852,18 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
|
|
||||||
Logger.d(TAG, "🔄 Starting restore from server...")
|
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
|
// 🆕 v1.2.2: Use downloadRemoteNotes() with Root fallback enabled
|
||||||
val resources = sardine.list(notesUrl)
|
val result = downloadRemoteNotes(
|
||||||
val jsonFiles = resources.filter {
|
sardine = sardine,
|
||||||
!it.isDirectory && it.name.endsWith(".json")
|
serverUrl = serverUrl,
|
||||||
}
|
includeRootFallback = true // ✅ Enable backward compatibility for restore
|
||||||
|
)
|
||||||
|
|
||||||
Logger.d(TAG, "📂 Found ${jsonFiles.size} files on server")
|
if (result.downloadedCount == 0) {
|
||||||
|
|
||||||
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()) {
|
|
||||||
return@withContext RestoreResult(
|
return@withContext RestoreResult(
|
||||||
isSuccess = false,
|
isSuccess = false,
|
||||||
errorMessage = "Keine Notizen auf Server gefunden",
|
errorMessage = "Keine Notizen auf Server gefunden",
|
||||||
@@ -796,22 +871,14 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear local storage
|
saveLastSyncTimestamp()
|
||||||
Logger.d(TAG, "🗑️ Clearing local storage...")
|
|
||||||
storage.deleteAllNotes()
|
|
||||||
|
|
||||||
// Save all restored notes
|
Logger.d(TAG, "✅ Restore completed: ${result.downloadedCount} 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")
|
|
||||||
|
|
||||||
RestoreResult(
|
RestoreResult(
|
||||||
isSuccess = true,
|
isSuccess = true,
|
||||||
errorMessage = null,
|
errorMessage = null,
|
||||||
restoredCount = restoredNotes.size
|
restoredCount = result.downloadedCount
|
||||||
)
|
)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ Step-by-step:
|
|||||||
---
|
---
|
||||||
|
|
||||||
**📚 See also:**
|
**📚 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
|
- [FEATURES.en.md](FEATURES.en.md) - Complete feature list
|
||||||
- [DESKTOP.en.md](DESKTOP.en.md) - Desktop integration with Markdown
|
- [DESKTOP.en.md](DESKTOP.en.md) - Desktop integration with Markdown
|
||||||
|
|
||||||
@@ -317,7 +317,7 @@ Schritt-für-Schritt:
|
|||||||
---
|
---
|
||||||
|
|
||||||
**📚 Siehe auch:**
|
**📚 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
|
- [FEATURES.md](FEATURES.md) - Vollständige Feature-Liste
|
||||||
- [DESKTOP.md](DESKTOP.md) - Desktop-Integration mit Markdown
|
- [DESKTOP.md](DESKTOP.md) - Desktop-Integration mit Markdown
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ Planned for v1.3.0+:
|
|||||||
---
|
---
|
||||||
|
|
||||||
**📚 See also:**
|
**📚 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
|
- [FEATURES.en.md](FEATURES.en.md) - Complete feature list
|
||||||
- [BACKUP.en.md](BACKUP.en.md) - Backup & restore
|
- [BACKUP.en.md](BACKUP.en.md) - Backup & restore
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ Geplant für v1.3.0+:
|
|||||||
---
|
---
|
||||||
|
|
||||||
**📚 Siehe auch:**
|
**📚 Siehe auch:**
|
||||||
- [QUICKSTART.md](QUICKSTART.md) - App-Einrichtung
|
- [QUICKSTART.md](../QUICKSTART.md) - App-Einrichtung
|
||||||
- [FEATURES.md](FEATURES.md) - Vollständige Feature-Liste
|
- [FEATURES.md](FEATURES.md) - Vollständige Feature-Liste
|
||||||
- [BACKUP.md](BACKUP.md) - Backup & Wiederherstellung
|
- [BACKUP.md](BACKUP.md) - Backup & Wiederherstellung
|
||||||
|
|
||||||
@@ -181,9 +181,10 @@
|
|||||||
- ✅ **OkHttp** - HTTP client (via Sardine)
|
- ✅ **OkHttp** - HTTP client (via Sardine)
|
||||||
|
|
||||||
### Build Variants
|
### Build Variants
|
||||||
- ✅ **Standard** - Google Play version (with Google services prepared)
|
- ✅ **Standard** - Universal APK (100% FOSS, no Google dependencies)
|
||||||
- ✅ **F-Droid** - FOSS version (no Google dependencies)
|
- ✅ **F-Droid** - Identical to Standard (100% FOSS)
|
||||||
- ✅ **Debug/Release** - Development and production
|
- ✅ **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)):
|
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
|
- ⏳ **Tags/labels** - Categorize notes
|
||||||
- ⏳ **Search** - Full-text search in all notes
|
- ⏳ **Search** - Full-text search in all notes
|
||||||
- ⏳ **Sorting** - By date, title, tags
|
- ⏳ **Sorting** - By date, title, tags
|
||||||
@@ -181,9 +181,10 @@
|
|||||||
- ✅ **OkHttp** - HTTP Client (via Sardine)
|
- ✅ **OkHttp** - HTTP Client (via Sardine)
|
||||||
|
|
||||||
### Build-Varianten
|
### Build-Varianten
|
||||||
- ✅ **Standard** - Google Play Version (mit Google-Services vorbereitet)
|
- ✅ **Standard** - Universal APK (100% FOSS, keine Google-Dependencies)
|
||||||
- ✅ **F-Droid** - FOSS Version (keine Google-Dependencies)
|
- ✅ **F-Droid** - Identisch mit Standard (100% FOSS)
|
||||||
- ✅ **Debug/Release** - Entwicklung und Production
|
- ✅ **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)):
|
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
|
- ⏳ **Tags/Labels** - Kategorisierung von Notizen
|
||||||
- ⏳ **Suche** - Volltextsuche in allen Notizen
|
- ⏳ **Suche** - Volltextsuche in allen Notizen
|
||||||
- ⏳ **Sortierung** - Nach Datum, Titel, Tags
|
- ⏳ **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