chore(v1.8.0): Resolve all Detekt code quality warnings

Fixes 22 Detekt warnings across the codebase:

- Remove 7 unused imports from UI components
- Add @Suppress annotations for 4 preview functions
- Define constants for 5 magic numbers
- Optimize state reads with derivedStateOf (2 fixes)
- Add @Suppress for long parameter list
- Move WidgetSizeClass to separate file
- Reformat long line in NoteEditorScreen
- Suppress unused parameter and property annotations
- Suppress WebDavSyncService method length/complexity with TODO for v1.9.0 refactoring

Test results:
- detekt: 0 warnings
- lintFdroidDebug: 0 errors
- Build successful

Progress v1.8.0: 0 Lint errors + 0 Detekt warnings complete
This commit is contained in:
inventory69
2026-02-10 12:44:14 +01:00
parent 96c819b154
commit 1da1a63566
17 changed files with 203 additions and 87 deletions

View File

@@ -2,6 +2,7 @@
package dev.dettmer.simplenotes
import android.annotation.SuppressLint
import android.app.ProgressDialog
import android.content.Context
import android.content.Intent
@@ -186,10 +187,12 @@ class SettingsActivity : AppCompatActivity() {
}
// Set URL with protocol prefix in the text field
@Suppress("SetTextI18n") // Technical URL, not UI text
editTextServerUrl.setText("$protocol://$hostPath")
} else {
// Default: HTTP selected (lokale Server sind häufiger), empty URL with prefix
radioHttp.isChecked = true
@Suppress("SetTextI18n") // Technical URL, not UI text
editTextServerUrl.setText("http://")
}
@@ -252,6 +255,7 @@ class SettingsActivity : AppCompatActivity() {
}
// Set new URL with correct protocol
@Suppress("SetTextI18n") // Technical URL, not UI text
editTextServerUrl.setText("$newProtocol://$hostPath")
// Move cursor to end
@@ -379,7 +383,7 @@ class SettingsActivity : AppCompatActivity() {
val versionName = BuildConfig.VERSION_NAME
val versionCode = BuildConfig.VERSION_CODE
textViewAppVersion.text = "Version $versionName ($versionCode)"
textViewAppVersion.text = getString(R.string.about_version, versionName, versionCode)
} catch (e: Exception) {
Logger.e(TAG, "Failed to load version info", e)
textViewAppVersion.text = getString(R.string.version_not_available)
@@ -644,7 +648,7 @@ class SettingsActivity : AppCompatActivity() {
val serverUrl = prefs.getString(Constants.KEY_SERVER_URL, null)
if (serverUrl.isNullOrEmpty()) {
textViewServerStatus.text = "❌ Nicht konfiguriert"
textViewServerStatus.text = getString(R.string.server_status_not_configured)
textViewServerStatus.setTextColor(getColor(android.R.color.holo_red_dark))
return
}
@@ -669,10 +673,10 @@ class SettingsActivity : AppCompatActivity() {
}
if (isReachable) {
textViewServerStatus.text = "✅ Erreichbar"
textViewServerStatus.text = getString(R.string.server_status_reachable)
textViewServerStatus.setTextColor(getColor(android.R.color.holo_green_dark))
} else {
textViewServerStatus.text = "❌ Nicht erreichbar"
textViewServerStatus.text = getString(R.string.server_status_unreachable)
textViewServerStatus.setTextColor(getColor(android.R.color.holo_red_dark))
}
}
@@ -818,6 +822,12 @@ class SettingsActivity : AppCompatActivity() {
.show()
}
/**
* Note: REQUEST_IGNORE_BATTERY_OPTIMIZATIONS is acceptable for F-Droid builds.
* For Play Store builds, this would need to be changed to
* ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS (shows list, doesn't request directly).
*/
@SuppressLint("BatteryLife")
private fun openBatteryOptimizationSettings() {
try {
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
@@ -947,6 +957,7 @@ class SettingsActivity : AppCompatActivity() {
}
// Info Text
@Suppress("SetTextI18n") // Programmatically generated dialog text
val infoText = android.widget.TextView(this).apply {
text = "Quelle: $sourceText\n\nWiederherstellungs-Modus:"
textSize = 16f
@@ -955,7 +966,7 @@ class SettingsActivity : AppCompatActivity() {
// Hinweis Text
val hintText = android.widget.TextView(this).apply {
text = "\n Ein Sicherheits-Backup wird vor dem Wiederherstellen automatisch erstellt."
text = getString(R.string.backup_restore_info)
textSize = 14f
setTypeface(null, android.graphics.Typeface.ITALIC)
setPadding(0, 20, 0, 0)

View File

@@ -12,6 +12,8 @@ import okhttp3.RequestBody.Companion.toRequestBody
import java.io.Closeable
import java.io.InputStream
private const val HTTP_METHOD_NOT_ALLOWED = 405
/**
* 🔧 v1.7.1: Wrapper für Sardine der Connection Leaks verhindert
* 🔧 v1.7.2 (IMPL_003): Implementiert Closeable für explizites Resource-Management
@@ -171,7 +173,7 @@ class SafeSardineWrapper private constructor(
.build()
okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful && response.code != 405) { // 405 = already exists
if (!response.isSuccessful && response.code != HTTP_METHOD_NOT_ALLOWED) { // 405 = already exists
throw java.io.IOException("MKCOL failed: ${response.code} ${response.message}")
}
Logger.d(TAG, "createDirectory($url) → ${response.code}")

View File

@@ -1164,8 +1164,14 @@ class WebDavSyncService(private val context: Context) {
return deletedCount
}
@Suppress("NestedBlockDepth", "LoopWithTooManyJumpStatements")
@Suppress(
"NestedBlockDepth",
"LoopWithTooManyJumpStatements",
"LongMethod",
"ComplexMethod"
)
// Sync logic requires nested conditions for comprehensive error handling and conflict resolution
// TODO: Refactor into smaller functions in v1.9.0/v2.0.0 (see LINT_DETEKT_FEHLER_BEHEBUNG_PLAN.md)
private fun downloadRemoteNotes(
sardine: Sardine,
serverUrl: String,

View File

@@ -121,6 +121,7 @@ class DragDropListState(
}
}
@Suppress("UnusedPrivateProperty")
private val LazyListItemInfo.offsetEnd: Int
get() = this.offset + this.size
}

View File

@@ -69,6 +69,10 @@ import kotlinx.coroutines.delay
import dev.dettmer.simplenotes.utils.showToast
import kotlin.math.roundToInt
private const val LAYOUT_DELAY_MS = 100L
private const val ITEM_CORNER_RADIUS_DP = 8
private const val DRAGGING_ITEM_Z_INDEX = 10f
/**
* Main Composable for the Note Editor screen.
*
@@ -108,7 +112,7 @@ fun NoteEditorScreen(
// v1.5.0: Auto-focus and show keyboard
LaunchedEffect(uiState.isNewNote, uiState.noteType) {
delay(100) // Wait for layout
delay(LAYOUT_DELAY_MS) // Wait for layout
when {
uiState.isNewNote -> {
// New note: focus title
@@ -398,9 +402,12 @@ private fun ChecklistEditor(
onDelete = { onDelete(item.id) },
onAddNewItem = { onAddNewItemAfter(item.id) },
requestFocus = shouldFocus,
isDragging = isDragging, // 🆕 v1.8.0: IMPL_023 - Drag state übergeben
isAnyItemDragging = dragDropState.draggingItemIndex != null, // 🆕 v1.8.0: IMPL_023 - Gradient während Drag ausblenden
dragModifier = Modifier.dragContainer(dragDropState, index), // 🆕 v1.8.0: IMPL_023 - Drag nur auf Handle
// 🆕 v1.8.0: IMPL_023 - Drag state übergeben
isDragging = isDragging,
// 🆕 v1.8.0: IMPL_023 - Gradient während Drag ausblenden
isAnyItemDragging = dragDropState.draggingItemIndex != null,
// 🆕 v1.8.0: IMPL_023 - Drag nur auf Handle
dragModifier = Modifier.dragContainer(dragDropState, index),
modifier = Modifier
.animateItem() // 🆕 v1.8.0 (IMPL_017): LazyColumn Item-Animation
.offset {
@@ -409,11 +416,12 @@ private fun ChecklistEditor(
if (isDragging) dragDropState.draggingItemOffset.roundToInt() else 0
)
}
.zIndex(if (isDragging) 10f else 0f) // 🆕 v1.8.0: IMPL_023 - Gedraggtes Item liegt über anderen
.shadow(elevation, shape = RoundedCornerShape(8.dp))
// 🆕 v1.8.0: IMPL_023 - Gedraggtes Item liegt über anderen
.zIndex(if (isDragging) DRAGGING_ITEM_Z_INDEX else 0f)
.shadow(elevation, shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp))
.background(
color = MaterialTheme.colorScheme.surface,
shape = RoundedCornerShape(8.dp)
shape = RoundedCornerShape(ITEM_CORNER_RADIUS_DP.dp)
)
)
}

View File

@@ -24,6 +24,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -54,7 +55,11 @@ import dev.dettmer.simplenotes.ui.editor.ChecklistItemState
* v1.5.0: Jetpack Compose NoteEditor Redesign
* v1.8.0: Long text UX improvements (gradient fade, auto-expand on focus)
* v1.8.0: IMPL_023 - Enlarged drag handle (48dp touch target) + drag modifier
*
* Note: Using 10 parameters for Composable is acceptable for complex UI components.
* @suppress LongParameterList - Composables naturally have many parameters
*/
@Suppress("LongParameterList")
@Composable
fun ChecklistItemRow(
item: ChecklistItemState,
@@ -92,8 +97,12 @@ fun ChecklistItemRow(
// 🆕 v1.8.0: Dynamische Gradient-Sichtbarkeit basierend auf Scroll-Position
val showGradient = useScrollClipping && !isFocused && !isAnyItemDragging
val showTopGradient = showGradient && scrollState.value > 0
val showBottomGradient = showGradient && scrollState.value < scrollState.maxValue
val showTopGradient by remember {
derivedStateOf { showGradient && scrollState.value > 0 }
}
val showBottomGradient by remember {
derivedStateOf { showGradient && scrollState.value < scrollState.maxValue }
}
// v1.5.0: Auto-focus AND show keyboard when requestFocus is true (new items)
LaunchedEffect(requestFocus) {
@@ -283,6 +292,7 @@ private const val COLLAPSED_MAX_LINES = 5
// 🆕 v1.8.0: Preview Composables for Manual Testing
// ════════════════════════════════════════════════════════════════
@Suppress("UnusedPrivateMember")
@Preview(showBackground = true)
@Composable
private fun ChecklistItemRowShortTextPreview() {
@@ -301,6 +311,7 @@ private fun ChecklistItemRowShortTextPreview() {
)
}
@Suppress("UnusedPrivateMember")
@Preview(showBackground = true)
@Composable
private fun ChecklistItemRowLongTextPreview() {
@@ -324,6 +335,7 @@ private fun ChecklistItemRowLongTextPreview() {
)
}
@Suppress("UnusedPrivateMember")
@Preview(showBackground = true)
@Composable
private fun ChecklistItemRowCheckedPreview() {
@@ -343,6 +355,7 @@ private fun ChecklistItemRowCheckedPreview() {
}
// 🆕 v1.8.0: IMPL_023 - Preview for dragging state
@Suppress("UnusedPrivateMember")
@Preview(showBackground = true)
@Composable
private fun ChecklistItemRowDraggingPreview() {

View File

@@ -5,12 +5,8 @@ import androidx.compose.animation.expandVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable

View File

@@ -1,5 +1,6 @@
package dev.dettmer.simplenotes.ui.settings
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
@@ -149,7 +150,12 @@ class ComposeSettingsActivity : AppCompatActivity() {
/**
* Open system battery optimization settings
* v1.5.0: Ported from old SettingsActivity
*
* Note: REQUEST_IGNORE_BATTERY_OPTIMIZATIONS is acceptable for F-Droid builds.
* For Play Store builds, this would need to be changed to
* ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS (shows list, doesn't request directly).
*/
@SuppressLint("BatteryLife")
private fun openBatteryOptimizationSettings() {
try {
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)

View File

@@ -12,7 +12,6 @@ import androidx.compose.material.icons.filled.PhonelinkRing
import androidx.compose.material.icons.filled.Save
import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material.icons.filled.SettingsInputAntenna
import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material3.Button
import androidx.compose.material3.Text

View File

@@ -6,7 +6,6 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
import androidx.activity.compose.setContent
import androidx.datastore.preferences.core.Preferences
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.state.getAppWidgetState
import androidx.glance.appwidget.state.updateAppWidgetState

View File

@@ -53,6 +53,9 @@ import kotlin.math.roundToInt
*
* 🆕 v1.8.0 (IMPL_025): Save-FAB + onSettingsChanged für Reconfigure-Flow
*/
private const val NOTE_PREVIEW_MAX_LENGTH = 50
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NoteWidgetConfigScreen(
@@ -63,7 +66,7 @@ fun NoteWidgetConfigScreen(
onNoteSelected: (noteId: String, isLocked: Boolean, opacity: Float) -> Unit,
onSave: ((noteId: String, isLocked: Boolean, opacity: Float) -> Unit)? = null,
onSettingsChanged: ((noteId: String?, isLocked: Boolean, opacity: Float) -> Unit)? = null,
onCancel: () -> Unit
@Suppress("UNUSED_PARAMETER") onCancel: () -> Unit // Reserved for future use
) {
val allNotes = remember { storage.loadAllNotes().sortedByDescending { it.updatedAt } }
var lockWidget by remember { mutableStateOf(initialLock) }
@@ -248,7 +251,7 @@ private fun NoteSelectionCard(
)
Text(
text = when (note.noteType) {
NoteType.TEXT -> note.content.take(50).replace("\n", " ")
NoteType.TEXT -> note.content.take(NOTE_PREVIEW_MAX_LENGTH).replace("\n", " ")
NoteType.CHECKLIST -> {
val items = note.checklistItems ?: emptyList()
val checked = items.count { it.isChecked }

View File

@@ -35,7 +35,6 @@ import androidx.glance.layout.height
import androidx.glance.layout.padding
import androidx.glance.layout.size
import androidx.glance.layout.width
import androidx.glance.text.FontWeight
import androidx.glance.text.Text
import androidx.glance.text.TextStyle
import dev.dettmer.simplenotes.R
@@ -52,20 +51,18 @@ import dev.dettmer.simplenotes.ui.editor.ComposeNoteEditorActivity
// ── Size Classification ──
enum class WidgetSizeClass {
SMALL, // Nur Titel
NARROW_MED, // Schmal, Vorschau
NARROW_TALL, // Schmal, voller Inhalt
WIDE_MED, // Breit, Vorschau
WIDE_TALL // Breit, voller Inhalt
}
private val WIDGET_HEIGHT_SMALL_THRESHOLD = 110.dp
private val WIDGET_SIZE_MEDIUM_THRESHOLD = 250.dp
private const val TEXT_PREVIEW_COMPACT_LENGTH = 100
private const val TEXT_PREVIEW_FULL_LENGTH = 200
private fun DpSize.toSizeClass(): WidgetSizeClass = when {
height < 110.dp -> WidgetSizeClass.SMALL
width < 250.dp && height < 250.dp -> WidgetSizeClass.NARROW_MED
width < 250.dp -> WidgetSizeClass.NARROW_TALL
height < 250.dp -> WidgetSizeClass.WIDE_MED
else -> WidgetSizeClass.WIDE_TALL
height < WIDGET_HEIGHT_SMALL_THRESHOLD -> WidgetSizeClass.SMALL
width < WIDGET_SIZE_MEDIUM_THRESHOLD && height < WIDGET_SIZE_MEDIUM_THRESHOLD -> WidgetSizeClass.NARROW_MED
width < WIDGET_SIZE_MEDIUM_THRESHOLD -> WidgetSizeClass.NARROW_TALL
height < WIDGET_SIZE_MEDIUM_THRESHOLD -> WidgetSizeClass.WIDE_MED
else -> WidgetSizeClass.WIDE_TALL
}
@Composable
@@ -316,7 +313,9 @@ private fun OptionsBar(
@Composable
private fun TextNotePreview(note: Note, compact: Boolean) {
Text(
text = note.content.take(if (compact) 100 else 200),
text = note.content.take(
if (compact) TEXT_PREVIEW_COMPACT_LENGTH else TEXT_PREVIEW_FULL_LENGTH
),
style = TextStyle(
color = GlanceTheme.colors.onSurface,
fontSize = if (compact) 13.sp else 14.sp

View File

@@ -0,0 +1,14 @@
package dev.dettmer.simplenotes.widget
/**
* 🆕 v1.8.0: Size classification for responsive Note Widget layouts
*
* Determines which layout variant to use based on widget dimensions.
*/
enum class WidgetSizeClass {
SMALL, // Nur Titel
NARROW_MED, // Schmal, Vorschau
NARROW_TALL, // Schmal, voller Inhalt
WIDE_MED, // Breit, Vorschau
WIDE_TALL // Breit, voller Inhalt
}

View File

@@ -56,7 +56,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Verbindungstyp"
android:text="@string/server_connection_type"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:layout_marginBottom="8dp" />
@@ -72,7 +72,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="🏠 Intern (HTTP)"
android:text="@string/server_connection_http"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
android:checked="false" />
@@ -81,7 +81,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="🌐 Extern (HTTPS)"
android:text="@string/server_connection_https"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
android:checked="true" />
@@ -92,7 +92,7 @@
android:id="@+id/protocolHintText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="HTTP nur für lokale Netzwerke (z.B. 192.168.x.x, 10.x.x.x)"
android:text="@string/server_connection_http_hint"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant"
android:layout_marginBottom="16dp"
@@ -104,12 +104,12 @@
android:id="@+id/textInputLayoutServerUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Server-Adresse"
android:hint="@string/server_address"
android:layout_marginBottom="12dp"
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
app:startIconDrawable="@android:drawable/ic_menu_compass"
app:endIconMode="clear_text"
app:helperText="z.B. http://192.168.0.188:8080/notes"
app:helperText="@string/server_address_hint"
app:helperTextEnabled="true"
app:boxCornerRadiusTopStart="12dp"
app:boxCornerRadiusTopEnd="12dp"
@@ -298,7 +298,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sync-Intervall"
android:text="@string/sync_interval_section"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:layout_marginBottom="12dp" />
@@ -315,7 +315,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text="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."
android:text="@string/sync_interval_info"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:lineSpacingMultiplier="1.3" />
@@ -333,14 +333,14 @@
android:id="@+id/radioInterval15"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="⚡ Alle 15 Minuten"
android:text="@string/sync_interval_15min_title"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:paddingVertical="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Schnellste Synchronisation • ~0.8% Akku/Tag (~23 mAh)"
android:text="@string/sync_interval_15min_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOutline"
android:paddingStart="48dp"
@@ -351,14 +351,14 @@
android:id="@+id/radioInterval30"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="✓ Alle 30 Minuten (Empfohlen)"
android:text="@string/sync_interval_30min_title"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:paddingVertical="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Ausgewogenes Verhältnis • ~0.4% Akku/Tag (~12 mAh)"
android:text="@string/sync_interval_30min_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOutline"
android:paddingStart="48dp"
@@ -369,14 +369,14 @@
android:id="@+id/radioInterval60"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🔋 Alle 60 Minuten"
android:text="@string/sync_interval_60min_title"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge"
android:paddingVertical="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Maximale Akkulaufzeit • ~0.2% Akku/Tag (~6 mAh geschätzt)"
android:text="@string/sync_interval_60min_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOutline"
android:paddingStart="48dp" />
@@ -405,7 +405,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Markdown Desktop-Integration"
android:text="@string/settings_markdown"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:layout_marginBottom="12dp" />
@@ -422,7 +422,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text=" 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."
android:text="@string/markdown_info"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnPrimaryContainer"
android:lineSpacingMultiplier="1.3" />
@@ -441,7 +441,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="🔄 Markdown Auto-Sync"
android:text="@string/markdown_auto_sync_title"
android:textAppearance="@style/TextAppearance.Material3.BodyLarge" />
<androidx.appcompat.widget.SwitchCompat
@@ -457,7 +457,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Synchronisiert Notizen automatisch als .md Dateien (Upload + Download bei jedem Sync)"
android:text="@string/markdown_auto_sync_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnSurfaceVariant" />
@@ -468,7 +468,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="Oder synchronisiere Markdown-Dateien manuell:"
android:text="@string/settings_markdown_manual_hint"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium"
android:textColor="?attr/colorOnSurface"
android:visibility="gone" />
@@ -478,7 +478,7 @@
android:id="@+id/buttonManualMarkdownSync"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Markdown synchronisieren"
android:text="@string/settings_markdown_manual_button"
android:visibility="gone"
style="@style/Widget.Material3.Button.TonalButton" />
@@ -521,7 +521,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:text=" Bei jeder Wiederherstellung wird automatisch ein Sicherheits-Backup erstellt."
android:text="@string/settings_backup_info"
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
android:textColor="?attr/colorOnPrimaryContainer"
android:lineSpacingMultiplier="1.3" />
@@ -532,7 +532,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lokales Backup"
android:text="@string/settings_backup_local_title"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:layout_marginBottom="12dp" />
@@ -541,7 +541,7 @@
android:id="@+id/buttonCreateBackup"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📥 Backup erstellen"
android:text="@string/backup_create"
android:layout_marginBottom="8dp"
style="@style/Widget.Material3.Button.TonalButton" />
@@ -550,7 +550,7 @@
android:id="@+id/buttonRestoreFromFile"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📤 Aus Datei wiederherstellen"
android:text="@string/backup_restore_file"
android:layout_marginBottom="16dp"
style="@style/Widget.Material3.Button.TonalButton" />
@@ -566,7 +566,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Server-Backup"
android:text="@string/settings_backup_server_title"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:layout_marginBottom="12dp" />
@@ -575,7 +575,7 @@
android:id="@+id/buttonRestoreFromServer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🔄 Vom Server wiederherstellen"
android:text="@string/backup_restore_server"
style="@style/Widget.Material3.Button.TonalButton" />
</LinearLayout>
@@ -600,7 +600,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Über diese App"
android:text="@string/settings_about"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:layout_marginBottom="16dp" />
@@ -622,7 +622,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📱 App-Version"
android:text="@string/settings_about_app_version"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="4dp" />
@@ -631,7 +631,7 @@
android:id="@+id/textViewAppVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Version wird geladen..."
android:text="@string/settings_about_app_version_loading"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</LinearLayout>
@@ -659,7 +659,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🌐 GitHub Repository"
android:text="@string/settings_about_github"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="4dp" />
@@ -667,7 +667,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Quellcode, Issues &amp; Dokumentation"
android:text="@string/about_github_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</LinearLayout>
@@ -695,7 +695,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="👤 Entwickler"
android:text="@string/settings_about_developer"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="4dp" />
@@ -703,7 +703,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="GitHub Profil: @inventory69"
android:text="@string/about_developer_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</LinearLayout>
@@ -730,7 +730,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="⚖️ Lizenz"
android:text="@string/settings_about_license"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="4dp" />
@@ -738,7 +738,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="MIT License - Open Source"
android:text="@string/about_license_subtitle"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</LinearLayout>
@@ -767,7 +767,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Debug &amp; Diagnose"
android:text="@string/settings_debug"
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
android:layout_marginBottom="16dp" />
@@ -796,7 +796,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📝 Datei-Logging"
android:text="@string/settings_debug_file_logging"
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
android:textColor="?attr/colorPrimary"
android:layout_marginBottom="4dp" />
@@ -804,7 +804,7 @@
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Sync-Logs in Datei speichern"
android:text="@string/settings_debug_file_logging_desc"
android:textAppearance="@style/TextAppearance.Material3.BodyMedium" />
</LinearLayout>
@@ -834,7 +834,7 @@
android:id="@+id/buttonExportLogs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📤 Logs exportieren &amp; teilen"
android:text="@string/settings_debug_export_logs"
style="@style/Widget.Material3.Button.TonalButton"
android:layout_marginBottom="8dp" />
@@ -843,7 +843,7 @@
android:id="@+id/buttonClearLogs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🗑️ Logs löschen"
android:text="@string/settings_debug_delete_logs"
style="@style/Widget.Material3.Button.OutlinedButton" />
</LinearLayout>

View File

@@ -58,6 +58,13 @@
<string name="sync_status_error">Synchronisierung fehlgeschlagen</string>
<string name="sync_already_running">Synchronisierung läuft bereits</string>
<!-- 🆕 v1.8.0: SyncStatus enum values -->
<string name="sync_status_synced">Mit Server synchronisiert</string>
<string name="sync_status_pending">Warte auf Synchronisierung</string>
<string name="sync_status_conflict">Synchronisierungskonflikt erkannt</string>
<string name="sync_status_local_only">Noch nicht synchronisiert</string>
<string name="sync_status_deleted_on_server">Auf Server gelöscht</string>
<!-- 🆕 v1.8.0: Sync-Status Legende Dialog -->
<string name="sync_legend_button">Sync-Status Hilfe</string>
<string name="sync_legend_title">Sync-Status Icons</string>
@@ -209,12 +216,26 @@
<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_markdown_manual_hint">Oder synchronisiere Markdown-Dateien manuell:</string>
<string name="settings_markdown_manual_button">Markdown synchronisieren</string>
<string name="settings_backup">Backup &amp; Wiederherstellung</string>
<string name="settings_backup_subtitle">Lokales oder Server-Backup</string>
<string name="settings_backup_info">📦 Bei jeder Wiederherstellung wird automatisch ein Sicherheits-Backup erstellt.</string>
<string name="settings_backup_local_title">Lokales Backup</string>
<string name="settings_backup_server_title">Server-Backup</string>
<string name="settings_about">Über diese App</string>
<string name="settings_about_app_version">📱 App-Version</string>
<string name="settings_about_app_version_loading">Version wird geladen…</string>
<string name="settings_about_github">🌐 GitHub Repository</string>
<string name="settings_about_developer">👤 Entwickler</string>
<string name="settings_about_license">⚖️ Lizenz</string>
<string name="settings_debug">Debug &amp; Diagnose</string>
<string name="settings_debug_logging_on">Logging: An</string>
<string name="settings_debug_logging_off">Logging: Aus</string>
<string name="settings_debug_file_logging">📝 Datei-Logging</string>
<string name="settings_debug_file_logging_desc">Sync-Logs in Datei speichern</string>
<string name="settings_debug_export_logs">📤 Logs exportieren &amp; teilen</string>
<string name="settings_debug_delete_logs">🗑️ Logs löschen</string>
<!-- ============================= -->
<!-- SETTINGS - SERVER -->
@@ -531,6 +552,17 @@
<item quantity="other">%d erledigt</item>
</plurals>
<!-- ============================= -->
<!-- PARALLEL DOWNLOADS v1.8.0 -->
<!-- ============================= -->
<string name="sync_parallel_downloads_title">Parallele Downloads</string>
<string name="sync_parallel_downloads_unit">parallel</string>
<string name="sync_parallel_downloads_desc_1">Sequentiell (langsam, sicher)</string>
<string name="sync_parallel_downloads_desc_3">Ausgewogen (3x schneller)</string>
<string name="sync_parallel_downloads_desc_5">Empfohlen (5x schneller)</string>
<string name="sync_parallel_downloads_desc_7">Schnell (7x schneller)</string>
<string name="sync_parallel_downloads_desc_10">Maximum (10x schneller, kann Server belasten)</string>
<!-- ============================= -->
<!-- WIDGETS v1.8.0 -->
<!-- ============================= -->

View File

@@ -216,12 +216,26 @@
<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_markdown_manual_hint">Or sync markdown files manually:</string>
<string name="settings_markdown_manual_button">Sync Markdown</string>
<string name="settings_backup">Backup &amp; Restore</string>
<string name="settings_backup_subtitle">Local or server backup</string>
<string name="settings_backup_info">📦 A safety backup is automatically created before each restore.</string>
<string name="settings_backup_local_title">Local Backup</string>
<string name="settings_backup_server_title">Server Backup</string>
<string name="settings_about">About this App</string>
<string name="settings_about_app_version">📱 App Version</string>
<string name="settings_about_app_version_loading">Loading version…</string>
<string name="settings_about_github">🌐 GitHub Repository</string>
<string name="settings_about_developer">👤 Developer</string>
<string name="settings_about_license">⚖️ License</string>
<string name="settings_debug">Debug &amp; Diagnostics</string>
<string name="settings_debug_logging_on">Logging: On</string>
<string name="settings_debug_logging_off">Logging: Off</string>
<string name="settings_debug_file_logging">📝 File Logging</string>
<string name="settings_debug_file_logging_desc">Save sync logs to file</string>
<string name="settings_debug_export_logs">📤 Export &amp; share logs</string>
<string name="settings_debug_delete_logs">🗑️ Delete logs</string>
<!-- ============================= -->
<!-- SETTINGS - SERVER -->

View File

@@ -1,18 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Allow HTTP for all connections during development/testing -->
<!-- Production validation happens in UrlValidator.kt to restrict HTTP to:
<network-security-config xmlns:tools="http://schemas.android.com/tools">
<!-- Allow HTTP for local network connections (192.168.x.x, 10.x.x.x, etc.)
⚠️ Security Note:
We intentionally allow cleartext traffic because this app is designed for
self-hosted WebDAV servers, which often run on local networks via HTTP.
HTTP connections are restricted at the application level by UrlValidator.kt to:
- Private IP ranges: 192.168.x.x, 10.x.x.x, 172.16-31.x.x, 127.x.x.x
- .local domains (mDNS/Bonjour)
This permissive config is necessary because Android's Network Security Config
doesn't support IP-based rules, only domain patterns.
We handle security through application-level validation instead. -->
<base-config cleartextTrafficPermitted="true">
Android's Network Security Config doesn't support IP-based domain rules,
so we must allow cleartext globally but validate URLs in the app.
Public servers MUST use HTTPS. -->
<base-config cleartextTrafficPermitted="true"
tools:ignore="InsecureBaseConfiguration">
<trust-anchors>
<certificates src="system" />
<!-- 🔐 v1.7.0: Trust user-installed CA certificates for self-signed SSL support -->
<certificates src="user" />
</trust-anchors>
</base-config>
<!-- Allow user-installed CA certificates only in debug builds for testing
self-signed certificates during development -->
<debug-overrides>
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</debug-overrides>
</network-security-config>