feat(v1.5.0): Complete i18n implementation + Language Selector feature
- Added comprehensive English (strings.xml) and German (strings-de.xml) localization with 400+ strings - Created new LanguageSettingsScreen with System Default, English, and German options - Fixed hardcoded German notification toasts in MainActivity and ComposeMainActivity - Integrated Language selector in Settings as top-level menu item - Changed ComposeSettingsActivity from ComponentActivity to AppCompatActivity for AppCompatDelegate compatibility - Added locales_config.xml for Android 13+ Per-App Language support - Updated Extensions.kt with i18n-aware timestamp formatting (toReadableTime with context) - Translated all UI strings including settings, toasts, notifications, and error messages - Added dynamic language display in SettingsMainScreen showing current language Fixes: - Notification permission toast now respects system language setting - Activity correctly restarts when language is changed - All string formatting with parameters properly localized Migration: - MainViewModel: All toast messages now use getString() - SettingsViewModel: All toast and dialog messages localized - NotificationHelper: Notification titles and messages translated - UrlValidator: Error messages now accept Context parameter for translation - NoteCard, DeleteConfirmationDialog, SyncStatusBanner: All strings externalized Testing completed on device with both EN and DE locale switching. Closes #5
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
android:localeConfig="@xml/locales_config"
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.SimpleNotes"
|
android:theme="@style/Theme.SimpleNotes"
|
||||||
|
|||||||
@@ -467,10 +467,10 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val checkboxAlways = dialogView.findViewById<CheckBox>(R.id.checkboxAlwaysDeleteFromServer)
|
val checkboxAlways = dialogView.findViewById<CheckBox>(R.id.checkboxAlwaysDeleteFromServer)
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
MaterialAlertDialogBuilder(this)
|
||||||
.setTitle("Notiz löschen")
|
.setTitle(getString(R.string.legacy_delete_dialog_title))
|
||||||
.setMessage("\"${note.title}\" wird lokal gelöscht.\n\nAuch vom Server löschen?")
|
.setMessage(getString(R.string.legacy_delete_dialog_message, note.title))
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setNeutralButton("Abbrechen") { _, _ ->
|
.setNeutralButton(getString(R.string.cancel)) { _, _ ->
|
||||||
// RESTORE: Re-submit original list (note is NOT deleted from storage)
|
// RESTORE: Re-submit original list (note is NOT deleted from storage)
|
||||||
adapter.submitList(originalList)
|
adapter.submitList(originalList)
|
||||||
}
|
}
|
||||||
@@ -485,7 +485,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
// NOW actually delete from storage
|
// NOW actually delete from storage
|
||||||
deleteNoteLocally(note, deleteFromServer = false)
|
deleteNoteLocally(note, deleteFromServer = false)
|
||||||
}
|
}
|
||||||
.setNegativeButton("Vom Server löschen") { _, _ ->
|
.setNegativeButton(getString(R.string.legacy_delete_from_server)) { _, _ ->
|
||||||
if (checkboxAlways.isChecked) {
|
if (checkboxAlways.isChecked) {
|
||||||
prefs.edit().putBoolean(Constants.KEY_ALWAYS_DELETE_FROM_SERVER, true).apply()
|
prefs.edit().putBoolean(Constants.KEY_ALWAYS_DELETE_FROM_SERVER, true).apply()
|
||||||
}
|
}
|
||||||
@@ -507,13 +507,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// Show Snackbar with UNDO option
|
// Show Snackbar with UNDO option
|
||||||
val message = if (deleteFromServer) {
|
val message = if (deleteFromServer) {
|
||||||
"\"${note.title}\" wird lokal und vom Server gelöscht"
|
getString(R.string.legacy_delete_with_server, note.title)
|
||||||
} else {
|
} else {
|
||||||
"\"${note.title}\" lokal gelöscht (Server bleibt)"
|
getString(R.string.legacy_delete_local_only, note.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
Snackbar.make(recyclerViewNotes, message, Snackbar.LENGTH_LONG)
|
Snackbar.make(recyclerViewNotes, message, Snackbar.LENGTH_LONG)
|
||||||
.setAction("RÜCKGÄNGIG") {
|
.setAction(getString(R.string.snackbar_undo)) {
|
||||||
// UNDO: Restore note
|
// UNDO: Restore note
|
||||||
storage.saveNote(note)
|
storage.saveNote(note)
|
||||||
pendingDeletions.remove(note.id)
|
pendingDeletions.remove(note.id)
|
||||||
@@ -535,7 +535,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
"Vom Server gelöscht",
|
getString(R.string.snackbar_deleted_from_server),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
@@ -543,7 +543,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
this@MainActivity,
|
this@MainActivity,
|
||||||
"Server-Löschung fehlgeschlagen",
|
getString(R.string.snackbar_server_delete_failed),
|
||||||
Toast.LENGTH_LONG
|
Toast.LENGTH_LONG
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
@@ -800,10 +800,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
REQUEST_NOTIFICATION_PERMISSION -> {
|
REQUEST_NOTIFICATION_PERMISSION -> {
|
||||||
if (grantResults.isNotEmpty() &&
|
if (grantResults.isNotEmpty() &&
|
||||||
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
showToast("Benachrichtigungen aktiviert")
|
showToast(getString(R.string.toast_notifications_enabled))
|
||||||
} else {
|
} else {
|
||||||
showToast("Benachrichtigungen deaktiviert. " +
|
showToast(getString(R.string.toast_notifications_disabled))
|
||||||
"Du kannst sie in den Einstellungen aktivieren.")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,9 +227,9 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun updateProtocolHint() {
|
private fun updateProtocolHint() {
|
||||||
protocolHintText.text = if (radioHttp.isChecked) {
|
protocolHintText.text = if (radioHttp.isChecked) {
|
||||||
"HTTP nur für lokale Netzwerke (z.B. 192.168.x.x, 10.x.x.x)"
|
getString(R.string.server_connection_http_hint)
|
||||||
} else {
|
} else {
|
||||||
"HTTPS für sichere Verbindungen über das Internet"
|
getString(R.string.server_connection_https_hint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,7 +359,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
60L -> "60 Minuten"
|
60L -> "60 Minuten"
|
||||||
else -> "$newInterval Minuten"
|
else -> "$newInterval Minuten"
|
||||||
}
|
}
|
||||||
showToast("⏱️ Sync-Intervall auf $intervalText geändert")
|
showToast(getString(R.string.toast_sync_interval_changed, intervalText))
|
||||||
Logger.i(TAG, "Sync interval changed to $newInterval minutes, restarted periodic sync")
|
Logger.i(TAG, "Sync interval changed to $newInterval minutes, restarted periodic sync")
|
||||||
} else {
|
} else {
|
||||||
showToast("⏱️ Sync-Intervall gespeichert (Auto-Sync ist deaktiviert)")
|
showToast("⏱️ Sync-Intervall gespeichert (Auto-Sync ist deaktiviert)")
|
||||||
@@ -379,7 +379,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
textViewAppVersion.text = "Version $versionName ($versionCode)"
|
textViewAppVersion.text = "Version $versionName ($versionCode)"
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e(TAG, "Failed to load version info", e)
|
Logger.e(TAG, "Failed to load version info", e)
|
||||||
textViewAppVersion.text = "Version nicht verfügbar"
|
textViewAppVersion.text = getString(R.string.version_not_available)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitHub Repository Card
|
// GitHub Repository Card
|
||||||
@@ -475,12 +475,12 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun showClearLogsConfirmation() {
|
private fun showClearLogsConfirmation() {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("Logs löschen?")
|
.setTitle(getString(R.string.debug_delete_logs_title))
|
||||||
.setMessage("Alle gespeicherten Sync-Logs werden unwiderruflich gelöscht.")
|
.setMessage(getString(R.string.debug_delete_logs_message))
|
||||||
.setPositiveButton("Löschen") { _, _ ->
|
.setPositiveButton(getString(R.string.delete)) { _, _ ->
|
||||||
clearLogs()
|
clearLogs()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Abbrechen", null)
|
.setNegativeButton(getString(R.string.cancel), null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,13 +491,13 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
try {
|
try {
|
||||||
val cleared = Logger.clearLogFile(this)
|
val cleared = Logger.clearLogFile(this)
|
||||||
if (cleared) {
|
if (cleared) {
|
||||||
showToast("🗑️ Logs gelöscht")
|
showToast(getString(R.string.toast_logs_deleted))
|
||||||
} else {
|
} else {
|
||||||
showToast("📭 Keine Logs zum Löschen")
|
showToast(getString(R.string.toast_no_logs_to_delete))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e(TAG, "Failed to clear logs", e)
|
Logger.e(TAG, "Failed to clear logs", e)
|
||||||
showToast("❌ Fehler beim Löschen: ${e.message}")
|
showToast(getString(R.string.toast_logs_delete_error, e.message ?: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -510,7 +510,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Logger.e(TAG, "Failed to open URL: $url", e)
|
Logger.e(TAG, "Failed to open URL: $url", e)
|
||||||
showToast("❌ Fehler beim Öffnen des Links")
|
showToast(getString(R.string.toast_link_error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -524,7 +524,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// 🔥 v1.1.2: Validate HTTP URL (only allow for local networks)
|
// 🔥 v1.1.2: Validate HTTP URL (only allow for local networks)
|
||||||
if (fullUrl.isNotEmpty()) {
|
if (fullUrl.isNotEmpty()) {
|
||||||
val (isValid, errorMessage) = UrlValidator.validateHttpUrl(fullUrl)
|
val (isValid, errorMessage) = UrlValidator.validateHttpUrl(this, fullUrl)
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
// Only show error in TextField (no Toast)
|
// Only show error in TextField (no Toast)
|
||||||
textInputLayoutServerUrl.isErrorEnabled = true
|
textInputLayoutServerUrl.isErrorEnabled = true
|
||||||
@@ -552,7 +552,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// 🔥 v1.1.2: Validate before testing
|
// 🔥 v1.1.2: Validate before testing
|
||||||
if (fullUrl.isNotEmpty()) {
|
if (fullUrl.isNotEmpty()) {
|
||||||
val (isValid, errorMessage) = UrlValidator.validateHttpUrl(fullUrl)
|
val (isValid, errorMessage) = UrlValidator.validateHttpUrl(this, fullUrl)
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
// Only show error in TextField (no Toast)
|
// Only show error in TextField (no Toast)
|
||||||
textInputLayoutServerUrl.isErrorEnabled = true
|
textInputLayoutServerUrl.isErrorEnabled = true
|
||||||
@@ -646,7 +646,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
textViewServerStatus.text = "🔍 Prüfe..."
|
textViewServerStatus.text = getString(R.string.status_checking)
|
||||||
textViewServerStatus.setTextColor(getColor(android.R.color.darker_gray))
|
textViewServerStatus.setTextColor(getColor(android.R.color.darker_gray))
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
@@ -803,12 +803,12 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
.setMessage(
|
.setMessage(
|
||||||
"Damit die App im Hintergrund synchronisieren kann, " +
|
"Damit die App im Hintergrund synchronisieren kann, " +
|
||||||
"muss die Akku-Optimierung deaktiviert werden.\n\n" +
|
"muss die Akku-Optimierung deaktiviert werden.\n\n" +
|
||||||
"Bitte wähle 'Nicht optimieren' für Simple Notes."
|
getString(R.string.battery_optimization_dialog_message)
|
||||||
)
|
)
|
||||||
.setPositiveButton("Einstellungen öffnen") { _, _ ->
|
.setPositiveButton(getString(R.string.battery_optimization_open_settings)) { _, _ ->
|
||||||
openBatteryOptimizationSettings()
|
openBatteryOptimizationSettings()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Später") { dialog, _ ->
|
.setNegativeButton(getString(R.string.battery_optimization_later)) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
@@ -915,20 +915,20 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
// Radio Buttons erstellen
|
// Radio Buttons erstellen
|
||||||
val radioMerge = android.widget.RadioButton(this).apply {
|
val radioMerge = android.widget.RadioButton(this).apply {
|
||||||
text = "⚪ Zusammenführen (Standard)\n → Neue hinzufügen, Bestehende behalten"
|
text = getString(R.string.backup_mode_merge_full)
|
||||||
id = android.view.View.generateViewId()
|
id = android.view.View.generateViewId()
|
||||||
isChecked = true
|
isChecked = true
|
||||||
setPadding(10, 10, 10, 10)
|
setPadding(10, 10, 10, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
val radioReplace = android.widget.RadioButton(this).apply {
|
val radioReplace = android.widget.RadioButton(this).apply {
|
||||||
text = "⚪ Ersetzen\n → Alle löschen & Backup importieren"
|
text = getString(R.string.backup_mode_replace_full)
|
||||||
id = android.view.View.generateViewId()
|
id = android.view.View.generateViewId()
|
||||||
setPadding(10, 10, 10, 10)
|
setPadding(10, 10, 10, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
val radioOverwrite = android.widget.RadioButton(this).apply {
|
val radioOverwrite = android.widget.RadioButton(this).apply {
|
||||||
text = "⚪ Duplikate überschreiben\n → Backup gewinnt bei Konflikten"
|
text = getString(R.string.backup_mode_overwrite_full)
|
||||||
id = android.view.View.generateViewId()
|
id = android.view.View.generateViewId()
|
||||||
setPadding(10, 10, 10, 10)
|
setPadding(10, 10, 10, 10)
|
||||||
}
|
}
|
||||||
@@ -978,7 +978,7 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
RestoreSource.WEBDAV_SERVER -> performRestoreFromServer(selectedMode)
|
RestoreSource.WEBDAV_SERVER -> performRestoreFromServer(selectedMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.setNegativeButton("Abbrechen", null)
|
.setNegativeButton(getString(R.string.cancel), null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.net.Uri
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.google.gson.GsonBuilder
|
import com.google.gson.GsonBuilder
|
||||||
import dev.dettmer.simplenotes.BuildConfig
|
import dev.dettmer.simplenotes.BuildConfig
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
import dev.dettmer.simplenotes.storage.NotesStorage
|
import dev.dettmer.simplenotes.storage.NotesStorage
|
||||||
import dev.dettmer.simplenotes.utils.Logger
|
import dev.dettmer.simplenotes.utils.Logger
|
||||||
@@ -144,7 +145,7 @@ class BackupManager(private val context: Context) {
|
|||||||
if (!validationResult.isValid) {
|
if (!validationResult.isValid) {
|
||||||
return@withContext RestoreResult(
|
return@withContext RestoreResult(
|
||||||
success = false,
|
success = false,
|
||||||
error = validationResult.errorMessage ?: "Ungültige Backup-Datei"
|
error = validationResult.errorMessage ?: context.getString(R.string.error_invalid_backup_file)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +172,7 @@ class BackupManager(private val context: Context) {
|
|||||||
Logger.e(TAG, "Failed to restore backup", e)
|
Logger.e(TAG, "Failed to restore backup", e)
|
||||||
RestoreResult(
|
RestoreResult(
|
||||||
success = false,
|
success = false,
|
||||||
error = "Wiederherstellung fehlgeschlagen: ${e.message}"
|
error = context.getString(R.string.error_restore_failed, e.message ?: "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,8 +188,7 @@ class BackupManager(private val context: Context) {
|
|||||||
if (backupData.backupVersion > BACKUP_VERSION) {
|
if (backupData.backupVersion > BACKUP_VERSION) {
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid = false,
|
isValid = false,
|
||||||
errorMessage = "Backup-Version nicht unterstützt " +
|
errorMessage = context.getString(R.string.error_backup_version_unsupported, backupData.backupVersion, BACKUP_VERSION)
|
||||||
"(v${backupData.backupVersion} benötigt v${BACKUP_VERSION}+)"
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ class BackupManager(private val context: Context) {
|
|||||||
if (backupData.notes.isEmpty()) {
|
if (backupData.notes.isEmpty()) {
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid = false,
|
isValid = false,
|
||||||
errorMessage = "Backup enthält keine Notizen"
|
errorMessage = context.getString(R.string.error_backup_empty)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,7 +208,7 @@ class BackupManager(private val context: Context) {
|
|||||||
if (invalidNotes.isNotEmpty()) {
|
if (invalidNotes.isNotEmpty()) {
|
||||||
return ValidationResult(
|
return ValidationResult(
|
||||||
isValid = false,
|
isValid = false,
|
||||||
errorMessage = "Backup enthält ${invalidNotes.size} ungültige Notizen"
|
errorMessage = context.getString(R.string.error_backup_invalid_notes, invalidNotes.size)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +217,7 @@ class BackupManager(private val context: Context) {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
ValidationResult(
|
ValidationResult(
|
||||||
isValid = false,
|
isValid = false,
|
||||||
errorMessage = "Backup-Datei beschädigt oder ungültig: ${e.message}"
|
errorMessage = context.getString(R.string.error_backup_corrupt, e.message ?: "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,7 +241,7 @@ class BackupManager(private val context: Context) {
|
|||||||
success = true,
|
success = true,
|
||||||
importedNotes = newNotes.size,
|
importedNotes = newNotes.size,
|
||||||
skippedNotes = skippedNotes,
|
skippedNotes = skippedNotes,
|
||||||
message = "${newNotes.size} neue Notizen importiert, $skippedNotes übersprungen"
|
message = context.getString(R.string.restore_merge_result, newNotes.size, skippedNotes)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -262,10 +262,10 @@ class BackupManager(private val context: Context) {
|
|||||||
success = true,
|
success = true,
|
||||||
importedNotes = backupNotes.size,
|
importedNotes = backupNotes.size,
|
||||||
skippedNotes = 0,
|
skippedNotes = 0,
|
||||||
message = "Alle Notizen ersetzt: ${backupNotes.size} importiert"
|
message = context.getString(R.string.restore_replace_result, backupNotes.size)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restore-Modus: OVERWRITE_DUPLICATES
|
* Restore-Modus: OVERWRITE_DUPLICATES
|
||||||
* Backup überschreibt bei ID-Konflikten
|
* Backup überschreibt bei ID-Konflikten
|
||||||
@@ -287,7 +287,7 @@ class BackupManager(private val context: Context) {
|
|||||||
importedNotes = newNotes.size,
|
importedNotes = newNotes.size,
|
||||||
skippedNotes = 0,
|
skippedNotes = 0,
|
||||||
overwrittenNotes = overwrittenNotes.size,
|
overwrittenNotes = overwrittenNotes.size,
|
||||||
message = "${newNotes.size} neu, ${overwrittenNotes.size} überschrieben"
|
message = context.getString(R.string.restore_overwrite_result, newNotes.size, overwrittenNotes.size)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.net.NetworkCapabilities
|
|||||||
import com.thegrizzlylabs.sardineandroid.Sardine
|
import com.thegrizzlylabs.sardineandroid.Sardine
|
||||||
import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
|
import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
|
||||||
import dev.dettmer.simplenotes.BuildConfig
|
import dev.dettmer.simplenotes.BuildConfig
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.DeletionTracker
|
import dev.dettmer.simplenotes.models.DeletionTracker
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
import dev.dettmer.simplenotes.models.SyncStatus
|
import dev.dettmer.simplenotes.models.SyncStatus
|
||||||
@@ -1852,15 +1853,15 @@ class WebDavSyncService(private val context: Context) {
|
|||||||
suspend fun manualMarkdownSync(): ManualMarkdownSyncResult = withContext(Dispatchers.IO) {
|
suspend fun manualMarkdownSync(): ManualMarkdownSyncResult = withContext(Dispatchers.IO) {
|
||||||
return@withContext try {
|
return@withContext try {
|
||||||
val sardine = getOrCreateSardine()
|
val sardine = getOrCreateSardine()
|
||||||
?: throw SyncException("Sardine client konnte nicht erstellt werden")
|
?: throw SyncException(context.getString(R.string.error_sardine_client_failed))
|
||||||
val serverUrl = getServerUrl()
|
val serverUrl = getServerUrl()
|
||||||
?: throw SyncException("Server-URL nicht konfiguriert")
|
?: throw SyncException(context.getString(R.string.error_server_url_not_configured))
|
||||||
|
|
||||||
val username = prefs.getString(Constants.KEY_USERNAME, "") ?: ""
|
val username = prefs.getString(Constants.KEY_USERNAME, "") ?: ""
|
||||||
val password = prefs.getString(Constants.KEY_PASSWORD, "") ?: ""
|
val password = prefs.getString(Constants.KEY_PASSWORD, "") ?: ""
|
||||||
|
|
||||||
if (serverUrl.isBlank() || username.isBlank() || password.isBlank()) {
|
if (serverUrl.isBlank() || username.isBlank() || password.isBlank()) {
|
||||||
throw SyncException("WebDAV-Server nicht vollständig konfiguriert")
|
throw SyncException(context.getString(R.string.error_server_not_configured))
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.d(TAG, "🔄 Manual Markdown Sync START")
|
Logger.d(TAG, "🔄 Manual Markdown Sync START")
|
||||||
|
|||||||
@@ -24,10 +24,12 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.editor.ComposeNoteEditorActivity
|
import dev.dettmer.simplenotes.ui.editor.ComposeNoteEditorActivity
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.models.SyncStatus
|
import dev.dettmer.simplenotes.models.SyncStatus
|
||||||
@@ -339,10 +341,10 @@ class ComposeMainActivity : ComponentActivity() {
|
|||||||
REQUEST_NOTIFICATION_PERMISSION -> {
|
REQUEST_NOTIFICATION_PERMISSION -> {
|
||||||
if (grantResults.isNotEmpty() &&
|
if (grantResults.isNotEmpty() &&
|
||||||
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
Toast.makeText(this, "Benachrichtigungen aktiviert", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, getString(R.string.toast_notifications_enabled), Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(this,
|
Toast.makeText(this,
|
||||||
"Benachrichtigungen deaktiviert. Du kannst sie in den Einstellungen aktivieren.",
|
getString(R.string.toast_notifications_disabled),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
@@ -363,21 +365,21 @@ private fun DeleteConfirmationDialog(
|
|||||||
) {
|
) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text("Notiz löschen") },
|
title = { Text(stringResource(R.string.legacy_delete_dialog_title)) },
|
||||||
text = {
|
text = {
|
||||||
Text("\"$noteTitle\" wird lokal gelöscht.\n\nAuch vom Server löschen?")
|
Text(stringResource(R.string.legacy_delete_dialog_message, noteTitle))
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text("Abbrechen")
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onDeleteLocal) {
|
TextButton(onClick = onDeleteLocal) {
|
||||||
Text("Nur lokal")
|
Text(stringResource(R.string.delete_local_only))
|
||||||
}
|
}
|
||||||
TextButton(onClick = onDeleteFromServer) {
|
TextButton(onClick = onDeleteFromServer) {
|
||||||
Text("Vom Server löschen")
|
Text(stringResource(R.string.legacy_delete_from_server))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.sync.SyncStateManager
|
import dev.dettmer.simplenotes.sync.SyncStateManager
|
||||||
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
import dev.dettmer.simplenotes.ui.main.components.DeleteConfirmationDialog
|
||||||
@@ -231,7 +233,7 @@ private fun MainTopBar(
|
|||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = "Simple Notes",
|
text = stringResource(R.string.main_title),
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -242,13 +244,13 @@ private fun MainTopBar(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Refresh,
|
imageVector = Icons.Default.Refresh,
|
||||||
contentDescription = "Synchronisieren"
|
contentDescription = stringResource(R.string.action_sync)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
IconButton(onClick = onSettingsClick) {
|
IconButton(onClick = onSettingsClick) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Settings,
|
imageVector = Icons.Default.Settings,
|
||||||
contentDescription = "Einstellungen"
|
contentDescription = stringResource(R.string.action_settings)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -276,13 +278,13 @@ private fun SelectionTopBar(
|
|||||||
IconButton(onClick = onCloseSelection) {
|
IconButton(onClick = onCloseSelection) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Close,
|
imageVector = Icons.Default.Close,
|
||||||
contentDescription = "Auswahl beenden"
|
contentDescription = stringResource(R.string.action_close_selection)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
title = {
|
title = {
|
||||||
Text(
|
Text(
|
||||||
text = "$selectedCount ausgewählt",
|
text = stringResource(R.string.selection_count, selectedCount),
|
||||||
style = MaterialTheme.typography.titleLarge
|
style = MaterialTheme.typography.titleLarge
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -292,7 +294,7 @@ private fun SelectionTopBar(
|
|||||||
IconButton(onClick = onSelectAll) {
|
IconButton(onClick = onSelectAll) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.SelectAll,
|
imageVector = Icons.Default.SelectAll,
|
||||||
contentDescription = "Alle auswählen"
|
contentDescription = stringResource(R.string.action_select_all)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -303,7 +305,7 @@ private fun SelectionTopBar(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Delete,
|
imageVector = Icons.Default.Delete,
|
||||||
contentDescription = "Ausgewählte löschen",
|
contentDescription = stringResource(R.string.action_delete_selected),
|
||||||
tint = if (selectedCount > 0) {
|
tint = if (selectedCount > 0) {
|
||||||
MaterialTheme.colorScheme.error
|
MaterialTheme.colorScheme.error
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import android.content.Context
|
|||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.storage.NotesStorage
|
import dev.dettmer.simplenotes.storage.NotesStorage
|
||||||
import dev.dettmer.simplenotes.sync.SyncStateManager
|
import dev.dettmer.simplenotes.sync.SyncStateManager
|
||||||
import dev.dettmer.simplenotes.sync.WebDavSyncService
|
import dev.dettmer.simplenotes.sync.WebDavSyncService
|
||||||
@@ -236,15 +237,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Show snackbar with undo
|
// Show snackbar with undo
|
||||||
val count = selectedNotes.size
|
val count = selectedNotes.size
|
||||||
val message = if (deleteFromServer) {
|
val message = if (deleteFromServer) {
|
||||||
"$count Notiz${if (count > 1) "en" else ""} werden vom Server gelöscht"
|
getString(R.string.snackbar_notes_deleted_server, count)
|
||||||
} else {
|
} else {
|
||||||
"$count Notiz${if (count > 1) "en" else ""} lokal gelöscht"
|
getString(R.string.snackbar_notes_deleted_local, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_showSnackbar.emit(SnackbarData(
|
_showSnackbar.emit(SnackbarData(
|
||||||
message = message,
|
message = message,
|
||||||
actionLabel = "RÜCKGÄNGIG",
|
actionLabel = getString(R.string.snackbar_undo),
|
||||||
onAction = {
|
onAction = {
|
||||||
undoDeleteMultiple(selectedNotes)
|
undoDeleteMultiple(selectedNotes)
|
||||||
}
|
}
|
||||||
@@ -336,15 +337,15 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
// Show snackbar with undo
|
// Show snackbar with undo
|
||||||
val message = if (deleteFromServer) {
|
val message = if (deleteFromServer) {
|
||||||
"\"${note.title}\" wird vom Server gelöscht"
|
getString(R.string.snackbar_note_deleted_server, note.title)
|
||||||
} else {
|
} else {
|
||||||
"\"${note.title}\" lokal gelöscht"
|
getString(R.string.snackbar_note_deleted_local, note.title)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_showSnackbar.emit(SnackbarData(
|
_showSnackbar.emit(SnackbarData(
|
||||||
message = message,
|
message = message,
|
||||||
actionLabel = "RÜCKGÄNGIG",
|
actionLabel = getString(R.string.snackbar_undo),
|
||||||
onAction = {
|
onAction = {
|
||||||
undoDelete(note)
|
undoDelete(note)
|
||||||
}
|
}
|
||||||
@@ -390,12 +391,12 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
_showToast.emit("Vom Server gelöscht")
|
_showToast.emit(getString(R.string.snackbar_deleted_from_server))
|
||||||
} else {
|
} else {
|
||||||
_showToast.emit("Server-Löschung fehlgeschlagen")
|
_showToast.emit(getString(R.string.snackbar_server_delete_failed))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_showToast.emit("Server-Fehler: ${e.message}")
|
_showToast.emit(getString(R.string.snackbar_server_error, e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
// Remove from pending deletions
|
// Remove from pending deletions
|
||||||
_pendingDeletions.value = _pendingDeletions.value - noteId
|
_pendingDeletions.value = _pendingDeletions.value - noteId
|
||||||
@@ -446,7 +447,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
if (!isReachable) {
|
if (!isReachable) {
|
||||||
Logger.d(TAG, "⏭️ $source Sync: Server not reachable")
|
Logger.d(TAG, "⏭️ $source Sync: Server not reachable")
|
||||||
SyncStateManager.markError("Server nicht erreichbar")
|
SyncStateManager.markError(getString(R.string.snackbar_server_unreachable))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +457,7 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
SyncStateManager.markCompleted("${result.syncedCount} Notizen")
|
SyncStateManager.markCompleted(getString(R.string.toast_sync_success, result.syncedCount))
|
||||||
loadNotes()
|
loadNotes()
|
||||||
} else {
|
} else {
|
||||||
SyncStateManager.markError(result.errorMessage)
|
SyncStateManager.markError(result.errorMessage)
|
||||||
@@ -524,8 +525,8 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
|
|
||||||
if (result.isSuccess && result.syncedCount > 0) {
|
if (result.isSuccess && result.syncedCount > 0) {
|
||||||
Logger.d(TAG, "✅ Auto-sync successful ($source): ${result.syncedCount} notes")
|
Logger.d(TAG, "✅ Auto-sync successful ($source): ${result.syncedCount} notes")
|
||||||
SyncStateManager.markCompleted("${result.syncedCount} Notizen")
|
SyncStateManager.markCompleted(getString(R.string.toast_sync_success, result.syncedCount))
|
||||||
_showToast.emit("✅ Gesynct: ${result.syncedCount} Notizen")
|
_showToast.emit(getString(R.string.snackbar_synced_count, result.syncedCount))
|
||||||
loadNotes()
|
loadNotes()
|
||||||
} else if (result.isSuccess) {
|
} else if (result.isSuccess) {
|
||||||
Logger.d(TAG, "ℹ️ Auto-sync ($source): No changes")
|
Logger.d(TAG, "ℹ️ Auto-sync ($source): No changes")
|
||||||
@@ -559,6 +560,11 @@ class MainViewModel(application: Application) : AndroidViewModel(application) {
|
|||||||
// Helpers
|
// Helpers
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private fun getString(resId: Int): String = getApplication<android.app.Application>().getString(resId)
|
||||||
|
|
||||||
|
private fun getString(resId: Int, vararg formatArgs: Any): String =
|
||||||
|
getApplication<android.app.Application>().getString(resId, *formatArgs)
|
||||||
|
|
||||||
fun isServerConfigured(): Boolean {
|
fun isServerConfigured(): Boolean {
|
||||||
val serverUrl = prefs.getString(Constants.KEY_SERVER_URL, null)
|
val serverUrl = prefs.getString(Constants.KEY_SERVER_URL, null)
|
||||||
return !serverUrl.isNullOrEmpty() && serverUrl != "http://" && serverUrl != "https://"
|
return !serverUrl.isNullOrEmpty() && serverUrl != "http://" && serverUrl != "https://"
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete confirmation dialog with server/local options
|
* Delete confirmation dialog with server/local options
|
||||||
@@ -28,15 +30,15 @@ fun DeleteConfirmationDialog(
|
|||||||
onDeleteEverywhere: () -> Unit
|
onDeleteEverywhere: () -> Unit
|
||||||
) {
|
) {
|
||||||
val title = if (noteCount == 1) {
|
val title = if (noteCount == 1) {
|
||||||
"Notiz löschen?"
|
stringResource(R.string.delete_note_title)
|
||||||
} else {
|
} else {
|
||||||
"$noteCount Notizen löschen?"
|
stringResource(R.string.delete_notes_title, noteCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
val message = if (noteCount == 1) {
|
val message = if (noteCount == 1) {
|
||||||
"Wie möchtest du diese Notiz löschen?"
|
stringResource(R.string.delete_note_message)
|
||||||
} else {
|
} else {
|
||||||
"Wie möchtest du diese $noteCount Notizen löschen?"
|
stringResource(R.string.delete_notes_message, noteCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
@@ -66,7 +68,7 @@ fun DeleteConfirmationDialog(
|
|||||||
contentColor = MaterialTheme.colorScheme.error
|
contentColor = MaterialTheme.colorScheme.error
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Text("Überall löschen (auch Server)")
|
Text(stringResource(R.string.delete_everywhere))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete local only
|
// Delete local only
|
||||||
@@ -74,7 +76,7 @@ fun DeleteConfirmationDialog(
|
|||||||
onClick = onDeleteLocal,
|
onClick = onDeleteLocal,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text("Nur lokal löschen")
|
Text(stringResource(R.string.delete_local_only))
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -84,7 +86,7 @@ fun DeleteConfirmationDialog(
|
|||||||
onClick = onDismiss,
|
onClick = onDismiss,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Text("Abbrechen")
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,9 +14,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty state card shown when no notes exist
|
* Empty state card shown when no notes exist
|
||||||
@@ -52,7 +54,7 @@ fun EmptyState(
|
|||||||
|
|
||||||
// Title
|
// Title
|
||||||
Text(
|
Text(
|
||||||
text = "Noch keine Notizen",
|
text = stringResource(R.string.empty_state_title),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
@@ -62,7 +64,7 @@ fun EmptyState(
|
|||||||
|
|
||||||
// Message
|
// Message
|
||||||
Text(
|
Text(
|
||||||
text = "Tippe + um eine neue Notiz zu erstellen",
|
text = stringResource(R.string.empty_state_message),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
textAlign = TextAlign.Center
|
textAlign = TextAlign.Center
|
||||||
|
|||||||
@@ -39,8 +39,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.Note
|
import dev.dettmer.simplenotes.models.Note
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
import dev.dettmer.simplenotes.models.SyncStatus
|
import dev.dettmer.simplenotes.models.SyncStatus
|
||||||
@@ -66,6 +70,7 @@ fun NoteCard(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit
|
onLongClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val borderColor = if (isSelected) {
|
val borderColor = if (isSelected) {
|
||||||
MaterialTheme.colorScheme.primary
|
MaterialTheme.colorScheme.primary
|
||||||
} else {
|
} else {
|
||||||
@@ -137,7 +142,7 @@ fun NoteCard(
|
|||||||
|
|
||||||
// Title
|
// Title
|
||||||
Text(
|
Text(
|
||||||
text = note.title.ifEmpty { "Ohne Titel" },
|
text = note.title.ifEmpty { stringResource(R.string.untitled) },
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
@@ -154,7 +159,7 @@ fun NoteCard(
|
|||||||
NoteType.TEXT -> note.content.take(100)
|
NoteType.TEXT -> note.content.take(100)
|
||||||
NoteType.CHECKLIST -> {
|
NoteType.CHECKLIST -> {
|
||||||
val items = note.checklistItems ?: emptyList()
|
val items = note.checklistItems ?: emptyList()
|
||||||
"${items.count { it.isChecked }}/${items.size} erledigt"
|
stringResource(R.string.checklist_progress, items.count { it.isChecked }, items.size)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
@@ -171,7 +176,7 @@ fun NoteCard(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = note.updatedAt.toReadableTime(),
|
text = note.updatedAt.toReadableTime(context),
|
||||||
style = MaterialTheme.typography.labelSmall,
|
style = MaterialTheme.typography.labelSmall,
|
||||||
color = MaterialTheme.colorScheme.outline,
|
color = MaterialTheme.colorScheme.outline,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
@@ -231,7 +236,7 @@ fun NoteCard(
|
|||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Check,
|
imageVector = Icons.Default.Check,
|
||||||
contentDescription = "Ausgewählt",
|
contentDescription = stringResource(R.string.selection_count, 1),
|
||||||
tint = MaterialTheme.colorScheme.onPrimary,
|
tint = MaterialTheme.colorScheme.onPrimary,
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.models.NoteType
|
import dev.dettmer.simplenotes.models.NoteType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,7 +44,7 @@ fun NoteTypeFAB(
|
|||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Add,
|
imageVector = Icons.Default.Add,
|
||||||
contentDescription = "Neue Notiz"
|
contentDescription = stringResource(R.string.fab_new_note)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Dropdown inside FAB - renders as popup overlay
|
// Dropdown inside FAB - renders as popup overlay
|
||||||
@@ -51,7 +53,7 @@ fun NoteTypeFAB(
|
|||||||
onDismissRequest = { expanded = false }
|
onDismissRequest = { expanded = false }
|
||||||
) {
|
) {
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Text-Notiz") },
|
text = { Text(stringResource(R.string.fab_text_note)) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Outlined.Description,
|
imageVector = Icons.Outlined.Description,
|
||||||
@@ -65,7 +67,7 @@ fun NoteTypeFAB(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
text = { Text("Checkliste") },
|
text = { Text(stringResource(R.string.fab_checklist)) },
|
||||||
leadingIcon = {
|
leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Outlined.List,
|
imageVector = Icons.AutoMirrored.Outlined.List,
|
||||||
|
|||||||
@@ -16,7 +16,9 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.sync.SyncStateManager
|
import dev.dettmer.simplenotes.sync.SyncStateManager
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,10 +62,10 @@ fun SyncStatusBanner(
|
|||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = when (syncState) {
|
text = when (syncState) {
|
||||||
SyncStateManager.SyncState.SYNCING -> "Synchronisiere..."
|
SyncStateManager.SyncState.SYNCING -> stringResource(R.string.sync_status_syncing)
|
||||||
SyncStateManager.SyncState.SYNCING_SILENT -> "" // v1.5.0: Wird nicht angezeigt (isVisible = false)
|
SyncStateManager.SyncState.SYNCING_SILENT -> "" // v1.5.0: Wird nicht angezeigt (isVisible = false)
|
||||||
SyncStateManager.SyncState.COMPLETED -> message ?: "Synchronisiert"
|
SyncStateManager.SyncState.COMPLETED -> message ?: stringResource(R.string.sync_status_completed)
|
||||||
SyncStateManager.SyncState.ERROR -> message ?: "Fehler"
|
SyncStateManager.SyncState.ERROR -> message ?: stringResource(R.string.sync_status_error)
|
||||||
SyncStateManager.SyncState.IDLE -> ""
|
SyncStateManager.SyncState.IDLE -> ""
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
|||||||
@@ -7,16 +7,17 @@ import android.os.Bundle
|
|||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
import androidx.activity.OnBackPressedCallback
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.SimpleNotesApplication
|
import dev.dettmer.simplenotes.SimpleNotesApplication
|
||||||
import dev.dettmer.simplenotes.ui.theme.SimpleNotesTheme
|
import dev.dettmer.simplenotes.ui.theme.SimpleNotesTheme
|
||||||
import dev.dettmer.simplenotes.utils.Logger
|
import dev.dettmer.simplenotes.utils.Logger
|
||||||
@@ -34,7 +35,7 @@ import kotlinx.coroutines.launch
|
|||||||
* - Navigation with back button in each screen
|
* - Navigation with back button in each screen
|
||||||
* - Clean separation of concerns with SettingsViewModel
|
* - Clean separation of concerns with SettingsViewModel
|
||||||
*/
|
*/
|
||||||
class ComposeSettingsActivity : ComponentActivity() {
|
class ComposeSettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ComposeSettingsActivity"
|
private const val TAG = "ComposeSettingsActivity"
|
||||||
@@ -133,16 +134,12 @@ class ComposeSettingsActivity : ComponentActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun showBatteryOptimizationDialog() {
|
private fun showBatteryOptimizationDialog() {
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this)
|
||||||
.setTitle("Hintergrund-Synchronisation")
|
.setTitle(getString(R.string.battery_optimization_dialog_title))
|
||||||
.setMessage(
|
.setMessage(getString(R.string.battery_optimization_dialog_full_message))
|
||||||
"Damit die App im Hintergrund synchronisieren kann, " +
|
.setPositiveButton(getString(R.string.battery_optimization_open_settings)) { _, _ ->
|
||||||
"muss die Akku-Optimierung deaktiviert werden.\n\n" +
|
|
||||||
"Bitte wähle 'Nicht optimieren' für Simple Notes."
|
|
||||||
)
|
|
||||||
.setPositiveButton("Einstellungen öffnen") { _, _ ->
|
|
||||||
openBatteryOptimizationSettings()
|
openBatteryOptimizationSettings()
|
||||||
}
|
}
|
||||||
.setNegativeButton("Später") { dialog, _ ->
|
.setNegativeButton(getString(R.string.battery_optimization_later)) { dialog, _ ->
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.navigation.compose.composable
|
|||||||
import dev.dettmer.simplenotes.ui.settings.screens.AboutScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.AboutScreen
|
||||||
import dev.dettmer.simplenotes.ui.settings.screens.BackupSettingsScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.BackupSettingsScreen
|
||||||
import dev.dettmer.simplenotes.ui.settings.screens.DebugSettingsScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.DebugSettingsScreen
|
||||||
|
import dev.dettmer.simplenotes.ui.settings.screens.LanguageSettingsScreen
|
||||||
import dev.dettmer.simplenotes.ui.settings.screens.MarkdownSettingsScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.MarkdownSettingsScreen
|
||||||
import dev.dettmer.simplenotes.ui.settings.screens.ServerSettingsScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.ServerSettingsScreen
|
||||||
import dev.dettmer.simplenotes.ui.settings.screens.SettingsMainScreen
|
import dev.dettmer.simplenotes.ui.settings.screens.SettingsMainScreen
|
||||||
@@ -35,6 +36,13 @@ fun SettingsNavHost(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Language Settings
|
||||||
|
composable(SettingsRoute.Language.route) {
|
||||||
|
LanguageSettingsScreen(
|
||||||
|
onBack = { navController.popBackStack() }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Server Settings
|
// Server Settings
|
||||||
composable(SettingsRoute.Server.route) {
|
composable(SettingsRoute.Server.route) {
|
||||||
ServerSettingsScreen(
|
ServerSettingsScreen(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ package dev.dettmer.simplenotes.ui.settings
|
|||||||
*/
|
*/
|
||||||
sealed class SettingsRoute(val route: String) {
|
sealed class SettingsRoute(val route: String) {
|
||||||
data object Main : SettingsRoute("settings_main")
|
data object Main : SettingsRoute("settings_main")
|
||||||
|
data object Language : SettingsRoute("settings_language")
|
||||||
data object Server : SettingsRoute("settings_server")
|
data object Server : SettingsRoute("settings_server")
|
||||||
data object Sync : SettingsRoute("settings_sync")
|
data object Sync : SettingsRoute("settings_sync")
|
||||||
data object Markdown : SettingsRoute("settings_markdown")
|
data object Markdown : SettingsRoute("settings_markdown")
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import android.net.Uri
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.AndroidViewModel
|
import androidx.lifecycle.AndroidViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.backup.BackupManager
|
import dev.dettmer.simplenotes.backup.BackupManager
|
||||||
import dev.dettmer.simplenotes.backup.RestoreMode
|
import dev.dettmer.simplenotes.backup.RestoreMode
|
||||||
import dev.dettmer.simplenotes.sync.WebDavSyncService
|
import dev.dettmer.simplenotes.sync.WebDavSyncService
|
||||||
@@ -184,10 +185,10 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
} else {
|
} else {
|
||||||
ServerStatus.Unreachable(result.errorMessage)
|
ServerStatus.Unreachable(result.errorMessage)
|
||||||
}
|
}
|
||||||
emitToast(if (result.isSuccess) "✅ Verbindung erfolgreich!" else "❌ ${result.errorMessage}")
|
emitToast(if (result.isSuccess) getString(R.string.toast_connection_success) else getString(R.string.toast_connection_failed, result.errorMessage ?: ""))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_serverStatus.value = ServerStatus.Unreachable(e.message)
|
_serverStatus.value = ServerStatus.Unreachable(e.message)
|
||||||
emitToast("❌ Fehler: ${e.message}")
|
emitToast(getString(R.string.toast_error, e.message ?: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -225,22 +226,22 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isSyncing.value = true
|
_isSyncing.value = true
|
||||||
try {
|
try {
|
||||||
emitToast("🔄 Synchronisiere...")
|
emitToast(getString(R.string.toast_syncing))
|
||||||
val syncService = WebDavSyncService(getApplication())
|
val syncService = WebDavSyncService(getApplication())
|
||||||
|
|
||||||
if (!syncService.hasUnsyncedChanges()) {
|
if (!syncService.hasUnsyncedChanges()) {
|
||||||
emitToast("✅ Bereits synchronisiert")
|
emitToast(getString(R.string.toast_already_synced))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
val result = syncService.syncNotes()
|
val result = syncService.syncNotes()
|
||||||
if (result.isSuccess) {
|
if (result.isSuccess) {
|
||||||
emitToast("✅ ${result.syncedCount} Notizen synchronisiert")
|
emitToast(getString(R.string.toast_sync_success, result.syncedCount))
|
||||||
} else {
|
} else {
|
||||||
emitToast("❌ ${result.errorMessage}")
|
emitToast(getString(R.string.toast_sync_failed, result.errorMessage ?: ""))
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Fehler: ${e.message}")
|
emitToast(getString(R.string.toast_error, e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
_isSyncing.value = false
|
_isSyncing.value = false
|
||||||
}
|
}
|
||||||
@@ -260,10 +261,10 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
// v1.5.0 Fix: Trigger battery optimization check and network monitor restart
|
// v1.5.0 Fix: Trigger battery optimization check and network monitor restart
|
||||||
_events.emit(SettingsEvent.RequestBatteryOptimization)
|
_events.emit(SettingsEvent.RequestBatteryOptimization)
|
||||||
_events.emit(SettingsEvent.RestartNetworkMonitor)
|
_events.emit(SettingsEvent.RestartNetworkMonitor)
|
||||||
emitToast("✅ Auto-Sync aktiviert")
|
emitToast(getString(R.string.toast_auto_sync_enabled))
|
||||||
} else {
|
} else {
|
||||||
_events.emit(SettingsEvent.RestartNetworkMonitor)
|
_events.emit(SettingsEvent.RestartNetworkMonitor)
|
||||||
emitToast("Auto-Sync deaktiviert")
|
emitToast(getString(R.string.toast_auto_sync_disabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -273,11 +274,11 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
prefs.edit().putLong(Constants.PREF_SYNC_INTERVAL_MINUTES, minutes).apply()
|
prefs.edit().putLong(Constants.PREF_SYNC_INTERVAL_MINUTES, minutes).apply()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val text = when (minutes) {
|
val text = when (minutes) {
|
||||||
15L -> "15 Minuten"
|
15L -> getString(R.string.toast_sync_interval_15min)
|
||||||
60L -> "60 Minuten"
|
60L -> getString(R.string.toast_sync_interval_60min)
|
||||||
else -> "30 Minuten"
|
else -> getString(R.string.toast_sync_interval_30min)
|
||||||
}
|
}
|
||||||
emitToast("⏱️ Sync-Intervall: $text")
|
emitToast(getString(R.string.toast_sync_interval, text))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,7 +297,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
val password = prefs.getString(Constants.KEY_PASSWORD, "") ?: ""
|
val password = prefs.getString(Constants.KEY_PASSWORD, "") ?: ""
|
||||||
|
|
||||||
if (serverUrl.isBlank() || username.isBlank() || password.isBlank()) {
|
if (serverUrl.isBlank() || username.isBlank() || password.isBlank()) {
|
||||||
emitToast("⚠️ Bitte zuerst WebDAV-Server konfigurieren")
|
emitToast(getString(R.string.toast_configure_server_first))
|
||||||
// Don't enable - revert state
|
// Don't enable - revert state
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
@@ -329,7 +330,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
.apply()
|
.apply()
|
||||||
|
|
||||||
_markdownExportProgress.value = MarkdownExportProgress(noteCount, noteCount, isComplete = true)
|
_markdownExportProgress.value = MarkdownExportProgress(noteCount, noteCount, isComplete = true)
|
||||||
emitToast("✅ $exportedCount Notizen nach Markdown exportiert")
|
emitToast(getString(R.string.toast_markdown_exported, exportedCount))
|
||||||
|
|
||||||
// Clear progress after short delay
|
// Clear progress after short delay
|
||||||
kotlinx.coroutines.delay(500)
|
kotlinx.coroutines.delay(500)
|
||||||
@@ -342,12 +343,12 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
.putBoolean(Constants.KEY_MARKDOWN_EXPORT, true)
|
.putBoolean(Constants.KEY_MARKDOWN_EXPORT, true)
|
||||||
.putBoolean(Constants.KEY_MARKDOWN_AUTO_IMPORT, true)
|
.putBoolean(Constants.KEY_MARKDOWN_AUTO_IMPORT, true)
|
||||||
.apply()
|
.apply()
|
||||||
emitToast("📝 Markdown Auto-Sync aktiviert")
|
emitToast(getString(R.string.toast_markdown_enabled))
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
_markdownExportProgress.value = null
|
_markdownExportProgress.value = null
|
||||||
emitToast("❌ Export fehlgeschlagen: ${e.message}")
|
emitToast(getString(R.string.toast_export_failed, e.message ?: ""))
|
||||||
// Don't enable on error
|
// Don't enable on error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -359,7 +360,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
.putBoolean(Constants.KEY_MARKDOWN_AUTO_IMPORT, false)
|
.putBoolean(Constants.KEY_MARKDOWN_AUTO_IMPORT, false)
|
||||||
.apply()
|
.apply()
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
emitToast("📝 Markdown Auto-Sync deaktiviert")
|
emitToast(getString(R.string.toast_markdown_disabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -367,12 +368,12 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
fun performManualMarkdownSync() {
|
fun performManualMarkdownSync() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
emitToast("📝 Markdown-Sync läuft...")
|
emitToast(getString(R.string.toast_markdown_syncing))
|
||||||
val syncService = WebDavSyncService(getApplication())
|
val syncService = WebDavSyncService(getApplication())
|
||||||
val result = syncService.manualMarkdownSync()
|
val result = syncService.manualMarkdownSync()
|
||||||
emitToast("✅ Export: ${result.exportedCount} • Import: ${result.importedCount}")
|
emitToast(getString(R.string.toast_markdown_result, result.exportedCount, result.importedCount))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Fehler: ${e.message}")
|
emitToast(getString(R.string.toast_error, e.message ?: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,9 +387,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
_isBackupInProgress.value = true
|
_isBackupInProgress.value = true
|
||||||
try {
|
try {
|
||||||
val result = backupManager.createBackup(uri)
|
val result = backupManager.createBackup(uri)
|
||||||
emitToast(if (result.success) "✅ ${result.message}" else "❌ ${result.error}")
|
emitToast(if (result.success) getString(R.string.toast_backup_success, result.message ?: "") else getString(R.string.toast_backup_failed, result.error ?: ""))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Backup fehlgeschlagen: ${e.message}")
|
emitToast(getString(R.string.toast_backup_failed, e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
_isBackupInProgress.value = false
|
_isBackupInProgress.value = false
|
||||||
}
|
}
|
||||||
@@ -400,9 +401,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
_isBackupInProgress.value = true
|
_isBackupInProgress.value = true
|
||||||
try {
|
try {
|
||||||
val result = backupManager.restoreBackup(uri, mode)
|
val result = backupManager.restoreBackup(uri, mode)
|
||||||
emitToast(if (result.success) "✅ ${result.importedNotes} Notizen wiederhergestellt" else "❌ ${result.error}")
|
emitToast(if (result.success) getString(R.string.toast_restore_success, result.importedNotes) else getString(R.string.toast_restore_failed, result.error ?: ""))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Wiederherstellung fehlgeschlagen: ${e.message}")
|
emitToast(getString(R.string.toast_restore_failed, e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
_isBackupInProgress.value = false
|
_isBackupInProgress.value = false
|
||||||
}
|
}
|
||||||
@@ -413,14 +414,14 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isBackupInProgress.value = true
|
_isBackupInProgress.value = true
|
||||||
try {
|
try {
|
||||||
emitToast("📥 Lade vom Server...")
|
emitToast(getString(R.string.restore_progress))
|
||||||
val syncService = WebDavSyncService(getApplication())
|
val syncService = WebDavSyncService(getApplication())
|
||||||
val result = withContext(Dispatchers.IO) {
|
val result = withContext(Dispatchers.IO) {
|
||||||
syncService.restoreFromServer(mode)
|
syncService.restoreFromServer(mode)
|
||||||
}
|
}
|
||||||
emitToast(if (result.isSuccess) "✅ ${result.restoredCount} Notizen wiederhergestellt" else "❌ ${result.errorMessage}")
|
emitToast(if (result.isSuccess) getString(R.string.toast_restore_success, result.restoredCount) else getString(R.string.toast_restore_failed, result.errorMessage ?: ""))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Fehler: ${e.message}")
|
emitToast(getString(R.string.toast_error, e.message ?: ""))
|
||||||
} finally {
|
} finally {
|
||||||
_isBackupInProgress.value = false
|
_isBackupInProgress.value = false
|
||||||
}
|
}
|
||||||
@@ -436,7 +437,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
prefs.edit().putBoolean(Constants.KEY_FILE_LOGGING_ENABLED, enabled).apply()
|
prefs.edit().putBoolean(Constants.KEY_FILE_LOGGING_ENABLED, enabled).apply()
|
||||||
Logger.setFileLoggingEnabled(enabled)
|
Logger.setFileLoggingEnabled(enabled)
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
emitToast(if (enabled) "📝 Datei-Logging aktiviert" else "📝 Datei-Logging deaktiviert")
|
emitToast(if (enabled) getString(R.string.toast_file_logging_enabled) else getString(R.string.toast_file_logging_disabled))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,9 +445,9 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
val cleared = Logger.clearLogFile(getApplication())
|
val cleared = Logger.clearLogFile(getApplication())
|
||||||
emitToast(if (cleared) "🗑️ Logs gelöscht" else "📭 Keine Logs zum Löschen")
|
emitToast(if (cleared) getString(R.string.toast_logs_deleted) else getString(R.string.toast_logs_deleted))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emitToast("❌ Fehler: ${e.message}")
|
emitToast(getString(R.string.toast_error, e.message ?: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -457,6 +458,11 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
|
|||||||
// Helper
|
// Helper
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
private fun getString(resId: Int): String = getApplication<android.app.Application>().getString(resId)
|
||||||
|
|
||||||
|
private fun getString(resId: Int, vararg formatArgs: Any): String =
|
||||||
|
getApplication<android.app.Application>().getString(resId, *formatArgs)
|
||||||
|
|
||||||
private suspend fun emitToast(message: String) {
|
private suspend fun emitToast(message: String) {
|
||||||
_showToast.emit(message)
|
_showToast.emit(message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import androidx.compose.material3.TopAppBar
|
|||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reusable Scaffold with back-navigation TopAppBar
|
* Reusable Scaffold with back-navigation TopAppBar
|
||||||
@@ -40,7 +42,7 @@ fun SettingsScaffold(
|
|||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
contentDescription = "Zurück"
|
contentDescription = stringResource(R.string.content_description_back)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,9 +37,11 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.graphics.asImageBitmap
|
import androidx.compose.ui.graphics.asImageBitmap
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import dev.dettmer.simplenotes.BuildConfig
|
import dev.dettmer.simplenotes.BuildConfig
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsScaffold
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsScaffold
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsSectionHeader
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsSectionHeader
|
||||||
@@ -59,7 +61,7 @@ fun AboutScreen(
|
|||||||
val licenseUrl = "https://github.com/inventory69/simple-notes-sync/blob/main/LICENSE"
|
val licenseUrl = "https://github.com/inventory69/simple-notes-sync/blob/main/LICENSE"
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Über diese App",
|
title = stringResource(R.string.about_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -110,7 +112,7 @@ fun AboutScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Simple Notes Sync",
|
text = stringResource(R.string.about_app_name),
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer
|
color = MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
)
|
)
|
||||||
@@ -118,7 +120,7 @@ fun AboutScreen(
|
|||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Version ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})",
|
text = stringResource(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE),
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f)
|
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f)
|
||||||
)
|
)
|
||||||
@@ -127,13 +129,13 @@ fun AboutScreen(
|
|||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
SettingsSectionHeader(text = "Links")
|
SettingsSectionHeader(text = stringResource(R.string.about_links_section))
|
||||||
|
|
||||||
// GitHub Repository
|
// GitHub Repository
|
||||||
AboutLinkItem(
|
AboutLinkItem(
|
||||||
icon = Icons.Default.Code,
|
icon = Icons.Default.Code,
|
||||||
title = "GitHub Repository",
|
title = stringResource(R.string.about_github_title),
|
||||||
subtitle = "Quellcode, Issues & Dokumentation",
|
subtitle = stringResource(R.string.about_github_subtitle),
|
||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubRepoUrl))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubRepoUrl))
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
@@ -143,8 +145,8 @@ fun AboutScreen(
|
|||||||
// Developer
|
// Developer
|
||||||
AboutLinkItem(
|
AboutLinkItem(
|
||||||
icon = Icons.Default.Person,
|
icon = Icons.Default.Person,
|
||||||
title = "Entwickler",
|
title = stringResource(R.string.about_developer_title),
|
||||||
subtitle = "GitHub Profil: @inventory69",
|
subtitle = stringResource(R.string.about_developer_subtitle),
|
||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubProfileUrl))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(githubProfileUrl))
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
@@ -154,8 +156,8 @@ fun AboutScreen(
|
|||||||
// License
|
// License
|
||||||
AboutLinkItem(
|
AboutLinkItem(
|
||||||
icon = Icons.Default.Policy,
|
icon = Icons.Default.Policy,
|
||||||
title = "Lizenz",
|
title = stringResource(R.string.about_license_title),
|
||||||
subtitle = "MIT License - Open Source",
|
subtitle = stringResource(R.string.about_license_subtitle),
|
||||||
onClick = {
|
onClick = {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(licenseUrl))
|
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(licenseUrl))
|
||||||
context.startActivity(intent)
|
context.startActivity(intent)
|
||||||
@@ -177,14 +179,12 @@ fun AboutScreen(
|
|||||||
modifier = Modifier.padding(16.dp)
|
modifier = Modifier.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "🔒 Datenschutz",
|
text = stringResource(R.string.about_privacy_title),
|
||||||
style = MaterialTheme.typography.titleSmall
|
style = MaterialTheme.typography.titleSmall
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "Diese App sammelt keine Daten. Alle Notizen werden " +
|
text = stringResource(R.string.about_privacy_text),
|
||||||
"nur lokal auf deinem Gerät und auf deinem eigenen " +
|
|
||||||
"WebDAV-Server gespeichert. Keine Telemetrie, keine Werbung.",
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.backup.RestoreMode
|
import dev.dettmer.simplenotes.backup.RestoreMode
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.RadioOption
|
import dev.dettmer.simplenotes.ui.settings.components.RadioOption
|
||||||
@@ -71,7 +73,7 @@ fun BackupSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Backup & Wiederherstellung",
|
title = stringResource(R.string.backup_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -84,19 +86,18 @@ fun BackupSettingsScreen(
|
|||||||
|
|
||||||
// Info Card
|
// Info Card
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "📦 Bei jeder Wiederherstellung wird automatisch ein " +
|
text = stringResource(R.string.backup_auto_info)
|
||||||
"Sicherheits-Backup erstellt."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
// Local Backup Section
|
// Local Backup Section
|
||||||
SettingsSectionHeader(text = "Lokales Backup")
|
SettingsSectionHeader(text = stringResource(R.string.backup_local_section))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingsButton(
|
SettingsButton(
|
||||||
text = "💾 Backup erstellen",
|
text = stringResource(R.string.backup_create),
|
||||||
onClick = {
|
onClick = {
|
||||||
val timestamp = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US)
|
val timestamp = SimpleDateFormat("yyyy-MM-dd_HHmmss", Locale.US)
|
||||||
.format(Date())
|
.format(Date())
|
||||||
@@ -110,7 +111,7 @@ fun BackupSettingsScreen(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingsOutlinedButton(
|
SettingsOutlinedButton(
|
||||||
text = "📂 Aus Datei wiederherstellen",
|
text = stringResource(R.string.backup_restore_file),
|
||||||
onClick = {
|
onClick = {
|
||||||
restoreFileLauncher.launch(arrayOf("application/json"))
|
restoreFileLauncher.launch(arrayOf("application/json"))
|
||||||
},
|
},
|
||||||
@@ -121,12 +122,12 @@ fun BackupSettingsScreen(
|
|||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
|
||||||
// Server Backup Section
|
// Server Backup Section
|
||||||
SettingsSectionHeader(text = "Server-Backup")
|
SettingsSectionHeader(text = stringResource(R.string.backup_server_section))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingsOutlinedButton(
|
SettingsOutlinedButton(
|
||||||
text = "☁️ Vom Server wiederherstellen",
|
text = stringResource(R.string.backup_restore_server),
|
||||||
onClick = {
|
onClick = {
|
||||||
restoreSource = RestoreSource.Server
|
restoreSource = RestoreSource.Server
|
||||||
showRestoreDialog = true
|
showRestoreDialog = true
|
||||||
@@ -186,42 +187,42 @@ private fun RestoreModeDialog(
|
|||||||
onDismiss: () -> Unit
|
onDismiss: () -> Unit
|
||||||
) {
|
) {
|
||||||
val sourceText = when (source) {
|
val sourceText = when (source) {
|
||||||
RestoreSource.LocalFile -> "Lokale Datei"
|
RestoreSource.LocalFile -> stringResource(R.string.backup_restore_source_file)
|
||||||
RestoreSource.Server -> "WebDAV Server"
|
RestoreSource.Server -> stringResource(R.string.backup_restore_source_server)
|
||||||
}
|
}
|
||||||
|
|
||||||
val modeOptions = listOf(
|
val modeOptions = listOf(
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = RestoreMode.MERGE,
|
value = RestoreMode.MERGE,
|
||||||
title = "⚪ Zusammenführen (Standard)",
|
title = stringResource(R.string.backup_mode_merge_title),
|
||||||
subtitle = "Neue hinzufügen, Bestehende behalten"
|
subtitle = stringResource(R.string.backup_mode_merge_subtitle)
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = RestoreMode.REPLACE,
|
value = RestoreMode.REPLACE,
|
||||||
title = "⚪ Ersetzen",
|
title = stringResource(R.string.backup_mode_replace_title),
|
||||||
subtitle = "Alle löschen & Backup importieren"
|
subtitle = stringResource(R.string.backup_mode_replace_subtitle)
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = RestoreMode.OVERWRITE_DUPLICATES,
|
value = RestoreMode.OVERWRITE_DUPLICATES,
|
||||||
title = "⚪ Duplikate überschreiben",
|
title = stringResource(R.string.backup_mode_overwrite_title),
|
||||||
subtitle = "Backup gewinnt bei Konflikten"
|
subtitle = stringResource(R.string.backup_mode_overwrite_subtitle)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
title = { Text("⚠️ Backup wiederherstellen?") },
|
title = { Text(stringResource(R.string.backup_restore_dialog_title)) },
|
||||||
text = {
|
text = {
|
||||||
Column {
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = "Quelle: $sourceText",
|
text = stringResource(R.string.backup_restore_source, sourceText),
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Wiederherstellungs-Modus:",
|
text = stringResource(R.string.backup_restore_mode_label),
|
||||||
style = MaterialTheme.typography.labelLarge
|
style = MaterialTheme.typography.labelLarge
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -236,7 +237,7 @@ private fun RestoreModeDialog(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "ℹ️ Ein Sicherheits-Backup wird vor dem Wiederherstellen automatisch erstellt.",
|
text = stringResource(R.string.backup_restore_info),
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
)
|
)
|
||||||
@@ -244,12 +245,12 @@ private fun RestoreModeDialog(
|
|||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(onClick = onConfirm) {
|
TextButton(onClick = onConfirm) {
|
||||||
Text("Wiederherstellen")
|
Text(stringResource(R.string.backup_restore_button))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = onDismiss) {
|
TextButton(onClick = onDismiss) {
|
||||||
Text("Abbrechen")
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -21,9 +21,11 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.FileProvider
|
import androidx.core.content.FileProvider
|
||||||
import dev.dettmer.simplenotes.BuildConfig
|
import dev.dettmer.simplenotes.BuildConfig
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsButton
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsButton
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsDangerButton
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsDangerButton
|
||||||
@@ -48,7 +50,7 @@ fun DebugSettingsScreen(
|
|||||||
var showClearLogsDialog by remember { mutableStateOf(false) }
|
var showClearLogsDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Debug & Diagnose",
|
title = stringResource(R.string.debug_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -61,8 +63,8 @@ fun DebugSettingsScreen(
|
|||||||
|
|
||||||
// File Logging Toggle
|
// File Logging Toggle
|
||||||
SettingsSwitch(
|
SettingsSwitch(
|
||||||
title = "Datei-Logging",
|
title = stringResource(R.string.debug_file_logging_title),
|
||||||
subtitle = "Sync-Logs in Datei speichern",
|
subtitle = stringResource(R.string.debug_file_logging_subtitle),
|
||||||
checked = fileLoggingEnabled,
|
checked = fileLoggingEnabled,
|
||||||
onCheckedChange = { viewModel.setFileLogging(it) },
|
onCheckedChange = { viewModel.setFileLogging(it) },
|
||||||
icon = Icons.AutoMirrored.Filled.Notes
|
icon = Icons.AutoMirrored.Filled.Notes
|
||||||
@@ -70,21 +72,18 @@ fun DebugSettingsScreen(
|
|||||||
|
|
||||||
// Privacy Info
|
// Privacy Info
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "🔒 Datenschutz: Logs werden nur lokal auf deinem Gerät gespeichert " +
|
text = stringResource(R.string.debug_privacy_info)
|
||||||
"und niemals an externe Server gesendet. Die Logs enthalten " +
|
|
||||||
"Sync-Aktivitäten zur Fehlerdiagnose. Du kannst sie jederzeit löschen " +
|
|
||||||
"oder exportieren."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
|
||||||
SettingsSectionHeader(text = "Log-Aktionen")
|
SettingsSectionHeader(text = stringResource(R.string.debug_log_actions_section))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// Export Logs Button
|
// Export Logs Button
|
||||||
SettingsButton(
|
SettingsButton(
|
||||||
text = "📤 Logs exportieren & teilen",
|
text = stringResource(R.string.debug_export_logs),
|
||||||
onClick = {
|
onClick = {
|
||||||
val logFile = viewModel.getLogFile()
|
val logFile = viewModel.getLogFile()
|
||||||
if (logFile != null && logFile.exists() && logFile.length() > 0L) {
|
if (logFile != null && logFile.exists() && logFile.length() > 0L) {
|
||||||
@@ -97,11 +96,11 @@ fun DebugSettingsScreen(
|
|||||||
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
putExtra(Intent.EXTRA_STREAM, logUri)
|
putExtra(Intent.EXTRA_STREAM, logUri)
|
||||||
putExtra(Intent.EXTRA_SUBJECT, "SimpleNotes Sync Logs")
|
putExtra(Intent.EXTRA_SUBJECT, context.getString(R.string.debug_logs_subject))
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}
|
}
|
||||||
|
|
||||||
context.startActivity(Intent.createChooser(shareIntent, "Logs teilen via..."))
|
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.debug_logs_share_via)))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
@@ -111,7 +110,7 @@ fun DebugSettingsScreen(
|
|||||||
|
|
||||||
// Clear Logs Button
|
// Clear Logs Button
|
||||||
SettingsDangerButton(
|
SettingsDangerButton(
|
||||||
text = "🗑️ Logs löschen",
|
text = stringResource(R.string.debug_delete_logs),
|
||||||
onClick = { showClearLogsDialog = true },
|
onClick = { showClearLogsDialog = true },
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
)
|
)
|
||||||
@@ -124,9 +123,9 @@ fun DebugSettingsScreen(
|
|||||||
if (showClearLogsDialog) {
|
if (showClearLogsDialog) {
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { showClearLogsDialog = false },
|
onDismissRequest = { showClearLogsDialog = false },
|
||||||
title = { Text("Logs löschen?") },
|
title = { Text(stringResource(R.string.debug_delete_logs_title)) },
|
||||||
text = {
|
text = {
|
||||||
Text("Alle gespeicherten Sync-Logs werden unwiderruflich gelöscht.")
|
Text(stringResource(R.string.debug_delete_logs_message))
|
||||||
},
|
},
|
||||||
confirmButton = {
|
confirmButton = {
|
||||||
TextButton(
|
TextButton(
|
||||||
@@ -135,12 +134,12 @@ fun DebugSettingsScreen(
|
|||||||
viewModel.clearLogs()
|
viewModel.clearLogs()
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text("Löschen")
|
Text(stringResource(R.string.delete))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dismissButton = {
|
dismissButton = {
|
||||||
TextButton(onClick = { showClearLogsDialog = false }) {
|
TextButton(onClick = { showClearLogsDialog = false }) {
|
||||||
Text("Abbrechen")
|
Text(stringResource(R.string.cancel))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
package dev.dettmer.simplenotes.ui.settings.screens
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
|
import dev.dettmer.simplenotes.ui.settings.components.RadioOption
|
||||||
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsInfoCard
|
||||||
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsRadioGroup
|
||||||
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsScaffold
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Language selection settings screen
|
||||||
|
* v1.5.0: Internationalization feature
|
||||||
|
*
|
||||||
|
* Uses Android's Per-App Language API (Android 13+) with AppCompat fallback
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
fun LanguageSettingsScreen(
|
||||||
|
onBack: () -> Unit
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// Get current app locale - fresh value each time (no remember, always reads current state)
|
||||||
|
val currentLocale = AppCompatDelegate.getApplicationLocales()
|
||||||
|
val currentLanguageCode = if (currentLocale.isEmpty) {
|
||||||
|
"" // System default
|
||||||
|
} else {
|
||||||
|
currentLocale.get(0)?.language ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedLanguage by remember(currentLanguageCode) { mutableStateOf(currentLanguageCode) }
|
||||||
|
|
||||||
|
// Language options
|
||||||
|
val languageOptions = listOf(
|
||||||
|
RadioOption(
|
||||||
|
value = "",
|
||||||
|
title = stringResource(R.string.language_system_default),
|
||||||
|
subtitle = null
|
||||||
|
),
|
||||||
|
RadioOption(
|
||||||
|
value = "en",
|
||||||
|
title = stringResource(R.string.language_english),
|
||||||
|
subtitle = "English"
|
||||||
|
),
|
||||||
|
RadioOption(
|
||||||
|
value = "de",
|
||||||
|
title = stringResource(R.string.language_german),
|
||||||
|
subtitle = "German"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
SettingsScaffold(
|
||||||
|
title = stringResource(R.string.language_settings_title),
|
||||||
|
onBack = onBack
|
||||||
|
) { paddingValues ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(paddingValues)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Info card
|
||||||
|
SettingsInfoCard(
|
||||||
|
text = stringResource(R.string.language_info)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// Language selection radio group
|
||||||
|
SettingsRadioGroup(
|
||||||
|
options = languageOptions,
|
||||||
|
selectedValue = selectedLanguage,
|
||||||
|
onValueSelected = { newLanguage ->
|
||||||
|
if (newLanguage != selectedLanguage) {
|
||||||
|
selectedLanguage = newLanguage
|
||||||
|
setAppLanguage(newLanguage, context as Activity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set app language using AppCompatDelegate
|
||||||
|
* Works on Android 13+ natively, falls back to AppCompat on older versions
|
||||||
|
*/
|
||||||
|
private fun setAppLanguage(languageCode: String, activity: Activity) {
|
||||||
|
val localeList = if (languageCode.isEmpty()) {
|
||||||
|
LocaleListCompat.getEmptyLocaleList()
|
||||||
|
} else {
|
||||||
|
LocaleListCompat.forLanguageTags(languageCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
AppCompatDelegate.setApplicationLocales(localeList)
|
||||||
|
|
||||||
|
// Restart the activity to apply the change
|
||||||
|
// On Android 13+ the system handles this automatically for some apps,
|
||||||
|
// but we need to recreate to ensure our Compose UI recomposes with new locale
|
||||||
|
activity.recreate()
|
||||||
|
}
|
||||||
@@ -20,7 +20,9 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsButton
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsButton
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
||||||
@@ -44,7 +46,7 @@ fun MarkdownSettingsScreen(
|
|||||||
exportProgress?.let { progress ->
|
exportProgress?.let { progress ->
|
||||||
AlertDialog(
|
AlertDialog(
|
||||||
onDismissRequest = { /* Not dismissable */ },
|
onDismissRequest = { /* Not dismissable */ },
|
||||||
title = { Text("Markdown Auto-Sync") },
|
title = { Text(stringResource(R.string.markdown_dialog_title)) },
|
||||||
text = {
|
text = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -53,9 +55,9 @@ fun MarkdownSettingsScreen(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (progress.isComplete) {
|
text = if (progress.isComplete) {
|
||||||
"✅ Export abgeschlossen"
|
stringResource(R.string.markdown_export_complete)
|
||||||
} else {
|
} else {
|
||||||
"Exportiere ${progress.current}/${progress.total} Notizen..."
|
stringResource(R.string.markdown_export_progress, progress.current, progress.total)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
)
|
||||||
@@ -75,7 +77,7 @@ fun MarkdownSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Markdown Desktop-Integration",
|
title = stringResource(R.string.markdown_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -88,17 +90,15 @@ fun MarkdownSettingsScreen(
|
|||||||
|
|
||||||
// Info Card
|
// Info Card
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "📝 Exportiert Notizen zusätzlich als .md-Dateien. Mounte " +
|
text = stringResource(R.string.markdown_info)
|
||||||
"WebDAV als Netzlaufwerk um mit VS Code, Typora oder jedem " +
|
|
||||||
"Markdown-Editor zu bearbeiten. JSON-Sync bleibt primäres Format."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// Markdown Auto-Sync Toggle
|
// Markdown Auto-Sync Toggle
|
||||||
SettingsSwitch(
|
SettingsSwitch(
|
||||||
title = "Markdown Auto-Sync",
|
title = stringResource(R.string.markdown_auto_sync_title),
|
||||||
subtitle = "Synchronisiert Notizen automatisch als .md-Dateien (Upload + Download bei jedem Sync)",
|
subtitle = stringResource(R.string.markdown_auto_sync_subtitle),
|
||||||
checked = markdownAutoSync,
|
checked = markdownAutoSync,
|
||||||
onCheckedChange = { viewModel.setMarkdownAutoSync(it) },
|
onCheckedChange = { viewModel.setMarkdownAutoSync(it) },
|
||||||
icon = Icons.Default.Description
|
icon = Icons.Default.Description
|
||||||
@@ -109,14 +109,13 @@ fun MarkdownSettingsScreen(
|
|||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "Manueller Sync exportiert alle Notizen als .md-Dateien und " +
|
text = stringResource(R.string.markdown_manual_sync_info)
|
||||||
"importiert .md-Dateien vom Server. Nützlich für einmalige Synchronisation."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
SettingsButton(
|
SettingsButton(
|
||||||
text = "📝 Manueller Markdown-Sync",
|
text = stringResource(R.string.markdown_manual_sync_button),
|
||||||
onClick = { viewModel.performManualMarkdownSync() },
|
onClick = { viewModel.performManualMarkdownSync() },
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier.padding(horizontal = 16.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -41,9 +41,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsScaffold
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsScaffold
|
||||||
|
|
||||||
@@ -71,7 +73,7 @@ fun ServerSettingsScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Server-Einstellungen",
|
title = stringResource(R.string.server_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -83,7 +85,7 @@ fun ServerSettingsScreen(
|
|||||||
) {
|
) {
|
||||||
// Verbindungstyp
|
// Verbindungstyp
|
||||||
Text(
|
Text(
|
||||||
text = "Verbindungstyp",
|
text = stringResource(R.string.server_connection_type),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
)
|
)
|
||||||
@@ -95,22 +97,22 @@ fun ServerSettingsScreen(
|
|||||||
FilterChip(
|
FilterChip(
|
||||||
selected = !isHttps,
|
selected = !isHttps,
|
||||||
onClick = { viewModel.updateProtocol(false) },
|
onClick = { viewModel.updateProtocol(false) },
|
||||||
label = { Text("🏠 Intern (HTTP)") },
|
label = { Text(stringResource(R.string.server_connection_http)) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
FilterChip(
|
FilterChip(
|
||||||
selected = isHttps,
|
selected = isHttps,
|
||||||
onClick = { viewModel.updateProtocol(true) },
|
onClick = { viewModel.updateProtocol(true) },
|
||||||
label = { Text("🌐 Extern (HTTPS)") },
|
label = { Text(stringResource(R.string.server_connection_https)) },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = if (!isHttps) {
|
text = if (!isHttps) {
|
||||||
"HTTP nur für lokale Netzwerke (z.B. 192.168.x.x, 10.x.x.x)"
|
stringResource(R.string.server_connection_http_hint)
|
||||||
} else {
|
} else {
|
||||||
"HTTPS für sichere Verbindungen über das Internet"
|
stringResource(R.string.server_connection_https_hint)
|
||||||
},
|
},
|
||||||
style = MaterialTheme.typography.bodySmall,
|
style = MaterialTheme.typography.bodySmall,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
@@ -121,8 +123,8 @@ fun ServerSettingsScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = serverUrl,
|
value = serverUrl,
|
||||||
onValueChange = { viewModel.updateServerUrl(it) },
|
onValueChange = { viewModel.updateServerUrl(it) },
|
||||||
label = { Text("Server-Adresse") },
|
label = { Text(stringResource(R.string.server_address)) },
|
||||||
supportingText = { Text("z.B. http://192.168.0.188:8080/notes") },
|
supportingText = { Text(stringResource(R.string.server_address_hint)) },
|
||||||
leadingIcon = { Icon(Icons.Default.Language, null) },
|
leadingIcon = { Icon(Icons.Default.Language, null) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
@@ -135,7 +137,7 @@ fun ServerSettingsScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = username,
|
value = username,
|
||||||
onValueChange = { viewModel.updateUsername(it) },
|
onValueChange = { viewModel.updateUsername(it) },
|
||||||
label = { Text("Benutzername") },
|
label = { Text(stringResource(R.string.username)) },
|
||||||
leadingIcon = { Icon(Icons.Default.Person, null) },
|
leadingIcon = { Icon(Icons.Default.Person, null) },
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
singleLine = true
|
singleLine = true
|
||||||
@@ -147,7 +149,7 @@ fun ServerSettingsScreen(
|
|||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = password,
|
value = password,
|
||||||
onValueChange = { viewModel.updatePassword(it) },
|
onValueChange = { viewModel.updatePassword(it) },
|
||||||
label = { Text("Passwort") },
|
label = { Text(stringResource(R.string.password)) },
|
||||||
leadingIcon = { Icon(Icons.Default.Lock, null) },
|
leadingIcon = { Icon(Icons.Default.Lock, null) },
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
IconButton(onClick = { passwordVisible = !passwordVisible }) {
|
||||||
@@ -157,7 +159,7 @@ fun ServerSettingsScreen(
|
|||||||
} else {
|
} else {
|
||||||
Icons.Default.Visibility
|
Icons.Default.Visibility
|
||||||
},
|
},
|
||||||
contentDescription = if (passwordVisible) "Verstecken" else "Anzeigen"
|
contentDescription = if (passwordVisible) stringResource(R.string.server_password_hide) else stringResource(R.string.server_password_show)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -187,14 +189,14 @@ fun ServerSettingsScreen(
|
|||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Text("Server-Status:", style = MaterialTheme.typography.labelLarge)
|
Text(stringResource(R.string.server_status_label), style = MaterialTheme.typography.labelLarge)
|
||||||
Text(
|
Text(
|
||||||
text = when (serverStatus) {
|
text = when (serverStatus) {
|
||||||
is SettingsViewModel.ServerStatus.Reachable -> "✅ Erreichbar"
|
is SettingsViewModel.ServerStatus.Reachable -> stringResource(R.string.server_status_reachable)
|
||||||
is SettingsViewModel.ServerStatus.Unreachable -> "❌ Nicht erreichbar"
|
is SettingsViewModel.ServerStatus.Unreachable -> stringResource(R.string.server_status_unreachable)
|
||||||
is SettingsViewModel.ServerStatus.Checking -> "🔍 Prüfe..."
|
is SettingsViewModel.ServerStatus.Checking -> stringResource(R.string.server_status_checking)
|
||||||
is SettingsViewModel.ServerStatus.NotConfigured -> "⚠️ Nicht konfiguriert"
|
is SettingsViewModel.ServerStatus.NotConfigured -> stringResource(R.string.server_status_not_configured)
|
||||||
else -> "❓ Unbekannt"
|
else -> stringResource(R.string.server_status_unknown)
|
||||||
},
|
},
|
||||||
color = when (serverStatus) {
|
color = when (serverStatus) {
|
||||||
is SettingsViewModel.ServerStatus.Reachable -> Color(0xFF4CAF50)
|
is SettingsViewModel.ServerStatus.Reachable -> Color(0xFF4CAF50)
|
||||||
@@ -217,7 +219,7 @@ fun ServerSettingsScreen(
|
|||||||
onClick = { viewModel.testConnection() },
|
onClick = { viewModel.testConnection() },
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text("Verbindung testen")
|
Text(stringResource(R.string.test_connection))
|
||||||
}
|
}
|
||||||
|
|
||||||
Button(
|
Button(
|
||||||
@@ -233,7 +235,7 @@ fun ServerSettingsScreen(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
Text("Jetzt synchronisieren")
|
Text(stringResource(R.string.sync_now))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package dev.dettmer.simplenotes.ui.settings.screens
|
package dev.dettmer.simplenotes.ui.settings.screens
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@@ -10,6 +11,7 @@ import androidx.compose.material.icons.filled.BugReport
|
|||||||
import androidx.compose.material.icons.filled.Cloud
|
import androidx.compose.material.icons.filled.Cloud
|
||||||
import androidx.compose.material.icons.filled.Description
|
import androidx.compose.material.icons.filled.Description
|
||||||
import androidx.compose.material.icons.filled.Info
|
import androidx.compose.material.icons.filled.Info
|
||||||
|
import androidx.compose.material.icons.filled.Language
|
||||||
import androidx.compose.material.icons.filled.Sync
|
import androidx.compose.material.icons.filled.Sync
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -17,8 +19,10 @@ import androidx.compose.runtime.collectAsState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.dettmer.simplenotes.BuildConfig
|
import dev.dettmer.simplenotes.BuildConfig
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsRoute
|
import dev.dettmer.simplenotes.ui.settings.SettingsRoute
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsCard
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsCard
|
||||||
@@ -46,8 +50,18 @@ fun SettingsMainScreen(
|
|||||||
viewModel.checkServerStatus()
|
viewModel.checkServerStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get current language for display (no remember - always fresh value after activity recreate)
|
||||||
|
val locales = AppCompatDelegate.getApplicationLocales()
|
||||||
|
val currentLanguageName = if (locales.isEmpty) {
|
||||||
|
null // System default
|
||||||
|
} else {
|
||||||
|
locales[0]?.displayLanguage?.replaceFirstChar { it.uppercase() }
|
||||||
|
}
|
||||||
|
val systemDefaultText = stringResource(R.string.language_system_default)
|
||||||
|
val languageSubtitle = currentLanguageName ?: systemDefaultText
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Einstellungen",
|
title = stringResource(R.string.settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -56,6 +70,16 @@ fun SettingsMainScreen(
|
|||||||
.padding(paddingValues),
|
.padding(paddingValues),
|
||||||
contentPadding = PaddingValues(vertical = 8.dp)
|
contentPadding = PaddingValues(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
// Language Settings
|
||||||
|
item {
|
||||||
|
SettingsCard(
|
||||||
|
icon = Icons.Default.Language,
|
||||||
|
title = stringResource(R.string.settings_language),
|
||||||
|
subtitle = languageSubtitle,
|
||||||
|
onClick = { onNavigate(SettingsRoute.Language) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Server-Einstellungen
|
// Server-Einstellungen
|
||||||
item {
|
item {
|
||||||
// v1.5.0 Fix: Nur Prefix-URLs gelten als "nicht konfiguriert"
|
// v1.5.0 Fix: Nur Prefix-URLs gelten als "nicht konfiguriert"
|
||||||
@@ -65,13 +89,13 @@ fun SettingsMainScreen(
|
|||||||
|
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.Cloud,
|
icon = Icons.Default.Cloud,
|
||||||
title = "Server-Einstellungen",
|
title = stringResource(R.string.settings_server),
|
||||||
subtitle = if (isConfigured) serverUrl else null,
|
subtitle = if (isConfigured) serverUrl else null,
|
||||||
statusText = when (serverStatus) {
|
statusText = when (serverStatus) {
|
||||||
is SettingsViewModel.ServerStatus.Reachable -> "✅ Erreichbar"
|
is SettingsViewModel.ServerStatus.Reachable -> stringResource(R.string.settings_server_status_reachable)
|
||||||
is SettingsViewModel.ServerStatus.Unreachable -> "❌ Nicht erreichbar"
|
is SettingsViewModel.ServerStatus.Unreachable -> stringResource(R.string.settings_server_status_unreachable)
|
||||||
is SettingsViewModel.ServerStatus.Checking -> "🔍 Prüfe..."
|
is SettingsViewModel.ServerStatus.Checking -> stringResource(R.string.settings_server_status_checking)
|
||||||
is SettingsViewModel.ServerStatus.NotConfigured -> "⚠️ Nicht konfiguriert"
|
is SettingsViewModel.ServerStatus.NotConfigured -> stringResource(R.string.settings_server_status_not_configured)
|
||||||
else -> null
|
else -> null
|
||||||
},
|
},
|
||||||
statusColor = when (serverStatus) {
|
statusColor = when (serverStatus) {
|
||||||
@@ -87,14 +111,14 @@ fun SettingsMainScreen(
|
|||||||
// Sync-Einstellungen
|
// Sync-Einstellungen
|
||||||
item {
|
item {
|
||||||
val intervalText = when (syncInterval) {
|
val intervalText = when (syncInterval) {
|
||||||
15L -> "15 Min"
|
15L -> stringResource(R.string.settings_interval_15min)
|
||||||
60L -> "60 Min"
|
60L -> stringResource(R.string.settings_interval_60min)
|
||||||
else -> "30 Min"
|
else -> stringResource(R.string.settings_interval_30min)
|
||||||
}
|
}
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.Sync,
|
icon = Icons.Default.Sync,
|
||||||
title = "Sync-Einstellungen",
|
title = stringResource(R.string.settings_sync),
|
||||||
subtitle = if (autoSyncEnabled) "Auto-Sync: An • $intervalText" else "Auto-Sync: Aus",
|
subtitle = if (autoSyncEnabled) stringResource(R.string.settings_sync_auto_on, intervalText) else stringResource(R.string.settings_sync_auto_off),
|
||||||
onClick = { onNavigate(SettingsRoute.Sync) }
|
onClick = { onNavigate(SettingsRoute.Sync) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -103,8 +127,8 @@ fun SettingsMainScreen(
|
|||||||
item {
|
item {
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.Description,
|
icon = Icons.Default.Description,
|
||||||
title = "Markdown Desktop-Integration",
|
title = stringResource(R.string.settings_markdown),
|
||||||
subtitle = if (markdownAutoSync) "Auto-Sync: An" else "Auto-Sync: Aus",
|
subtitle = if (markdownAutoSync) stringResource(R.string.settings_markdown_auto_on) else stringResource(R.string.settings_markdown_auto_off),
|
||||||
onClick = { onNavigate(SettingsRoute.Markdown) }
|
onClick = { onNavigate(SettingsRoute.Markdown) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -113,8 +137,8 @@ fun SettingsMainScreen(
|
|||||||
item {
|
item {
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.Backup,
|
icon = Icons.Default.Backup,
|
||||||
title = "Backup & Wiederherstellung",
|
title = stringResource(R.string.settings_backup),
|
||||||
subtitle = "Lokales oder Server-Backup",
|
subtitle = stringResource(R.string.settings_backup_subtitle),
|
||||||
onClick = { onNavigate(SettingsRoute.Backup) }
|
onClick = { onNavigate(SettingsRoute.Backup) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -123,8 +147,8 @@ fun SettingsMainScreen(
|
|||||||
item {
|
item {
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.Info,
|
icon = Icons.Default.Info,
|
||||||
title = "Über diese App",
|
title = stringResource(R.string.settings_about),
|
||||||
subtitle = "Version ${BuildConfig.VERSION_NAME}",
|
subtitle = stringResource(R.string.about_version, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE),
|
||||||
onClick = { onNavigate(SettingsRoute.About) }
|
onClick = { onNavigate(SettingsRoute.About) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -133,8 +157,8 @@ fun SettingsMainScreen(
|
|||||||
item {
|
item {
|
||||||
SettingsCard(
|
SettingsCard(
|
||||||
icon = Icons.Default.BugReport,
|
icon = Icons.Default.BugReport,
|
||||||
title = "Debug & Diagnose",
|
title = stringResource(R.string.settings_debug),
|
||||||
subtitle = if (fileLoggingEnabled) "Logging: An" else "Logging: Aus",
|
subtitle = if (fileLoggingEnabled) stringResource(R.string.settings_debug_logging_on) else stringResource(R.string.settings_debug_logging_off),
|
||||||
onClick = { onNavigate(SettingsRoute.Debug) }
|
onClick = { onNavigate(SettingsRoute.Debug) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
import dev.dettmer.simplenotes.ui.settings.SettingsViewModel
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.RadioOption
|
import dev.dettmer.simplenotes.ui.settings.components.RadioOption
|
||||||
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
import dev.dettmer.simplenotes.ui.settings.components.SettingsDivider
|
||||||
@@ -36,7 +38,7 @@ fun SyncSettingsScreen(
|
|||||||
val syncInterval by viewModel.syncInterval.collectAsState()
|
val syncInterval by viewModel.syncInterval.collectAsState()
|
||||||
|
|
||||||
SettingsScaffold(
|
SettingsScaffold(
|
||||||
title = "Sync-Einstellungen",
|
title = stringResource(R.string.sync_settings_title),
|
||||||
onBack = onBack
|
onBack = onBack
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -49,18 +51,14 @@ fun SyncSettingsScreen(
|
|||||||
|
|
||||||
// Auto-Sync Info
|
// Auto-Sync Info
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "🔄 Auto-Sync:\n" +
|
text = stringResource(R.string.sync_auto_sync_info)
|
||||||
"• Prüft alle 30 Min. ob Server erreichbar\n" +
|
|
||||||
"• Funktioniert bei jeder WiFi-Verbindung\n" +
|
|
||||||
"• Läuft auch im Hintergrund\n" +
|
|
||||||
"• Minimaler Akkuverbrauch (~0.4%/Tag)"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
// Auto-Sync Toggle
|
// Auto-Sync Toggle
|
||||||
SettingsSwitch(
|
SettingsSwitch(
|
||||||
title = "Auto-Sync aktiviert",
|
title = stringResource(R.string.sync_auto_sync_enabled),
|
||||||
checked = autoSyncEnabled,
|
checked = autoSyncEnabled,
|
||||||
onCheckedChange = { viewModel.setAutoSync(it) },
|
onCheckedChange = { viewModel.setAutoSync(it) },
|
||||||
icon = Icons.Default.Sync
|
icon = Icons.Default.Sync
|
||||||
@@ -69,14 +67,10 @@ fun SyncSettingsScreen(
|
|||||||
SettingsDivider()
|
SettingsDivider()
|
||||||
|
|
||||||
// Sync Interval Section
|
// Sync Interval Section
|
||||||
SettingsSectionHeader(text = "Sync-Intervall")
|
SettingsSectionHeader(text = stringResource(R.string.sync_interval_section))
|
||||||
|
|
||||||
SettingsInfoCard(
|
SettingsInfoCard(
|
||||||
text = "Legt fest, wie oft die App im Hintergrund synchronisiert. " +
|
text = stringResource(R.string.sync_interval_info)
|
||||||
"Kürzere Intervalle bedeuten aktuellere Daten, verbrauchen aber etwas mehr Akku.\n\n" +
|
|
||||||
"⏱️ Hinweis: Wenn dein Smartphone im Standby ist, kann Android die " +
|
|
||||||
"Synchronisation verzögern (bis zu 60 Min.), um Akku zu sparen. " +
|
|
||||||
"Das ist normal und betrifft alle Hintergrund-Apps."
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -85,18 +79,18 @@ fun SyncSettingsScreen(
|
|||||||
val intervalOptions = listOf(
|
val intervalOptions = listOf(
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = 15L,
|
value = 15L,
|
||||||
title = "⚡ Alle 15 Minuten",
|
title = stringResource(R.string.sync_interval_15min_title),
|
||||||
subtitle = "Schnellste Synchronisation • ~0.8% Akku/Tag (~23 mAh)"
|
subtitle = stringResource(R.string.sync_interval_15min_subtitle)
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = 30L,
|
value = 30L,
|
||||||
title = "✓ Alle 30 Minuten (Empfohlen)",
|
title = stringResource(R.string.sync_interval_30min_title),
|
||||||
subtitle = "Ausgewogenes Verhältnis • ~0.4% Akku/Tag (~12 mAh)"
|
subtitle = stringResource(R.string.sync_interval_30min_subtitle)
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value = 60L,
|
value = 60L,
|
||||||
title = "🔋 Alle 60 Minuten",
|
title = stringResource(R.string.sync_interval_60min_title),
|
||||||
subtitle = "Maximale Akkulaufzeit • ~0.2% Akku/Tag (~6 mAh geschätzt)"
|
subtitle = stringResource(R.string.sync_interval_60min_subtitle)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.dettmer.simplenotes.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@@ -15,7 +16,7 @@ fun Context.showToast(message: String, duration: Int = Toast.LENGTH_SHORT) {
|
|||||||
Toast.makeText(this, message, duration).show()
|
Toast.makeText(this, message, duration).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Timestamp to readable format
|
// Timestamp to readable format (legacy - without context, uses German)
|
||||||
fun Long.toReadableTime(): String {
|
fun Long.toReadableTime(): String {
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val diff = now - this
|
val diff = now - this
|
||||||
@@ -41,6 +42,32 @@ fun Long.toReadableTime(): String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timestamp to readable format (with context for i18n)
|
||||||
|
fun Long.toReadableTime(context: Context): String {
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val diff = now - this
|
||||||
|
|
||||||
|
return when {
|
||||||
|
diff < TimeUnit.MINUTES.toMillis(1) -> context.getString(R.string.time_just_now)
|
||||||
|
diff < TimeUnit.HOURS.toMillis(1) -> {
|
||||||
|
val minutes = TimeUnit.MILLISECONDS.toMinutes(diff).toInt()
|
||||||
|
context.getString(R.string.time_minutes_ago, minutes)
|
||||||
|
}
|
||||||
|
diff < TimeUnit.DAYS.toMillis(1) -> {
|
||||||
|
val hours = TimeUnit.MILLISECONDS.toHours(diff).toInt()
|
||||||
|
context.getString(R.string.time_hours_ago, hours)
|
||||||
|
}
|
||||||
|
diff < TimeUnit.DAYS.toMillis(DAYS_THRESHOLD) -> {
|
||||||
|
val days = TimeUnit.MILLISECONDS.toDays(diff).toInt()
|
||||||
|
context.getString(R.string.time_days_ago, days)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
val sdf = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault())
|
||||||
|
sdf.format(Date(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Truncate long strings
|
// Truncate long strings
|
||||||
fun String.truncate(maxLength: Int): String {
|
fun String.truncate(maxLength: Int): String {
|
||||||
return if (length > maxLength) {
|
return if (length > maxLength) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package dev.dettmer.simplenotes.utils
|
|||||||
|
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -16,8 +17,6 @@ object NotificationHelper {
|
|||||||
|
|
||||||
private const val TAG = "NotificationHelper"
|
private const val TAG = "NotificationHelper"
|
||||||
private const val CHANNEL_ID = "notes_sync_channel"
|
private const val CHANNEL_ID = "notes_sync_channel"
|
||||||
private const val CHANNEL_NAME = "Notizen Synchronisierung"
|
|
||||||
private const val CHANNEL_DESCRIPTION = "Benachrichtigungen über Sync-Status"
|
|
||||||
private const val NOTIFICATION_ID = 1001
|
private const val NOTIFICATION_ID = 1001
|
||||||
private const val SYNC_NOTIFICATION_ID = 2
|
private const val SYNC_NOTIFICATION_ID = 2
|
||||||
private const val AUTO_CANCEL_TIMEOUT_MS = 30_000L
|
private const val AUTO_CANCEL_TIMEOUT_MS = 30_000L
|
||||||
@@ -29,9 +28,11 @@ object NotificationHelper {
|
|||||||
fun createNotificationChannel(context: Context) {
|
fun createNotificationChannel(context: Context) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
val importance = NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
val channelName = context.getString(R.string.notification_channel_name)
|
||||||
|
val channelDescription = context.getString(R.string.notification_channel_desc)
|
||||||
|
|
||||||
val channel = NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance).apply {
|
val channel = NotificationChannel(CHANNEL_ID, channelName, importance).apply {
|
||||||
description = CHANNEL_DESCRIPTION
|
description = channelDescription
|
||||||
enableVibration(true)
|
enableVibration(true)
|
||||||
enableLights(true)
|
enableLights(true)
|
||||||
}
|
}
|
||||||
@@ -68,8 +69,8 @@ object NotificationHelper {
|
|||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.ic_menu_upload)
|
.setSmallIcon(android.R.drawable.ic_menu_upload)
|
||||||
.setContentTitle("Sync erfolgreich")
|
.setContentTitle(context.getString(R.string.notification_sync_success_title))
|
||||||
.setContentText("$syncedCount Notiz(en) synchronisiert")
|
.setContentText(context.getString(R.string.notification_sync_success_message, syncedCount))
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -96,7 +97,7 @@ object NotificationHelper {
|
|||||||
fun showSyncFailureNotification(context: Context, errorMessage: String) {
|
fun showSyncFailureNotification(context: Context, errorMessage: String) {
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
.setSmallIcon(android.R.drawable.ic_dialog_alert)
|
||||||
.setContentTitle("Sync fehlgeschlagen")
|
.setContentTitle(context.getString(R.string.notification_sync_failed_title))
|
||||||
.setContentText(errorMessage)
|
.setContentText(errorMessage)
|
||||||
.setStyle(NotificationCompat.BigTextStyle()
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
.bigText(errorMessage))
|
.bigText(errorMessage))
|
||||||
@@ -125,8 +126,8 @@ object NotificationHelper {
|
|||||||
fun showSyncProgressNotification(context: Context): Int {
|
fun showSyncProgressNotification(context: Context): Int {
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.ic_popup_sync)
|
.setSmallIcon(android.R.drawable.ic_popup_sync)
|
||||||
.setContentTitle("Synchronisiere...")
|
.setContentTitle(context.getString(R.string.notification_sync_progress_title))
|
||||||
.setContentText("Notizen werden synchronisiert")
|
.setContentText(context.getString(R.string.notification_sync_progress_message))
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setProgress(0, 0, true)
|
.setProgress(0, 0, true)
|
||||||
@@ -161,8 +162,8 @@ object NotificationHelper {
|
|||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
.setSmallIcon(android.R.drawable.ic_dialog_info)
|
||||||
.setContentTitle("Sync-Konflikt erkannt")
|
.setContentTitle(context.getString(R.string.notification_sync_conflict_title))
|
||||||
.setContentText("$conflictCount Notiz(en) haben Konflikte")
|
.setContentText(context.getString(R.string.notification_sync_conflict_message, conflictCount))
|
||||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
@@ -212,8 +213,8 @@ object NotificationHelper {
|
|||||||
fun showSyncInProgress(context: Context) {
|
fun showSyncInProgress(context: Context) {
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||||
.setContentTitle("Synchronisierung läuft")
|
.setContentTitle(context.getString(R.string.notification_sync_in_progress_title))
|
||||||
.setContentText("Notizen werden synchronisiert...")
|
.setContentText(context.getString(R.string.notification_sync_in_progress_message))
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.build()
|
.build()
|
||||||
@@ -240,8 +241,8 @@ object NotificationHelper {
|
|||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
.setSmallIcon(android.R.drawable.stat_notify_sync)
|
||||||
.setContentTitle("Sync erfolgreich")
|
.setContentTitle(context.getString(R.string.notification_sync_success_title))
|
||||||
.setContentText("$count Notizen synchronisiert")
|
.setContentText(context.getString(R.string.notification_sync_success_message, count))
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||||
.setContentIntent(pendingIntent) // Click öffnet App
|
.setContentIntent(pendingIntent) // Click öffnet App
|
||||||
@@ -271,7 +272,7 @@ object NotificationHelper {
|
|||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
.setContentTitle("Sync Fehler")
|
.setContentTitle(context.getString(R.string.notification_sync_error_title))
|
||||||
.setContentText(message)
|
.setContentText(message)
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||||
@@ -308,11 +309,10 @@ object NotificationHelper {
|
|||||||
|
|
||||||
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
|
||||||
.setSmallIcon(android.R.drawable.stat_notify_error)
|
.setSmallIcon(android.R.drawable.stat_notify_error)
|
||||||
.setContentTitle("⚠️ Sync-Warnung")
|
.setContentTitle(context.getString(R.string.notification_sync_warning_title))
|
||||||
.setContentText("Server seit ${hoursSinceLastSync}h nicht erreichbar")
|
.setContentText(context.getString(R.string.notification_sync_warning_message, hoursSinceLastSync.toInt()))
|
||||||
.setStyle(NotificationCompat.BigTextStyle()
|
.setStyle(NotificationCompat.BigTextStyle()
|
||||||
.bigText("Der WebDAV-Server ist seit ${hoursSinceLastSync} Stunden nicht erreichbar. " +
|
.bigText(context.getString(R.string.notification_sync_warning_detail, hoursSinceLastSync.toInt())))
|
||||||
"Bitte prüfe deine Netzwerkverbindung oder Server-Einstellungen."))
|
|
||||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
.setCategory(NotificationCompat.CATEGORY_STATUS)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package dev.dettmer.simplenotes.utils
|
package dev.dettmer.simplenotes.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import dev.dettmer.simplenotes.R
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -91,7 +93,7 @@ object UrlValidator {
|
|||||||
* Validiert ob HTTP URL erlaubt ist
|
* Validiert ob HTTP URL erlaubt ist
|
||||||
* @return Pair<Boolean, String?> - (isValid, errorMessage)
|
* @return Pair<Boolean, String?> - (isValid, errorMessage)
|
||||||
*/
|
*/
|
||||||
fun validateHttpUrl(url: String): Pair<Boolean, String?> {
|
fun validateHttpUrl(context: Context, url: String): Pair<Boolean, String?> {
|
||||||
return try {
|
return try {
|
||||||
val parsedUrl = URL(url)
|
val parsedUrl = URL(url)
|
||||||
|
|
||||||
@@ -107,16 +109,15 @@ object UrlValidator {
|
|||||||
} else {
|
} else {
|
||||||
return Pair(
|
return Pair(
|
||||||
false,
|
false,
|
||||||
"HTTP ist nur für lokale Server erlaubt (z.B. 192.168.x.x, 10.x.x.x, nas.local). " +
|
context.getString(R.string.error_http_local_only)
|
||||||
"Für öffentliche Server verwende bitte HTTPS."
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Anderes Protokoll
|
// Anderes Protokoll
|
||||||
Pair(false, "Ungültiges Protokoll: ${parsedUrl.protocol}. Bitte verwende HTTP oder HTTPS.")
|
Pair(false, context.getString(R.string.error_invalid_protocol, parsedUrl.protocol))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Pair(false, "Ungültige URL: ${e.message}")
|
Pair(false, context.getString(R.string.error_invalid_url, e.message ?: ""))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
393
android/app/src/main/res/values-de/strings.xml
Normal file
393
android/app/src/main/res/values-de/strings.xml
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- APP IDENTITY -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="app_name">Simple Notes</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- MAIN SCREEN -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="main_title">Simple Notes</string>
|
||||||
|
<string name="no_notes_yet">Noch keine Notizen.\nTippe + um eine zu erstellen.</string>
|
||||||
|
<string name="add_note">Notiz hinzufügen</string>
|
||||||
|
<string name="sync">Synchronisieren</string>
|
||||||
|
<string name="settings">Einstellungen</string>
|
||||||
|
<string name="action_sync">Synchronisieren</string>
|
||||||
|
<string name="action_settings">Einstellungen</string>
|
||||||
|
<string name="action_close_selection">Auswahl beenden</string>
|
||||||
|
<string name="action_select_all">Alle auswählen</string>
|
||||||
|
<string name="action_delete_selected">Ausgewählte löschen</string>
|
||||||
|
<string name="selection_count">%d ausgewählt</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- EMPTY STATE -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="empty_state_title">Noch keine Notizen</string>
|
||||||
|
<string name="empty_state_message">Tippe + um eine neue Notiz zu erstellen</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- FAB MENU -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="fab_new_note">Neue Notiz</string>
|
||||||
|
<string name="fab_text_note">Text-Notiz</string>
|
||||||
|
<string name="fab_checklist">Checkliste</string>
|
||||||
|
<string name="create_text_note">Notiz</string>
|
||||||
|
<string name="create_checklist">Liste</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- NOTE CARD -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="note_title_placeholder">Notiz-Titel</string>
|
||||||
|
<string name="note_content_placeholder">Notiz-Vorschau…</string>
|
||||||
|
<string name="note_timestamp_placeholder">Vor 2 Std</string>
|
||||||
|
<string name="untitled">Ohne Titel</string>
|
||||||
|
<string name="checklist_progress">%1$d/%2$d erledigt</string>
|
||||||
|
<string name="empty_checklist">Keine Einträge</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SYNC STATUS BANNER -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="sync_status">Sync-Status</string>
|
||||||
|
<string name="sync_syncing">Synchronisiere…</string>
|
||||||
|
<string name="sync_completed">Synchronisiert</string>
|
||||||
|
<string name="sync_error">Fehler</string>
|
||||||
|
<string name="sync_status_syncing">Synchronisiere…</string>
|
||||||
|
<string name="sync_status_completed">Synchronisierung abgeschlossen</string>
|
||||||
|
<string name="sync_status_error">Synchronisierung fehlgeschlagen</string>
|
||||||
|
<string name="sync_already_running">Synchronisierung läuft bereits</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- DELETE DIALOGS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="delete_note_title">Notiz löschen?</string>
|
||||||
|
<string name="delete_notes_title">%d Notizen löschen?</string>
|
||||||
|
<string name="delete_note_message">Wie möchtest du diese Notiz löschen?</string>
|
||||||
|
<string name="delete_notes_message">Wie möchtest du diese %d Notizen löschen?</string>
|
||||||
|
<string name="delete_everywhere">Überall löschen (auch Server)</string>
|
||||||
|
<string name="delete_local_only">Nur lokal löschen</string>
|
||||||
|
<string name="delete">Löschen</string>
|
||||||
|
<string name="cancel">Abbrechen</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<!-- Legacy delete dialogs -->
|
||||||
|
<string name="legacy_delete_dialog_title">Notiz löschen</string>
|
||||||
|
<string name="legacy_delete_dialog_message">\"%s\" wird lokal gelöscht.\n\nAuch vom Server löschen?</string>
|
||||||
|
<string name="legacy_delete_from_server">Vom Server löschen</string>
|
||||||
|
<string name="legacy_delete_with_server">\"%s\" wird lokal und vom Server gelöscht</string>
|
||||||
|
<string name="legacy_delete_local_only">\"%s\" lokal gelöscht (Server bleibt)</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SNACKBAR MESSAGES -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="snackbar_undo">RÜCKGÄNGIG</string>
|
||||||
|
<string name="snackbar_note_deleted_local">\"%s\" lokal gelöscht</string>
|
||||||
|
<string name="snackbar_note_deleted_server">\"%s\" wird vom Server gelöscht</string>
|
||||||
|
<string name="snackbar_notes_deleted_local">%d Notiz(en) lokal gelöscht</string>
|
||||||
|
<string name="snackbar_notes_deleted_server">%d Notiz(en) werden vom Server gelöscht</string>
|
||||||
|
<string name="snackbar_deleted_from_server">Vom Server gelöscht</string>
|
||||||
|
<string name="snackbar_server_delete_failed">Server-Löschung fehlgeschlagen</string>
|
||||||
|
<string name="snackbar_server_error">Server-Fehler: %s</string>
|
||||||
|
<string name="snackbar_already_synced">Bereits synchronisiert</string>
|
||||||
|
<string name="snackbar_server_unreachable">Server nicht erreichbar</string>
|
||||||
|
<string name="snackbar_synced_count">✅ Gesynct: %d Notizen</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- URL VALIDATION ERRORS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="error_http_local_only">HTTP ist nur für lokale Server erlaubt (z.B. 192.168.x.x, 10.x.x.x, nas.local). Für öffentliche Server verwende bitte HTTPS.</string>
|
||||||
|
<string name="error_invalid_protocol">Ungültiges Protokoll: %s. Bitte verwende HTTP oder HTTPS.</string>
|
||||||
|
<string name="error_invalid_url">Ungültige URL: %s</string>
|
||||||
|
<string name="error_server_not_configured">WebDAV-Server nicht vollständig konfiguriert</string>
|
||||||
|
<string name="error_sardine_client_failed">Sardine Client konnte nicht erstellt werden</string>
|
||||||
|
<string name="error_server_url_not_configured">Server-URL nicht konfiguriert</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- NOTE EDITOR -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="new_note">Neue Notiz</string>
|
||||||
|
<string name="edit_note">Notiz bearbeiten</string>
|
||||||
|
<string name="new_checklist">Neue Liste</string>
|
||||||
|
<string name="edit_checklist">Liste bearbeiten</string>
|
||||||
|
<string name="title">Titel</string>
|
||||||
|
<string name="content">Inhalt</string>
|
||||||
|
<string name="back">Zurück</string>
|
||||||
|
<string name="save">Speichern</string>
|
||||||
|
<string name="add_item">Element hinzufügen</string>
|
||||||
|
<string name="item_placeholder">Neues Element…</string>
|
||||||
|
<string name="reorder_item">Element verschieben</string>
|
||||||
|
<string name="drag_to_reorder">Ziehen zum Sortieren</string>
|
||||||
|
<string name="delete_item">Element löschen</string>
|
||||||
|
<string name="note_is_empty">Notiz ist leer</string>
|
||||||
|
<string name="note_saved">Notiz gespeichert</string>
|
||||||
|
<string name="note_deleted">Notiz gelöscht</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - MAIN -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="settings_title">Einstellungen</string>
|
||||||
|
<string name="settings_language">Sprache</string>
|
||||||
|
<string name="settings_language_subtitle">%s</string>
|
||||||
|
<string name="settings_server">Server-Einstellungen</string>
|
||||||
|
<string name="settings_server_status_reachable">✅ Erreichbar</string>
|
||||||
|
<string name="settings_server_status_unreachable">❌ Nicht erreichbar</string>
|
||||||
|
<string name="settings_server_status_checking">🔍 Prüfe…</string>
|
||||||
|
<string name="settings_server_status_not_configured">⚠️ Nicht konfiguriert</string>
|
||||||
|
<string name="settings_sync">Sync-Einstellungen</string>
|
||||||
|
<string name="settings_sync_auto_on">Auto-Sync: An • %s</string>
|
||||||
|
<string name="settings_sync_auto_off">Auto-Sync: Aus</string>
|
||||||
|
<string name="settings_interval_15min">15 Min</string>
|
||||||
|
<string name="settings_interval_30min">30 Min</string>
|
||||||
|
<string name="settings_interval_60min">60 Min</string>
|
||||||
|
<string name="settings_markdown">Markdown Desktop-Integration</string>
|
||||||
|
<string name="settings_markdown_auto_on">Auto-Sync: An</string>
|
||||||
|
<string name="settings_markdown_auto_off">Auto-Sync: Aus</string>
|
||||||
|
<string name="settings_backup">Backup & Wiederherstellung</string>
|
||||||
|
<string name="settings_backup_subtitle">Lokales oder Server-Backup</string>
|
||||||
|
<string name="settings_about">Über diese App</string>
|
||||||
|
<string name="settings_debug">Debug & Diagnose</string>
|
||||||
|
<string name="settings_debug_logging_on">Logging: An</string>
|
||||||
|
<string name="settings_debug_logging_off">Logging: Aus</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - SERVER -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="server_settings">Server-Einstellungen</string>
|
||||||
|
<string name="server_settings_title">Server-Einstellungen</string>
|
||||||
|
<string name="server_connection_type">Verbindungstyp</string>
|
||||||
|
<string name="server_connection_http">🏠 Intern (HTTP)</string>
|
||||||
|
<string name="server_connection_https">🌐 Extern (HTTPS)</string>
|
||||||
|
<string name="server_connection_http_hint">HTTP nur für lokale Netzwerke (z.B. 192.168.x.x, 10.x.x.x)</string>
|
||||||
|
<string name="server_connection_https_hint">HTTPS für sichere Verbindungen über das Internet</string>
|
||||||
|
<string name="server_address">Server-Adresse</string>
|
||||||
|
<string name="server_address_hint">z.B. http://192.168.0.188:8080/notes</string>
|
||||||
|
<string name="server_url">Server URL</string>
|
||||||
|
<string name="username">Benutzername</string>
|
||||||
|
<string name="password">Passwort</string>
|
||||||
|
<string name="server_password_show">Anzeigen</string>
|
||||||
|
<string name="server_password_hide">Verstecken</string>
|
||||||
|
<string name="server_status_label">Server-Status:</string>
|
||||||
|
<string name="server_status_reachable">✅ Erreichbar</string>
|
||||||
|
<string name="server_status_unreachable">❌ Nicht erreichbar</string>
|
||||||
|
<string name="server_status_checking">🔍 Prüfe…</string>
|
||||||
|
<string name="server_status_not_configured">⚠️ Nicht konfiguriert</string>
|
||||||
|
<string name="server_status_unknown">❓ Unbekannt</string>
|
||||||
|
<string name="test_connection">Verbindung testen</string>
|
||||||
|
<string name="sync_now">Jetzt synchronisieren</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - SYNC -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="sync_settings">Sync-Einstellungen</string>
|
||||||
|
<string name="sync_settings_title">Sync-Einstellungen</string>
|
||||||
|
<string name="auto_sync">Auto-Sync aktiviert</string>
|
||||||
|
<string name="sync_auto_sync_info">🔄 Auto-Sync:\n• Prüft alle 30 Min. ob Server erreichbar\n• Funktioniert bei jeder WiFi-Verbindung\n• Läuft auch im Hintergrund\n• Minimaler Akkuverbrauch (~0.4%/Tag)</string>
|
||||||
|
<string name="sync_auto_sync_enabled">Auto-Sync aktiviert</string>
|
||||||
|
<string name="sync_interval_section">Sync-Intervall</string>
|
||||||
|
<string name="sync_interval_info">Legt fest, wie oft die App im Hintergrund synchronisiert. Kürzere Intervalle bedeuten aktuellere Daten, verbrauchen aber etwas mehr Akku.\n\n⏱️ Hinweis: Wenn dein Smartphone im Standby ist, kann Android die Synchronisation verzögern (bis zu 60 Min.), um Akku zu sparen. Das ist normal und betrifft alle Hintergrund-Apps.</string>
|
||||||
|
<string name="sync_interval_15min_title">⚡ Alle 15 Minuten</string>
|
||||||
|
<string name="sync_interval_15min_subtitle">Schnellste Synchronisation • ~0.8% Akku/Tag (~23 mAh)</string>
|
||||||
|
<string name="sync_interval_30min_title">✓ Alle 30 Minuten (Empfohlen)</string>
|
||||||
|
<string name="sync_interval_30min_subtitle">Ausgewogenes Verhältnis • ~0.4% Akku/Tag (~12 mAh)</string>
|
||||||
|
<string name="sync_interval_60min_title">🔋 Alle 60 Minuten</string>
|
||||||
|
<string name="sync_interval_60min_subtitle">Maximale Akkulaufzeit • ~0.2% Akku/Tag (~6 mAh geschätzt)</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="auto_sync_info">ℹ️ Auto-Sync:\n\n• Prüft alle 30 Min ob Server erreichbar\n• Funktioniert bei jeder WiFi-Verbindung\n• Läuft auch im Hintergrund\n• Minimaler Akkuverbrauch (~0.4%%/Tag)</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - MARKDOWN -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="markdown_settings_title">Markdown Desktop-Integration</string>
|
||||||
|
<string name="markdown_dialog_title">Markdown Auto-Sync</string>
|
||||||
|
<string name="markdown_export_complete">✅ Export abgeschlossen</string>
|
||||||
|
<string name="markdown_export_progress">Exportiere %1$d/%2$d Notizen…</string>
|
||||||
|
<string name="markdown_info">📝 Exportiert Notizen zusätzlich als .md-Dateien. Mounte WebDAV als Netzlaufwerk um mit VS Code, Typora oder jedem Markdown-Editor zu bearbeiten. JSON-Sync bleibt primäres Format.</string>
|
||||||
|
<string name="markdown_auto_sync_title">Markdown Auto-Sync</string>
|
||||||
|
<string name="markdown_auto_sync_subtitle">Synchronisiert Notizen automatisch als .md-Dateien (Upload + Download bei jedem Sync)</string>
|
||||||
|
<string name="markdown_manual_sync_info">Manueller Sync exportiert alle Notizen als .md-Dateien und importiert .md-Dateien vom Server. Nützlich für einmalige Synchronisation.</string>
|
||||||
|
<string name="markdown_manual_sync_button">📝 Manueller Markdown-Sync</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - BACKUP -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="backup_settings_title">Backup & Wiederherstellung</string>
|
||||||
|
<string name="backup_restore_title">Backup & Wiederherstellung</string>
|
||||||
|
<string name="backup_auto_info">📦 Bei jeder Wiederherstellung wird automatisch ein Sicherheits-Backup erstellt.</string>
|
||||||
|
<string name="backup_local_section">Lokales Backup</string>
|
||||||
|
<string name="backup_create">💾 Backup erstellen</string>
|
||||||
|
<string name="backup_restore_file">📂 Aus Datei wiederherstellen</string>
|
||||||
|
<string name="backup_server_section">Server-Backup</string>
|
||||||
|
<string name="backup_restore_server">☁️ Vom Server wiederherstellen</string>
|
||||||
|
<string name="backup_restore_dialog_title">⚠️ Backup wiederherstellen?</string>
|
||||||
|
<string name="backup_restore_source">Quelle: %s</string>
|
||||||
|
<string name="backup_restore_source_file">Lokale Datei</string>
|
||||||
|
<string name="backup_restore_source_server">WebDAV Server</string>
|
||||||
|
<string name="backup_restore_mode_label">Wiederherstellungs-Modus:</string>
|
||||||
|
<string name="backup_mode_merge_title">⚪ Zusammenführen (Standard)</string>
|
||||||
|
<string name="backup_mode_merge_subtitle">Neue hinzufügen, Bestehende behalten</string>
|
||||||
|
<string name="backup_mode_merge_full">⚪ Zusammenführen (Standard)\n → Neue hinzufügen, Bestehende behalten</string>
|
||||||
|
<string name="backup_mode_replace_title">⚪ Ersetzen</string>
|
||||||
|
<string name="backup_mode_replace_subtitle">Alle löschen & Backup importieren</string>
|
||||||
|
<string name="backup_mode_replace_full">⚪ Ersetzen\n → Alle löschen & Backup importieren</string>
|
||||||
|
<string name="backup_mode_overwrite_title">⚪ Duplikate überschreiben</string>
|
||||||
|
<string name="backup_mode_overwrite_subtitle">Backup gewinnt bei Konflikten</string>
|
||||||
|
<string name="backup_mode_overwrite_full">⚪ Duplikate überschreiben\n → Backup gewinnt bei Konflikten</string>
|
||||||
|
<string name="backup_restore_info">ℹ️ Ein Sicherheits-Backup wird vor dem Wiederherstellen automatisch erstellt.</string>
|
||||||
|
<string name="backup_restore_button">Wiederherstellen</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="backup_restore_warning">⚠️ Achtung:\n\nDie Wiederherstellung überschreibt ALLE lokalen Notizen mit den Daten vom Server. Diese Aktion kann nicht rückgängig gemacht werden!</string>
|
||||||
|
<string name="restore_from_server">Vom Server wiederherstellen</string>
|
||||||
|
<string name="restore_confirmation_title">⚠️ Vom Server wiederherstellen?</string>
|
||||||
|
<string name="restore_confirmation_message">WARNUNG: Alle lokalen Notizen werden gelöscht und durch die Notizen vom Server ersetzt.\n\nDieser Vorgang kann nicht rückgängig gemacht werden!</string>
|
||||||
|
<string name="restore_button">Wiederherstellen</string>
|
||||||
|
<string name="restore_progress">Stelle Notizen wieder her…</string>
|
||||||
|
<string name="restore_success">✓ %d Notizen wiederhergestellt</string>
|
||||||
|
<string name="restore_error">Fehler: %s</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - DEBUG -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="debug_settings_title">Debug & Diagnose</string>
|
||||||
|
<string name="debug_file_logging_title">Datei-Logging</string>
|
||||||
|
<string name="debug_file_logging_subtitle">Sync-Logs in Datei speichern</string>
|
||||||
|
<string name="debug_privacy_info">🔒 Datenschutz: Logs werden nur lokal auf deinem Gerät gespeichert und niemals an externe Server gesendet. Die Logs enthalten Sync-Aktivitäten zur Fehlerdiagnose. Du kannst sie jederzeit löschen oder exportieren.</string>
|
||||||
|
<string name="debug_log_actions_section">Log-Aktionen</string>
|
||||||
|
<string name="debug_export_logs">📤 Logs exportieren & teilen</string>
|
||||||
|
<string name="debug_logs_subject">SimpleNotes Sync Logs</string>
|
||||||
|
<string name="debug_logs_share_via">Logs teilen via…</string>
|
||||||
|
<string name="debug_delete_logs">🗑️ Logs löschen</string>
|
||||||
|
<string name="debug_delete_logs_title">Logs löschen?</string>
|
||||||
|
<string name="debug_delete_logs_message">Alle gespeicherten Sync-Logs werden unwiderruflich gelöscht.</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="file_logging_privacy_notice">ℹ️ Datenschutz: Logs werden nur lokal auf deinem Gerät gespeichert und niemals an externe Server gesendet. Die Logs enthalten Sync-Aktivitäten zur Fehlerdiagnose. Du kannst sie jederzeit löschen oder exportieren.</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - LANGUAGE -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="language_settings_title">Sprache</string>
|
||||||
|
<string name="language_system_default">Systemstandard</string>
|
||||||
|
<string name="language_english">English</string>
|
||||||
|
<string name="language_german">Deutsch</string>
|
||||||
|
<string name="language_info">ℹ️ Wähle deine bevorzugte Sprache. Die App wird neu gestartet, um die Änderung anzuwenden.</string>
|
||||||
|
<string name="language_changed_restart">Sprache geändert. Neustart…</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - ABOUT -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="about_settings_title">Über diese App</string>
|
||||||
|
<string name="about_app_name">Simple Notes Sync</string>
|
||||||
|
<string name="about_version">Version %1$s (%2$d)</string>
|
||||||
|
<string name="about_links_section">Links</string>
|
||||||
|
<string name="about_github_title">GitHub Repository</string>
|
||||||
|
<string name="about_github_subtitle">Quellcode, Issues & Dokumentation</string>
|
||||||
|
<string name="about_developer_title">Entwickler</string>
|
||||||
|
<string name="about_developer_subtitle">GitHub Profil: @inventory69</string>
|
||||||
|
<string name="about_license_title">Lizenz</string>
|
||||||
|
<string name="about_license_subtitle">MIT License - Open Source</string>
|
||||||
|
<string name="about_privacy_title">🔒 Datenschutz</string>
|
||||||
|
<string name="about_privacy_text">Diese App sammelt keine Daten. Alle Notizen werden nur lokal auf deinem Gerät und auf deinem eigenen WebDAV-Server gespeichert. Keine Telemetrie, keine Werbung.</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- TOAST MESSAGES -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="toast_connection_success">✅ Verbindung erfolgreich!</string>
|
||||||
|
<string name="toast_connection_failed">❌ %s</string>
|
||||||
|
<string name="toast_error">❌ Fehler: %s</string>
|
||||||
|
<string name="toast_syncing">🔄 Synchronisiere…</string>
|
||||||
|
<string name="toast_already_synced">✅ Bereits synchronisiert</string>
|
||||||
|
<string name="toast_sync_success">✅ %d Notizen synchronisiert</string>
|
||||||
|
<string name="toast_sync_failed">❌ %s</string>
|
||||||
|
<string name="toast_auto_sync_enabled">✅ Auto-Sync aktiviert</string>
|
||||||
|
<string name="toast_auto_sync_disabled">Auto-Sync deaktiviert</string>
|
||||||
|
<string name="toast_sync_interval">⏱️ Sync-Intervall: %s</string>
|
||||||
|
<string name="toast_sync_interval_15min">15 Minuten</string>
|
||||||
|
<string name="toast_sync_interval_30min">30 Minuten</string>
|
||||||
|
<string name="toast_sync_interval_60min">60 Minuten</string>
|
||||||
|
<string name="toast_configure_server_first">⚠️ Bitte zuerst WebDAV-Server konfigurieren</string>
|
||||||
|
<string name="toast_markdown_exported">✅ %d Notizen nach Markdown exportiert</string>
|
||||||
|
<string name="toast_markdown_enabled">📝 Markdown Auto-Sync aktiviert</string>
|
||||||
|
<string name="toast_markdown_disabled">📝 Markdown Auto-Sync deaktiviert</string>
|
||||||
|
<string name="toast_markdown_syncing">📝 Markdown-Sync läuft…</string>
|
||||||
|
<string name="toast_markdown_result">✅ Export: %1$d • Import: %2$d</string>
|
||||||
|
<string name="toast_export_failed">❌ Export fehlgeschlagen: %s</string>
|
||||||
|
<string name="toast_backup_success">✅ %s</string>
|
||||||
|
<string name="toast_backup_failed">❌ Backup fehlgeschlagen: %s</string>
|
||||||
|
<string name="toast_restore_success">✅ %d Notizen wiederhergestellt</string>
|
||||||
|
<string name="toast_restore_failed">❌ Wiederherstellung fehlgeschlagen: %s</string>
|
||||||
|
<string name="toast_notifications_enabled">Benachrichtigungen aktiviert</string>
|
||||||
|
<string name="toast_notifications_disabled">Benachrichtigungen deaktiviert. Du kannst sie in den Einstellungen aktivieren.</string>
|
||||||
|
<string name="toast_battery_optimization">Bitte Akku-Optimierung manuell deaktivieren</string>
|
||||||
|
<string name="toast_logs_deleted">🗑️ Logs gelöscht</string>
|
||||||
|
<string name="toast_no_logs_to_delete">📭 Keine Logs zum Löschen</string>
|
||||||
|
<string name="toast_logs_delete_error">❌ Fehler beim Löschen: %s</string>
|
||||||
|
<string name="toast_link_error">❌ Fehler beim Öffnen des Links</string>
|
||||||
|
<string name="toast_file_logging_enabled">📝 Datei-Logging aktiviert</string>
|
||||||
|
<string name="toast_file_logging_disabled">📝 Datei-Logging deaktiviert</string>
|
||||||
|
<string name="toast_sync_interval_changed">⏱️ Sync-Intervall auf %s geändert</string>
|
||||||
|
<string name="version_not_available">Version nicht verfügbar</string>
|
||||||
|
<string name="status_checking">🔍 Prüfe…</string>
|
||||||
|
<string name="battery_optimization_dialog_message">Bitte wähle \'Nicht optimieren\' für Simple Notes.</string>
|
||||||
|
<string name="battery_optimization_dialog_title">Hintergrund-Synchronisation</string>
|
||||||
|
<string name="battery_optimization_dialog_full_message">Damit die App im Hintergrund synchronisieren kann, muss die Akku-Optimierung deaktiviert werden.\n\nBitte wähle \'Nicht optimieren\' für Simple Notes.</string>
|
||||||
|
<string name="battery_optimization_open_settings">Einstellungen öffnen</string>
|
||||||
|
<string name="battery_optimization_later">Später</string>
|
||||||
|
<string name="content_description_back">Zurück</string>
|
||||||
|
<string name="error_invalid_backup_file">Ungültige Backup-Datei</string>
|
||||||
|
<string name="error_backup_version_unsupported">Backup-Version nicht unterstützt (v%1$d benötigt v%2$d+)</string>
|
||||||
|
<string name="error_backup_empty">Backup enthält keine Notizen</string>
|
||||||
|
<string name="error_backup_invalid_notes">Backup enthält %d ungültige Notizen</string>
|
||||||
|
<string name="error_backup_corrupt">Backup-Datei beschädigt oder ungültig: %s</string>
|
||||||
|
<string name="error_restore_failed">Wiederherstellung fehlgeschlagen: %s</string>
|
||||||
|
<string name="restore_merge_result">%1$d neue Notizen importiert, %2$d übersprungen</string>
|
||||||
|
<string name="restore_overwrite_result">%1$d neu, %2$d überschrieben</string>
|
||||||
|
<string name="restore_replace_result">Alle Notizen ersetzt: %d importiert</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- RELATIVE TIME -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="time_just_now">Gerade eben</string>
|
||||||
|
<string name="time_minutes_ago">Vor %d Min</string>
|
||||||
|
<string name="time_hours_ago">Vor %d Std</string>
|
||||||
|
<string name="time_days_ago">Vor %d Tagen</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- NOTIFICATIONS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="notification_channel_name">Notizen Synchronisierung</string>
|
||||||
|
<string name="notification_channel_desc">Benachrichtigungen über Sync-Status</string>
|
||||||
|
<string name="notification_sync_success_title">Sync erfolgreich</string>
|
||||||
|
<string name="notification_sync_success_message">%d Notiz(en) synchronisiert</string>
|
||||||
|
<string name="notification_sync_failed_title">Sync fehlgeschlagen</string>
|
||||||
|
<string name="notification_sync_progress_title">Synchronisiere…</string>
|
||||||
|
<string name="notification_sync_progress_message">Notizen werden synchronisiert</string>
|
||||||
|
<string name="notification_sync_conflict_title">Sync-Konflikt erkannt</string>
|
||||||
|
<string name="notification_sync_conflict_message">%d Notiz(en) haben Konflikte</string>
|
||||||
|
<string name="notification_sync_warning_title">⚠️ Sync-Warnung</string>
|
||||||
|
<string name="notification_sync_warning_message">Server seit %dh nicht erreichbar</string>
|
||||||
|
<string name="notification_sync_warning_detail">Der WebDAV-Server ist seit %d Stunden nicht erreichbar. Bitte prüfe deine Netzwerkverbindung oder Server-Einstellungen.</string>
|
||||||
|
<string name="notification_sync_in_progress_title">Synchronisierung läuft</string>
|
||||||
|
<string name="notification_sync_in_progress_message">Notizen werden synchronisiert…</string>
|
||||||
|
<string name="notification_sync_error_title">Sync Fehler</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- PLURALS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<plurals name="notes_count">
|
||||||
|
<item quantity="one">%d Notiz</item>
|
||||||
|
<item quantity="other">%d Notizen</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_deleted_local">
|
||||||
|
<item quantity="one">%d Notiz lokal gelöscht</item>
|
||||||
|
<item quantity="other">%d Notizen lokal gelöscht</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_deleted_server">
|
||||||
|
<item quantity="one">%d Notiz wird vom Server gelöscht</item>
|
||||||
|
<item quantity="other">%d Notizen werden vom Server gelöscht</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_synced">
|
||||||
|
<item quantity="one">%d Notiz synchronisiert</item>
|
||||||
|
<item quantity="other">%d Notizen synchronisiert</item>
|
||||||
|
</plurals>
|
||||||
|
</resources>
|
||||||
@@ -1,94 +1,394 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- APP IDENTITY -->
|
||||||
|
<!-- ============================= -->
|
||||||
<string name="app_name">Simple Notes</string>
|
<string name="app_name">Simple Notes</string>
|
||||||
|
|
||||||
<!-- Main Activity -->
|
<!-- ============================= -->
|
||||||
<string name="no_notes_yet">Noch keine Notizen.\nTippe + um eine zu erstellen.</string>
|
<!-- MAIN SCREEN -->
|
||||||
<string name="add_note">Notiz hinzufügen</string>
|
<!-- ============================= -->
|
||||||
<string name="sync">Synchronisieren</string>
|
<string name="main_title">Simple Notes</string>
|
||||||
<string name="settings">Einstellungen</string>
|
<string name="no_notes_yet">No notes yet.\nTap + to create one.</string>
|
||||||
|
<string name="add_note">Add note</string>
|
||||||
|
<string name="sync">Sync</string>
|
||||||
|
<string name="settings">Settings</string>
|
||||||
|
<string name="action_sync">Sync</string>
|
||||||
|
<string name="action_settings">Settings</string>
|
||||||
|
<string name="action_close_selection">Close selection</string>
|
||||||
|
<string name="action_select_all">Select all</string>
|
||||||
|
<string name="action_delete_selected">Delete selected</string>
|
||||||
|
<string name="selection_count">%d selected</string>
|
||||||
|
|
||||||
<!-- Empty State -->
|
<!-- ============================= -->
|
||||||
|
<!-- EMPTY STATE -->
|
||||||
|
<!-- ============================= -->
|
||||||
<string name="empty_state_emoji">📝</string>
|
<string name="empty_state_emoji">📝</string>
|
||||||
<string name="empty_state_title">Noch keine Notizen</string>
|
<string name="empty_state_title">No notes yet</string>
|
||||||
<string name="empty_state_message">Tippe auf ➕ um deine erste Notiz zu erstellen</string>
|
<string name="empty_state_message">Tap + to create a new note</string>
|
||||||
|
|
||||||
<!-- Note Editor -->
|
<!-- ============================= -->
|
||||||
<string name="edit_note">Notiz bearbeiten</string>
|
<!-- FAB MENU -->
|
||||||
<string name="new_note">Neue Notiz</string>
|
<!-- ============================= -->
|
||||||
<string name="title">Titel</string>
|
<string name="fab_new_note">New note</string>
|
||||||
<string name="content">Inhalt</string>
|
<string name="fab_text_note">Text note</string>
|
||||||
<string name="save">Speichern</string>
|
<string name="fab_checklist">Checklist</string>
|
||||||
<string name="delete">Löschen</string>
|
<string name="create_text_note">Note</string>
|
||||||
<string name="back">Zurück</string>
|
<string name="create_checklist">List</string>
|
||||||
|
|
||||||
<!-- Note List Item (Preview placeholders) -->
|
<!-- ============================= -->
|
||||||
|
<!-- NOTE CARD -->
|
||||||
|
<!-- ============================= -->
|
||||||
<string name="note_title_placeholder">Note Title</string>
|
<string name="note_title_placeholder">Note Title</string>
|
||||||
<string name="note_content_placeholder">Note content preview…</string>
|
<string name="note_content_placeholder">Note content preview…</string>
|
||||||
<string name="note_timestamp_placeholder">Vor 2 Std</string>
|
<string name="note_timestamp_placeholder">2 hours ago</string>
|
||||||
<string name="untitled">Ohne Titel</string>
|
<string name="untitled">Untitled</string>
|
||||||
|
<string name="checklist_progress">%1$d/%2$d done</string>
|
||||||
|
<string name="empty_checklist">No entries</string>
|
||||||
|
|
||||||
<!-- Delete Confirmation Dialog -->
|
<!-- ============================= -->
|
||||||
<string name="delete_note_title">Notiz löschen?</string>
|
<!-- SYNC STATUS BANNER -->
|
||||||
<string name="delete_note_message">Diese Aktion kann nicht rückgängig gemacht werden.</string>
|
<!-- ============================= -->
|
||||||
<string name="cancel">Abbrechen</string>
|
<string name="sync_status">Sync Status</string>
|
||||||
|
<string name="sync_syncing">Syncing…</string>
|
||||||
|
<string name="sync_completed">Synced</string>
|
||||||
|
<string name="sync_error">Error</string>
|
||||||
|
<string name="sync_status_syncing">Syncing…</string>
|
||||||
|
<string name="sync_status_completed">Sync completed</string>
|
||||||
|
<string name="sync_status_error">Sync failed</string>
|
||||||
|
<string name="sync_already_running">Sync already in progress</string>
|
||||||
|
|
||||||
<!-- Settings -->
|
<!-- ============================= -->
|
||||||
<string name="server_settings">Server-Einstellungen</string>
|
<!-- DELETE DIALOGS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="delete_note_title">Delete note?</string>
|
||||||
|
<string name="delete_notes_title">Delete %d notes?</string>
|
||||||
|
<string name="delete_note_message">How do you want to delete this note?</string>
|
||||||
|
<string name="delete_notes_message">How do you want to delete these %d notes?</string>
|
||||||
|
<string name="delete_everywhere">Delete everywhere (also server)</string>
|
||||||
|
<string name="delete_local_only">Delete local only</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<!-- Legacy delete dialogs -->
|
||||||
|
<string name="legacy_delete_dialog_title">Delete note</string>
|
||||||
|
<string name="legacy_delete_dialog_message">\"%s\" will be deleted locally.\n\nAlso delete from server?</string>
|
||||||
|
<string name="legacy_delete_from_server">Delete from server</string>
|
||||||
|
<string name="legacy_delete_with_server">\"%s\" will be deleted locally and from server</string>
|
||||||
|
<string name="legacy_delete_local_only">\"%s\" deleted locally (server remains)</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SNACKBAR MESSAGES -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="snackbar_undo">UNDO</string>
|
||||||
|
<string name="snackbar_note_deleted_local">\"%s\" deleted locally</string>
|
||||||
|
<string name="snackbar_note_deleted_server">\"%s\" will be deleted from server</string>
|
||||||
|
<string name="snackbar_notes_deleted_local">%d note(s) deleted locally</string>
|
||||||
|
<string name="snackbar_notes_deleted_server">%d note(s) will be deleted from server</string>
|
||||||
|
<string name="snackbar_deleted_from_server">Deleted from server</string>
|
||||||
|
<string name="snackbar_server_delete_failed">Server deletion failed</string>
|
||||||
|
<string name="snackbar_server_error">Server error: %s</string>
|
||||||
|
<string name="snackbar_already_synced">Already synced</string>
|
||||||
|
<string name="snackbar_server_unreachable">Server not reachable</string>
|
||||||
|
<string name="snackbar_synced_count">✅ Synced: %d notes</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- URL VALIDATION ERRORS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="error_http_local_only">HTTP is only allowed for local servers (e.g. 192.168.x.x, 10.x.x.x, nas.local). For public servers, please use HTTPS.</string>
|
||||||
|
<string name="error_invalid_protocol">Invalid protocol: %s. Please use HTTP or HTTPS.</string>
|
||||||
|
<string name="error_invalid_url">Invalid URL: %s</string>
|
||||||
|
<string name="error_server_not_configured">WebDAV server not fully configured</string>
|
||||||
|
<string name="error_sardine_client_failed">Sardine client could not be created</string>
|
||||||
|
<string name="error_server_url_not_configured">Server URL not configured</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- NOTE EDITOR -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="new_note">New Note</string>
|
||||||
|
<string name="edit_note">Edit Note</string>
|
||||||
|
<string name="new_checklist">New List</string>
|
||||||
|
<string name="edit_checklist">Edit List</string>
|
||||||
|
<string name="title">Title</string>
|
||||||
|
<string name="content">Content</string>
|
||||||
|
<string name="back">Back</string>
|
||||||
|
<string name="save">Save</string>
|
||||||
|
<string name="add_item">Add item</string>
|
||||||
|
<string name="item_placeholder">New item…</string>
|
||||||
|
<string name="reorder_item">Reorder item</string>
|
||||||
|
<string name="drag_to_reorder">Drag to reorder</string>
|
||||||
|
<string name="delete_item">Delete item</string>
|
||||||
|
<string name="note_is_empty">Note is empty</string>
|
||||||
|
<string name="note_saved">Note saved</string>
|
||||||
|
<string name="note_deleted">Note deleted</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - MAIN -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="settings_title">Settings</string>
|
||||||
|
<string name="settings_language">Language</string>
|
||||||
|
<string name="settings_language_subtitle">%s</string>
|
||||||
|
<string name="settings_server">Server Settings</string>
|
||||||
|
<string name="settings_server_status_reachable">✅ Reachable</string>
|
||||||
|
<string name="settings_server_status_unreachable">❌ Not reachable</string>
|
||||||
|
<string name="settings_server_status_checking">🔍 Checking…</string>
|
||||||
|
<string name="settings_server_status_not_configured">⚠️ Not configured</string>
|
||||||
|
<string name="settings_sync">Sync Settings</string>
|
||||||
|
<string name="settings_sync_auto_on">Auto-Sync: On • %s</string>
|
||||||
|
<string name="settings_sync_auto_off">Auto-Sync: Off</string>
|
||||||
|
<string name="settings_interval_15min">15 min</string>
|
||||||
|
<string name="settings_interval_30min">30 min</string>
|
||||||
|
<string name="settings_interval_60min">60 min</string>
|
||||||
|
<string name="settings_markdown">Markdown Desktop Integration</string>
|
||||||
|
<string name="settings_markdown_auto_on">Auto-Sync: On</string>
|
||||||
|
<string name="settings_markdown_auto_off">Auto-Sync: Off</string>
|
||||||
|
<string name="settings_backup">Backup & Restore</string>
|
||||||
|
<string name="settings_backup_subtitle">Local or server backup</string>
|
||||||
|
<string name="settings_about">About this App</string>
|
||||||
|
<string name="settings_debug">Debug & Diagnostics</string>
|
||||||
|
<string name="settings_debug_logging_on">Logging: On</string>
|
||||||
|
<string name="settings_debug_logging_off">Logging: Off</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- SETTINGS - SERVER -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="server_settings">Server Settings</string>
|
||||||
|
<string name="server_settings_title">Server Settings</string>
|
||||||
|
<string name="server_connection_type">Connection Type</string>
|
||||||
|
<string name="server_connection_http">🏠 Internal (HTTP)</string>
|
||||||
|
<string name="server_connection_https">🌐 External (HTTPS)</string>
|
||||||
|
<string name="server_connection_http_hint">HTTP only for local networks (e.g. 192.168.x.x, 10.x.x.x)</string>
|
||||||
|
<string name="server_connection_https_hint">HTTPS for secure connections over the internet</string>
|
||||||
|
<string name="server_address">Server Address</string>
|
||||||
|
<string name="server_address_hint">e.g. http://192.168.0.188:8080/notes</string>
|
||||||
<string name="server_url">Server URL</string>
|
<string name="server_url">Server URL</string>
|
||||||
<string name="username">Benutzername</string>
|
<string name="username">Username</string>
|
||||||
<string name="password">Passwort</string>
|
<string name="password">Password</string>
|
||||||
<string name="server_status_label">Server-Status:</string>
|
<string name="server_password_show">Show</string>
|
||||||
<string name="server_status_checking">Prüfe…</string>
|
<string name="server_password_hide">Hide</string>
|
||||||
<string name="test_connection">Verbindung testen</string>
|
<string name="server_status_label">Server Status:</string>
|
||||||
<string name="sync_now">Jetzt synchronisieren</string>
|
<string name="server_status_reachable">✅ Reachable</string>
|
||||||
|
<string name="server_status_unreachable">❌ Not reachable</string>
|
||||||
|
<string name="server_status_checking">🔍 Checking…</string>
|
||||||
|
<string name="server_status_not_configured">⚠️ Not configured</string>
|
||||||
|
<string name="server_status_unknown">❓ Unknown</string>
|
||||||
|
<string name="test_connection">Test Connection</string>
|
||||||
|
<string name="sync_now">Sync now</string>
|
||||||
|
|
||||||
<!-- Auto-Sync Settings -->
|
<!-- ============================= -->
|
||||||
<string name="sync_settings">Sync-Einstellungen</string>
|
<!-- SETTINGS - SYNC -->
|
||||||
<string name="auto_sync">Auto-Sync aktiviert</string>
|
<!-- ============================= -->
|
||||||
<string name="sync_status">Sync-Status</string>
|
<string name="sync_settings">Sync Settings</string>
|
||||||
<string name="auto_sync_info">ℹ️ Auto-Sync:\n\n• Prüft alle 30 Min ob Server erreichbar\n• Funktioniert bei jeder WiFi-Verbindung\n• Läuft auch im Hintergrund\n• Minimaler Akkuverbrauch (~0.4%%/Tag)</string>
|
<string name="sync_settings_title">Sync Settings</string>
|
||||||
|
<string name="auto_sync">Auto-Sync enabled</string>
|
||||||
|
<string name="sync_auto_sync_info">🔄 Auto-Sync:\n• Checks every 30 min if server is reachable\n• Works on any WiFi connection\n• Runs in background\n• Minimal battery usage (~0.4%/day)</string>
|
||||||
|
<string name="sync_auto_sync_enabled">Auto-Sync enabled</string>
|
||||||
|
<string name="sync_interval_section">Sync Interval</string>
|
||||||
|
<string name="sync_interval_info">Determines how often the app syncs in the background. Shorter intervals mean more up-to-date data, but use slightly more battery.\n\n⏱️ Note: When your phone is in standby, Android may delay syncs (up to 60 min) to save battery. This is normal and affects all background apps.</string>
|
||||||
|
<string name="sync_interval_15min_title">⚡ Every 15 minutes</string>
|
||||||
|
<string name="sync_interval_15min_subtitle">Fastest sync • ~0.8% battery/day (~23 mAh)</string>
|
||||||
|
<string name="sync_interval_30min_title">✓ Every 30 minutes (Recommended)</string>
|
||||||
|
<string name="sync_interval_30min_subtitle">Balanced ratio • ~0.4% battery/day (~12 mAh)</string>
|
||||||
|
<string name="sync_interval_60min_title">🔋 Every 60 minutes</string>
|
||||||
|
<string name="sync_interval_60min_subtitle">Maximum battery life • ~0.2% battery/day (~6 mAh est.)</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="auto_sync_info">ℹ️ Auto-Sync:\n\n• Checks every 30 min if server is reachable\n• Works on any WiFi connection\n• Runs in background\n• Minimal battery usage (~0.4%%/day)</string>
|
||||||
|
|
||||||
<!-- Backup & Restore -->
|
<!-- ============================= -->
|
||||||
<string name="backup_restore_title">Backup & Wiederherstellung</string>
|
<!-- SETTINGS - MARKDOWN -->
|
||||||
<string name="backup_restore_warning">⚠️ Achtung:\n\nDie Wiederherstellung überschreibt ALLE lokalen Notizen mit den Daten vom Server. Diese Aktion kann nicht rückgängig gemacht werden!</string>
|
<!-- ============================= -->
|
||||||
<string name="restore_from_server">Vom Server wiederherstellen</string>
|
<string name="markdown_settings_title">Markdown Desktop Integration</string>
|
||||||
<string name="restore_confirmation_title">⚠️ Vom Server wiederherstellen?</string>
|
<string name="markdown_dialog_title">Markdown Auto-Sync</string>
|
||||||
<string name="restore_confirmation_message">WARNUNG: Alle lokalen Notizen werden gelöscht und durch die Notizen vom Server ersetzt.\n\nDieser Vorgang kann nicht rückgängig gemacht werden!</string>
|
<string name="markdown_export_complete">✅ Export complete</string>
|
||||||
<string name="restore_button">Wiederherstellen</string>
|
<string name="markdown_export_progress">Exporting %1$d/%2$d notes…</string>
|
||||||
<string name="restore_progress">Stelle Notizen wieder her…</string>
|
<string name="markdown_info">📝 Exports notes additionally as .md files. Mount WebDAV as network drive to edit with VS Code, Typora, or any Markdown editor. JSON sync remains primary format.</string>
|
||||||
<string name="restore_success">✓ %d Notizen wiederhergestellt</string>
|
<string name="markdown_auto_sync_title">Markdown Auto-Sync</string>
|
||||||
<string name="restore_error">Fehler: %s</string>
|
<string name="markdown_auto_sync_subtitle">Automatically syncs notes as .md files (upload + download on each sync)</string>
|
||||||
|
<string name="markdown_manual_sync_info">Manual sync exports all notes as .md files and imports .md files from the server. Useful for one-time sync.</string>
|
||||||
|
<string name="markdown_manual_sync_button">📝 Manual Markdown Sync</string>
|
||||||
|
|
||||||
<!-- Sync Status Banner (v1.3.1) -->
|
<!-- ============================= -->
|
||||||
<string name="sync_status_syncing">Synchronisiere…</string>
|
<!-- SETTINGS - BACKUP -->
|
||||||
<string name="sync_status_completed">Synchronisierung abgeschlossen</string>
|
<!-- ============================= -->
|
||||||
<string name="sync_status_error">Synchronisierung fehlgeschlagen</string>
|
<string name="backup_settings_title">Backup & Restore</string>
|
||||||
<string name="sync_already_running">Synchronisierung läuft bereits</string>
|
<string name="backup_restore_title">Backup & Restore</string>
|
||||||
|
<string name="backup_auto_info">📦 A safety backup is automatically created before each restore.</string>
|
||||||
|
<string name="backup_local_section">Local Backup</string>
|
||||||
|
<string name="backup_create">💾 Create Backup</string>
|
||||||
|
<string name="backup_restore_file">📂 Restore from File</string>
|
||||||
|
<string name="backup_server_section">Server Backup</string>
|
||||||
|
<string name="backup_restore_server">☁️ Restore from Server</string>
|
||||||
|
<string name="backup_restore_dialog_title">⚠️ Restore Backup?</string>
|
||||||
|
<string name="backup_restore_source">Source: %s</string>
|
||||||
|
<string name="backup_restore_source_file">Local File</string>
|
||||||
|
<string name="backup_restore_source_server">WebDAV Server</string>
|
||||||
|
<string name="backup_restore_mode_label">Restore Mode:</string>
|
||||||
|
<string name="backup_mode_merge_title">⚪ Merge (Default)</string>
|
||||||
|
<string name="backup_mode_merge_subtitle">Add new, keep existing</string>
|
||||||
|
<string name="backup_mode_merge_full">⚪ Merge (Default)\n → Add new, keep existing</string>
|
||||||
|
<string name="backup_mode_replace_title">⚪ Replace</string>
|
||||||
|
<string name="backup_mode_replace_subtitle">Delete all & import backup</string>
|
||||||
|
<string name="backup_mode_replace_full">⚪ Replace\n → Delete all & import backup</string>
|
||||||
|
<string name="backup_mode_overwrite_title">⚪ Overwrite duplicates</string>
|
||||||
|
<string name="backup_mode_overwrite_subtitle">Backup wins on conflicts</string>
|
||||||
|
<string name="backup_mode_overwrite_full">⚪ Overwrite duplicates\n → Backup wins on conflicts</string>
|
||||||
|
<string name="backup_restore_info">ℹ️ A safety backup will be automatically created before restoring.</string>
|
||||||
|
<string name="backup_restore_button">Restore</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="backup_restore_warning">⚠️ Warning:\n\nRestoring will overwrite ALL local notes with data from the server. This action cannot be undone!</string>
|
||||||
|
<string name="restore_from_server">Restore from Server</string>
|
||||||
|
<string name="restore_confirmation_title">⚠️ Restore from Server?</string>
|
||||||
|
<string name="restore_confirmation_message">WARNING: All local notes will be deleted and replaced with notes from the server.\n\nThis action cannot be undone!</string>
|
||||||
|
<string name="restore_button">Restore</string>
|
||||||
|
<string name="restore_progress">Restoring notes…</string>
|
||||||
|
<string name="restore_success">✓ %d notes restored</string>
|
||||||
|
<string name="restore_error">Error: %s</string>
|
||||||
|
|
||||||
<!-- Debug/Logging Section (v1.3.2) -->
|
<!-- ============================= -->
|
||||||
<string name="file_logging_privacy_notice">ℹ️ Datenschutz: Logs werden nur lokal auf deinem Gerät gespeichert und niemals an externe Server gesendet. Die Logs enthalten Sync-Aktivitäten zur Fehlerdiagnose. Du kannst sie jederzeit löschen oder exportieren.</string>
|
<!-- SETTINGS - DEBUG -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="debug_settings_title">Debug & Diagnostics</string>
|
||||||
|
<string name="debug_file_logging_title">File Logging</string>
|
||||||
|
<string name="debug_file_logging_subtitle">Save sync logs to file</string>
|
||||||
|
<string name="debug_privacy_info">🔒 Privacy: Logs are only stored locally on your device and are never sent to external servers. Logs contain sync activities for troubleshooting. You can delete or export them at any time.</string>
|
||||||
|
<string name="debug_log_actions_section">Log Actions</string>
|
||||||
|
<string name="debug_export_logs">📤 Export & Share Logs</string>
|
||||||
|
<string name="debug_logs_subject">SimpleNotes Sync Logs</string>
|
||||||
|
<string name="debug_logs_share_via">Share logs via…</string>
|
||||||
|
<string name="debug_delete_logs">🗑️ Delete Logs</string>
|
||||||
|
<string name="debug_delete_logs_title">Delete logs?</string>
|
||||||
|
<string name="debug_delete_logs_message">All saved sync logs will be permanently deleted.</string>
|
||||||
|
<!-- Legacy -->
|
||||||
|
<string name="file_logging_privacy_notice">ℹ️ Privacy: Logs are only stored locally on your device and are never sent to external servers. Logs contain sync activities for troubleshooting. You can delete or export them at any time.</string>
|
||||||
|
|
||||||
<!-- ========================== -->
|
<!-- ============================= -->
|
||||||
<!-- CHECKLIST FEATURE (v1.4.0) -->
|
<!-- SETTINGS - LANGUAGE -->
|
||||||
<!-- ========================== -->
|
<!-- ============================= -->
|
||||||
|
<string name="language_settings_title">Language</string>
|
||||||
|
<string name="language_system_default">System Default</string>
|
||||||
|
<string name="language_english">English</string>
|
||||||
|
<string name="language_german">Deutsch</string>
|
||||||
|
<string name="language_info">ℹ️ Choose your preferred language. The app will restart to apply the change.</string>
|
||||||
|
<string name="language_changed_restart">Language changed. Restarting…</string>
|
||||||
|
|
||||||
<!-- FAB Menu -->
|
<!-- ============================= -->
|
||||||
<string name="create_text_note">Notiz</string>
|
<!-- SETTINGS - ABOUT -->
|
||||||
<string name="create_checklist">Liste</string>
|
<!-- ============================= -->
|
||||||
|
<string name="about_settings_title">About this App</string>
|
||||||
|
<string name="about_app_name">Simple Notes Sync</string>
|
||||||
|
<string name="about_version">Version %1$s (%2$d)</string>
|
||||||
|
<string name="about_links_section">Links</string>
|
||||||
|
<string name="about_github_title">GitHub Repository</string>
|
||||||
|
<string name="about_github_subtitle">Source code, issues & documentation</string>
|
||||||
|
<string name="about_developer_title">Developer</string>
|
||||||
|
<string name="about_developer_subtitle">GitHub Profile: @inventory69</string>
|
||||||
|
<string name="about_license_title">License</string>
|
||||||
|
<string name="about_license_subtitle">MIT License - Open Source</string>
|
||||||
|
<string name="about_privacy_title">🔒 Privacy</string>
|
||||||
|
<string name="about_privacy_text">This app collects no data. All notes are stored only locally on your device and on your own WebDAV server. No telemetry, no ads.</string>
|
||||||
|
|
||||||
<!-- Editor -->
|
<!-- ============================= -->
|
||||||
<string name="new_checklist">Neue Liste</string>
|
<!-- TOAST MESSAGES -->
|
||||||
<string name="edit_checklist">Liste bearbeiten</string>
|
<!-- ============================= -->
|
||||||
<string name="add_item">Element hinzufügen</string>
|
<string name="toast_connection_success">✅ Connection successful!</string>
|
||||||
<string name="item_placeholder">Neues Element…</string>
|
<string name="toast_connection_failed">❌ %s</string>
|
||||||
<string name="reorder_item">Element verschieben</string>
|
<string name="toast_error">❌ Error: %s</string>
|
||||||
<string name="drag_to_reorder">Ziehen zum Sortieren</string>
|
<string name="toast_syncing">🔄 Syncing…</string>
|
||||||
<string name="delete_item">Element löschen</string>
|
<string name="toast_already_synced">✅ Already synced</string>
|
||||||
<string name="note_is_empty">Notiz ist leer</string>
|
<string name="toast_sync_success">✅ %d notes synced</string>
|
||||||
<string name="note_saved">Notiz gespeichert</string>
|
<string name="toast_sync_failed">❌ %s</string>
|
||||||
<string name="note_deleted">Notiz gelöscht</string>
|
<string name="toast_auto_sync_enabled">✅ Auto-Sync enabled</string>
|
||||||
|
<string name="toast_auto_sync_disabled">Auto-Sync disabled</string>
|
||||||
|
<string name="toast_sync_interval">⏱️ Sync interval: %s</string>
|
||||||
|
<string name="toast_sync_interval_15min">15 minutes</string>
|
||||||
|
<string name="toast_sync_interval_30min">30 minutes</string>
|
||||||
|
<string name="toast_sync_interval_60min">60 minutes</string>
|
||||||
|
<string name="toast_configure_server_first">⚠️ Please configure WebDAV server first</string>
|
||||||
|
<string name="toast_markdown_exported">✅ %d notes exported to Markdown</string>
|
||||||
|
<string name="toast_markdown_enabled">📝 Markdown Auto-Sync enabled</string>
|
||||||
|
<string name="toast_markdown_disabled">📝 Markdown Auto-Sync disabled</string>
|
||||||
|
<string name="toast_markdown_syncing">📝 Markdown sync running…</string>
|
||||||
|
<string name="toast_markdown_result">✅ Export: %1$d • Import: %2$d</string>
|
||||||
|
<string name="toast_export_failed">❌ Export failed: %s</string>
|
||||||
|
<string name="toast_backup_success">✅ %s</string>
|
||||||
|
<string name="toast_backup_failed">❌ Backup failed: %s</string>
|
||||||
|
<string name="toast_restore_success">✅ %d notes restored</string>
|
||||||
|
<string name="toast_restore_failed">❌ Restore failed: %s</string>
|
||||||
|
<string name="toast_notifications_enabled">Notifications enabled</string>
|
||||||
|
<string name="toast_notifications_disabled">Notifications disabled. You can enable them in settings.</string>
|
||||||
|
<string name="toast_battery_optimization">Please disable battery optimization manually</string>
|
||||||
|
<string name="toast_logs_deleted">🗑️ Logs deleted</string>
|
||||||
|
<string name="toast_no_logs_to_delete">📭 No logs to delete</string>
|
||||||
|
<string name="toast_logs_delete_error">❌ Error deleting: %s</string>
|
||||||
|
<string name="toast_link_error">❌ Error opening link</string>
|
||||||
|
<string name="toast_file_logging_enabled">📝 File logging enabled</string>
|
||||||
|
<string name="toast_file_logging_disabled">📝 File logging disabled</string>
|
||||||
|
<string name="toast_sync_interval_changed">⏱️ Sync interval changed to %s</string>
|
||||||
|
<string name="version_not_available">Version not available</string>
|
||||||
|
<string name="status_checking">🔍 Checking…</string>
|
||||||
|
<string name="battery_optimization_dialog_message">Please select \'Not optimized\' for Simple Notes.</string>
|
||||||
|
<string name="battery_optimization_dialog_title">Background Synchronization</string>
|
||||||
|
<string name="battery_optimization_dialog_full_message">For the app to sync in the background, battery optimization must be disabled.\n\nPlease select \'Not optimized\' for Simple Notes.</string>
|
||||||
|
<string name="battery_optimization_open_settings">Open Settings</string>
|
||||||
|
<string name="battery_optimization_later">Later</string>
|
||||||
|
<string name="content_description_back">Back</string>
|
||||||
|
<string name="error_invalid_backup_file">Invalid backup file</string>
|
||||||
|
<string name="error_backup_version_unsupported">Backup version not supported (v%1$d requires v%2$d+)</string>
|
||||||
|
<string name="error_backup_empty">Backup contains no notes</string>
|
||||||
|
<string name="error_backup_invalid_notes">Backup contains %d invalid notes</string>
|
||||||
|
<string name="error_backup_corrupt">Backup file corrupt or invalid: %s</string>
|
||||||
|
<string name="error_restore_failed">Restore failed: %s</string>
|
||||||
|
<string name="restore_merge_result">%1$d new notes imported, %2$d skipped</string>
|
||||||
|
<string name="restore_overwrite_result">%1$d new, %2$d overwritten</string>
|
||||||
|
<string name="restore_replace_result">All notes replaced: %d imported</string>
|
||||||
|
|
||||||
<!-- List Preview -->
|
<!-- ============================= -->
|
||||||
<string name="checklist_progress">%1$d/%2$d erledigt</string>
|
<!-- RELATIVE TIME -->
|
||||||
<string name="empty_checklist">Keine Einträge</string>
|
<!-- ============================= -->
|
||||||
|
<string name="time_just_now">Just now</string>
|
||||||
|
<string name="time_minutes_ago">%d min ago</string>
|
||||||
|
<string name="time_hours_ago">%d hours ago</string>
|
||||||
|
<string name="time_days_ago">%d days ago</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- NOTIFICATIONS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<string name="notification_channel_name">Notes Synchronization</string>
|
||||||
|
<string name="notification_channel_desc">Notifications about sync status</string>
|
||||||
|
<string name="notification_sync_success_title">Sync successful</string>
|
||||||
|
<string name="notification_sync_success_message">%d note(s) synchronized</string>
|
||||||
|
<string name="notification_sync_failed_title">Sync failed</string>
|
||||||
|
<string name="notification_sync_progress_title">Syncing…</string>
|
||||||
|
<string name="notification_sync_progress_message">Notes are being synchronized</string>
|
||||||
|
<string name="notification_sync_conflict_title">Sync conflict detected</string>
|
||||||
|
<string name="notification_sync_conflict_message">%d note(s) have conflicts</string>
|
||||||
|
<string name="notification_sync_warning_title">⚠️ Sync Warning</string>
|
||||||
|
<string name="notification_sync_warning_message">Server unreachable for %dh</string>
|
||||||
|
<string name="notification_sync_warning_detail">The WebDAV server has been unreachable for %d hours. Please check your network connection or server settings.</string>
|
||||||
|
<string name="notification_sync_in_progress_title">Synchronization in progress</string>
|
||||||
|
<string name="notification_sync_in_progress_message">Notes are being synchronized…</string>
|
||||||
|
<string name="notification_sync_error_title">Sync Error</string>
|
||||||
|
|
||||||
|
<!-- ============================= -->
|
||||||
|
<!-- PLURALS -->
|
||||||
|
<!-- ============================= -->
|
||||||
|
<plurals name="notes_count">
|
||||||
|
<item quantity="one">%d note</item>
|
||||||
|
<item quantity="other">%d notes</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_deleted_local">
|
||||||
|
<item quantity="one">%d note deleted locally</item>
|
||||||
|
<item quantity="other">%d notes deleted locally</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_deleted_server">
|
||||||
|
<item quantity="one">%d note will be deleted from server</item>
|
||||||
|
<item quantity="other">%d notes will be deleted from server</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
|
<plurals name="notes_synced">
|
||||||
|
<item quantity="one">%d note synced</item>
|
||||||
|
<item quantity="other">%d notes synced</item>
|
||||||
|
</plurals>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
7
android/app/src/main/res/xml/locales_config.xml
Normal file
7
android/app/src/main/res/xml/locales_config.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- Default/Fallback language -->
|
||||||
|
<locale android:name="en" />
|
||||||
|
<!-- Supported languages -->
|
||||||
|
<locale android:name="de" />
|
||||||
|
</locale-config>
|
||||||
Reference in New Issue
Block a user