From b16dafa3e8e86f22fe5d2ad399c9352b504096ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Tue, 26 May 2026 13:42:54 +0200 Subject: [PATCH 1/4] refactor(build): change detekt to Gradle plugin --- build-plugin/plugin/build.gradle.kts | 4 + .../gradle/plugin/ProjectConfig.kt | 12 ++ .../plugin/quality/detekt/DetektPlugin.kt | 103 ++++++++++++++++++ ...thunderbird.app.android.compose.gradle.kts | 2 +- .../kotlin/thunderbird.app.android.gradle.kts | 2 +- .../kotlin/thunderbird.app.jvm.gradle.kts | 2 +- ...derbird.library.android.compose.gradle.kts | 2 +- .../thunderbird.library.android.gradle.kts | 2 +- .../kotlin/thunderbird.library.jvm.gradle.kts | 2 +- ...thunderbird.library.kmp.compose.gradle.kts | 2 +- .../kotlin/thunderbird.library.kmp.gradle.kts | 2 +- ...hunderbird.quality.detekt.typed.gradle.kts | 47 -------- gradle/libs.versions.toml | 1 + 13 files changed, 128 insertions(+), 55 deletions(-) create mode 100644 build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/ProjectConfig.kt create mode 100644 build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt delete mode 100644 build-plugin/src/main/kotlin/thunderbird.quality.detekt.typed.gradle.kts diff --git a/build-plugin/plugin/build.gradle.kts b/build-plugin/plugin/build.gradle.kts index e895becb5b2..09c5c668121 100644 --- a/build-plugin/plugin/build.gradle.kts +++ b/build-plugin/plugin/build.gradle.kts @@ -67,6 +67,10 @@ gradlePlugin { id = "net.thunderbird.gradle.plugin.quality.coverage" implementationClass = "net.thunderbird.gradle.plugin.quality.coverage.CodeCoveragePlugin" } + register("QualityDetekt") { + id = "net.thunderbird.gradle.plugin.quality.detekt" + implementationClass = "net.thunderbird.gradle.plugin.quality.detekt.DetektPlugin" + } } } diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/ProjectConfig.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/ProjectConfig.kt new file mode 100644 index 00000000000..0679e1f2d81 --- /dev/null +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/ProjectConfig.kt @@ -0,0 +1,12 @@ +package net.thunderbird.gradle.plugin + +import org.gradle.api.JavaVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +object ProjectConfig { + + object Compiler { + val javaCompatibility = JavaVersion.VERSION_11 + val jvmTarget = JvmTarget.JVM_11 + } +} diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt new file mode 100644 index 00000000000..2a5a803750b --- /dev/null +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt @@ -0,0 +1,103 @@ +package net.thunderbird.gradle.plugin.quality.detekt + +import io.gitlab.arturbosch.detekt.Detekt +import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask +import io.gitlab.arturbosch.detekt.extensions.DetektExtension +import net.thunderbird.gradle.plugin.ProjectConfig +import net.thunderbird.gradle.plugin.libs +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.withType + +/** + * Detekt plugin configuration. + * + * Applies the Detekt plugin, sets up configuration, and defines tasks for static code analysis. + */ +class DetektPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("io.gitlab.arturbosch.detekt") + + // Access libs extension lazily since it might not be available yet when the plugin is applied + // (especially in precompiled script plugins). Could be removed once all precompiled script plugins + // are migrated to the new plugin model. + afterEvaluate { + dependencies { + add("detektPlugins", libs.detekt.plugin.compose) + } + } + + if (this == rootProject) { + configureRootDetektTasks() + } else { + configureDetekt() + configureDetektTasks() + } + } + } + + private fun Project.configureDetekt() { + extensions.configure("detekt") { + config.setFrom(project.rootProject.files("config/detekt/detekt.yml")) + + val name = project.path.replace(":", "-").replace("/", "-") + baseline = project.rootProject.file("config/detekt/detekt-baseline$name.xml") + + ignoredBuildTypes = listOf("release") + } + } + + private fun Project.configureDetektTasks() { + with(tasks) { + withType().configureEach { + jvmTarget = ProjectConfig.Compiler.jvmTarget.target + + exclude(defaultExcludes) + + reports { + html.required.set(true) + sarif.required.set(true) + xml.required.set(true) + } + + tasks.getByName("build").dependsOn(this) + } + + withType().configureEach { + jvmTarget = ProjectConfig.Compiler.jvmTarget.target + + exclude(defaultExcludes) + } + + register("detektAll") { + group = "verification" + description = "Runs detekt on this project" + + dependsOn(tasks.withType()) + } + } + } + + private fun Project.configureRootDetektTasks() { + with(tasks) { + register("detektAll") { + group = "verification" + description = "Runs detekt on the whole project" + + allprojects { + this@register.dependsOn(tasks.withType()) + } + } + } + } +} + +private val defaultExcludes = listOf( + "**/.gradle/**", + "**/.idea/**", + "**/build/**", + ".github/**", + "gradle/**", +) diff --git a/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts index fd12913822d..0e581c99c45 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts @@ -1,8 +1,8 @@ plugins { id("thunderbird.app.android") id("org.jetbrains.kotlin.plugin.compose") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts index 676c5be246a..6f8462a0174 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.application") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts index 54561b000e3..773855ec1a4 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts @@ -1,8 +1,8 @@ plugins { id("application") id("org.jetbrains.kotlin.jvm") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts index 5ea33ea1539..2627d6daa05 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts @@ -4,8 +4,8 @@ plugins { id("thunderbird.library.android") id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.kotlin.plugin.serialization") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts index 5a866e83087..bbd0670fcd4 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts @@ -1,7 +1,7 @@ plugins { id("com.android.library") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts index 3dc20671ae6..8fa1cf77b13 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts @@ -3,8 +3,8 @@ import org.gradle.jvm.tasks.Jar plugins { `java-library` id("org.jetbrains.kotlin.jvm") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts index 2bb016f2495..dd7050f6318 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts @@ -4,8 +4,8 @@ plugins { id("org.jetbrains.kotlin.multiplatform") id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.kotlin.plugin.serialization") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts index 0262b32fd81..c86f38da36b 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts @@ -2,8 +2,8 @@ plugins { id("com.android.kotlin.multiplatform.library") id("org.jetbrains.kotlin.multiplatform") id("org.jetbrains.kotlin.plugin.serialization") - id("thunderbird.quality.detekt.typed") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.detekt") id("thunderbird.quality.spotless") } diff --git a/build-plugin/src/main/kotlin/thunderbird.quality.detekt.typed.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.quality.detekt.typed.gradle.kts deleted file mode 100644 index 73dd76fe4d5..00000000000 --- a/build-plugin/src/main/kotlin/thunderbird.quality.detekt.typed.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -import io.gitlab.arturbosch.detekt.Detekt -import io.gitlab.arturbosch.detekt.DetektCreateBaselineTask -import io.gitlab.arturbosch.detekt.extensions.DetektExtension -import org.gradle.kotlin.dsl.configure -import org.gradle.kotlin.dsl.withType - -plugins { - id("io.gitlab.arturbosch.detekt") -} - -configure { - config.setFrom(project.rootProject.files("config/detekt/detekt.yml")) - val name = project.path.replace(":", "-").replace("/", "-") - baseline = project.rootProject.file("config/detekt/detekt-baseline$name.xml") - - ignoredBuildTypes = listOf("release") -} - -tasks.withType().configureEach { - jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString() - - exclude(defaultExcludes) - - reports { - html.required.set(true) - sarif.required.set(true) - xml.required.set(true) - } -} - -tasks.withType().configureEach { - jvmTarget = ThunderbirdProjectConfig.Compiler.javaCompatibility.toString() - - exclude(defaultExcludes) -} - -dependencies { - detektPlugins(libs.detekt.plugin.compose) -} - -val defaultExcludes = listOf( - "**/.gradle/**", - "**/.idea/**", - "**/build/**", - ".github/**", - "gradle/**", -) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 33ed324754b..68ef7407bcd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -149,6 +149,7 @@ dev-mokkery = { id = "dev.mokkery", version.ref = "mokkery" } tb-app-badging = { id = "net.thunderbird.gradle.plugin.app.badging" } tb-app-versioning = { id = "net.thunderbird.gradle.plugin.app.versioning" } tb-quality-code-coverage = { id = "net.thunderbird.gradle.plugin.quality.coverage" } +tb-quality-detekt = { id = "net.thunderbird.gradle.plugin.quality.detekt" } [libraries] android-billing = { module = "com.android.billingclient:billing", version.ref = "androidBilling" } From a911b9c6c351e811c6c9d3e96478a436e2b42f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Tue, 26 May 2026 16:53:24 +0200 Subject: [PATCH 2/4] refactor(build): change spotless to Gradle plugin --- build-plugin/plugin/build.gradle.kts | 4 + .../quality/spotless}/SpotlessExtension.kt | 1 + .../plugin/quality/spotless/SpotlessPlugin.kt | 113 ++++++++++++++++++ ...thunderbird.app.android.compose.gradle.kts | 2 +- .../kotlin/thunderbird.app.android.gradle.kts | 2 +- .../kotlin/thunderbird.app.jvm.gradle.kts | 2 +- ...derbird.library.android.compose.gradle.kts | 2 +- .../thunderbird.library.android.gradle.kts | 2 +- .../kotlin/thunderbird.library.jvm.gradle.kts | 2 +- ...thunderbird.library.kmp.compose.gradle.kts | 2 +- .../kotlin/thunderbird.library.kmp.gradle.kts | 2 +- .../thunderbird.quality.spotless.gradle.kts | 48 -------- ...underbird.quality.spotless.root.gradle.kts | 50 -------- build.gradle.kts | 2 +- gradle/libs.versions.toml | 1 + 15 files changed, 128 insertions(+), 107 deletions(-) rename build-plugin/{src/main/kotlin => plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless}/SpotlessExtension.kt (90%) create mode 100644 build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessPlugin.kt delete mode 100644 build-plugin/src/main/kotlin/thunderbird.quality.spotless.gradle.kts delete mode 100644 build-plugin/src/main/kotlin/thunderbird.quality.spotless.root.gradle.kts diff --git a/build-plugin/plugin/build.gradle.kts b/build-plugin/plugin/build.gradle.kts index 09c5c668121..49f33b914ff 100644 --- a/build-plugin/plugin/build.gradle.kts +++ b/build-plugin/plugin/build.gradle.kts @@ -71,6 +71,10 @@ gradlePlugin { id = "net.thunderbird.gradle.plugin.quality.detekt" implementationClass = "net.thunderbird.gradle.plugin.quality.detekt.DetektPlugin" } + register("QualitySpotless") { + id = "net.thunderbird.gradle.plugin.quality.spotless" + implementationClass = "net.thunderbird.gradle.plugin.quality.spotless.SpotlessPlugin" + } } } diff --git a/build-plugin/src/main/kotlin/SpotlessExtension.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessExtension.kt similarity index 90% rename from build-plugin/src/main/kotlin/SpotlessExtension.kt rename to build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessExtension.kt index 06e68209449..84c1e6384d5 100644 --- a/build-plugin/src/main/kotlin/SpotlessExtension.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessExtension.kt @@ -1,3 +1,4 @@ +package net.thunderbird.gradle.plugin.quality.spotless val kotlinEditorConfigOverride = mapOf( "ktlint_code_style" to "intellij_idea", diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessPlugin.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessPlugin.kt new file mode 100644 index 00000000000..f6f9562a962 --- /dev/null +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/spotless/SpotlessPlugin.kt @@ -0,0 +1,113 @@ +package net.thunderbird.gradle.plugin.quality.spotless + +import com.diffplug.gradle.spotless.SpotlessExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure + +/** + * A Gradle plugin to configure Spotless code formatting for Kotlin, Kotlin Gradle scripts, Markdown, + * and miscellaneous files like .gitignore. + */ +class SpotlessPlugin : Plugin { + override fun apply(target: Project) { + with(target) { + pluginManager.apply("com.diffplug.spotless") + + if (this == rootProject) { + configureSpotlessRoot() + } else { + configureSpotless() + } + } + } + + private fun Project.configureSpotless() { + extensions.configure { + kotlin { + target( + "src/*/kotlin/*.kt", + "src/*/kotlin/**/*.kt", + ) + + ktlint() + .setEditorConfigPath("${rootProject.projectDir}/.editorconfig") + .editorConfigOverride(kotlinEditorConfigOverride) + } + + kotlinGradle { + target( + "*.gradle.kts", + ) + + ktlint() + .setEditorConfigPath("${rootProject.projectDir}/.editorconfig") + .editorConfigOverride( + mapOf( + "ktlint_code_style" to "intellij_idea", + "ktlint_standard_function-expression-body" to "disabled", + "ktlint_standard_function-signature" to "disabled", + ), + ) + } + + flexmark { + target( + "*.md", + ) + flexmark() + } + + format("misc") { + target(".gitignore") + trimTrailingWhitespace() + } + } + } + + private fun Project.configureSpotlessRoot() { + extensions.configure { + kotlin { + target( + "build-plugin/plugin/src/*/kotlin/*.kt", + "build-plugin/plugin/src/*/kotlin/**/*.kt", + ) + ktlint() + .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") + .editorConfigOverride(kotlinEditorConfigOverride) + } + + kotlinGradle { + target( + "*.gradle.kts", + "build-plugin/*.gradle.kts", + "build-plugin/plugin/*.gradle.kts", + ) + + ktlint() + .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") + .editorConfigOverride( + mapOf( + "ktlint_code_style" to "intellij_idea", + "ktlint_standard_function-expression-body" to "disabled", + "ktlint_standard_function-signature" to "disabled", + ), + ) + } + + flexmark { + target( + "*.md", + "docs/*.md", + "docs/**/*.md", + ) + flexmark() + } + + format("misc") { + target(".gitignore") + trimTrailingWhitespace() + } + } + } +} diff --git a/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts index 0e581c99c45..a8937ce6a2a 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.android.compose.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.jetbrains.kotlin.plugin.compose") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } android { diff --git a/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts index 6f8462a0174..9aabfd6fb30 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.android.gradle.kts @@ -2,7 +2,7 @@ plugins { id("com.android.application") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } android { diff --git a/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts index 773855ec1a4..b6a901759e9 100644 --- a/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.app.jvm.gradle.kts @@ -3,7 +3,7 @@ plugins { id("org.jetbrains.kotlin.jvm") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } java { diff --git a/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts index 2627d6daa05..8c4c506063e 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.android.compose.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } androidComponents { diff --git a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts index bbd0670fcd4..f2c5508f75b 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.android.gradle.kts @@ -2,7 +2,7 @@ plugins { id("com.android.library") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } android { diff --git a/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts index 8fa1cf77b13..beaeb7df809 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.jvm.gradle.kts @@ -5,7 +5,7 @@ plugins { id("org.jetbrains.kotlin.jvm") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } java { diff --git a/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts index dd7050f6318..b7478aad2c8 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.kmp.compose.gradle.kts @@ -6,7 +6,7 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } kotlin { diff --git a/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts index c86f38da36b..ad841a9fa64 100644 --- a/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts +++ b/build-plugin/src/main/kotlin/thunderbird.library.kmp.gradle.kts @@ -4,7 +4,7 @@ plugins { id("org.jetbrains.kotlin.plugin.serialization") id("net.thunderbird.gradle.plugin.quality.coverage") id("net.thunderbird.gradle.plugin.quality.detekt") - id("thunderbird.quality.spotless") + id("net.thunderbird.gradle.plugin.quality.spotless") } kotlin { diff --git a/build-plugin/src/main/kotlin/thunderbird.quality.spotless.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.quality.spotless.gradle.kts deleted file mode 100644 index e7d02047269..00000000000 --- a/build-plugin/src/main/kotlin/thunderbird.quality.spotless.gradle.kts +++ /dev/null @@ -1,48 +0,0 @@ -import com.diffplug.gradle.spotless.SpotlessExtension - -plugins { - id("com.diffplug.spotless") -} - -configure { - kotlin { - target( - "src/*/java/*.kt", - "src/*/kotlin/*.kt", - "src/*/java/**/*.kt", - "src/*/kotlin/**/*.kt", - ) - - ktlint(libs.versions.ktlint.get()) - .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") - .editorConfigOverride(kotlinEditorConfigOverride) - } - - kotlinGradle { - target( - "*.gradle.kts", - ) - - ktlint(libs.versions.ktlint.get()) - .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") - .editorConfigOverride( - mapOf( - "ktlint_code_style" to "intellij_idea", - "ktlint_standard_function-expression-body" to "disabled", - "ktlint_standard_function-signature" to "disabled", - ), - ) - } - - flexmark { - target( - "*.md", - ) - flexmark() - } - - format("misc") { - target(".gitignore") - trimTrailingWhitespace() - } -} diff --git a/build-plugin/src/main/kotlin/thunderbird.quality.spotless.root.gradle.kts b/build-plugin/src/main/kotlin/thunderbird.quality.spotless.root.gradle.kts deleted file mode 100644 index dde36c7c115..00000000000 --- a/build-plugin/src/main/kotlin/thunderbird.quality.spotless.root.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -import com.diffplug.gradle.spotless.SpotlessExtension - -plugins { - id("com.diffplug.spotless") -} - -configure { - kotlin { - target( - "build-plugin/src/*/kotlin/*.kt", - "build-plugin/src/*/kotlin/**/*.kt", - ) - ktlint(libs.versions.ktlint.get()) - .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") - .editorConfigOverride(kotlinEditorConfigOverride) - } - - kotlinGradle { - target( - "*.gradle.kts", - "build-plugin/*.gradle.kts", - "build-plugin/src/*/kotlin/*.kts", - "build-plugin/src/*/kotlin/**/*.kts", - ) - - ktlint(libs.versions.ktlint.get()) - .setEditorConfigPath("${project.rootProject.projectDir}/.editorconfig") - .editorConfigOverride( - mapOf( - "ktlint_code_style" to "intellij_idea", - "ktlint_standard_function-expression-body" to "disabled", - "ktlint_standard_function-signature" to "disabled", - ), - ) - } - - flexmark { - target( - "*.md", - "docs/*.md", - "docs/**/*.md", - ) - flexmark() - } - - format("misc") { - target(".gitignore") - trimTrailingWhitespace() - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 5ff01de17e0..3d909220988 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -13,8 +13,8 @@ plugins { alias(libs.plugins.jetbrains.compose) apply false id("thunderbird.dependency.check") - id("thunderbird.quality.spotless.root") id("net.thunderbird.gradle.plugin.quality.coverage") + id("net.thunderbird.gradle.plugin.quality.spotless") } allprojects { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 68ef7407bcd..2b8013be87b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -150,6 +150,7 @@ tb-app-badging = { id = "net.thunderbird.gradle.plugin.app.badging" } tb-app-versioning = { id = "net.thunderbird.gradle.plugin.app.versioning" } tb-quality-code-coverage = { id = "net.thunderbird.gradle.plugin.quality.coverage" } tb-quality-detekt = { id = "net.thunderbird.gradle.plugin.quality.detekt" } +tb-quality-spotless = { id = "net.thunderbird.gradle.plugin.quality.spotless" } [libraries] android-billing = { module = "com.android.billingclient:billing", version.ref = "androidBilling" } From 072ae3fcc7afb919db8cc5fc0bc60ee503ff2239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Tue, 26 May 2026 18:42:18 +0200 Subject: [PATCH 3/4] fix: detektAll findings --- .../plugin/quality/detekt/DetektPlugin.kt | 9 ++ .../cli/weblate/ComponentConfigDiff.kt | 14 ++- .../debug/StatePrettyPrinterVocabulary.kt | 1 + .../core/configstore/ConfigDefinition.kt | 12 ++- .../backend/DefaultDataStoreConfigBackend.kt | 1 + .../core/file/command/CopyCommand.kt | 92 +++++++++++++------ .../file/command/CreateDirectoriesCommand.kt | 3 +- .../core/file/JvmFileSystemManager.kt | 3 +- .../net/thunderbird/core/logging/LogLevel.kt | 1 + .../thunderbird/core/logging/legacy/Log.kt | 5 +- .../interaction/InteractionSettings.kt | 2 +- .../assertk/assertions/ValueExtensions.kt | 1 + 12 files changed, 105 insertions(+), 39 deletions(-) diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt index 2a5a803750b..0ef3f57d2a9 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/detekt/DetektPlugin.kt @@ -52,6 +52,10 @@ class DetektPlugin : Plugin { private fun Project.configureDetektTasks() { with(tasks) { withType().configureEach { + if (name.contains("androidHostTest", ignoreCase = true)) { + enabled = false + } + jvmTarget = ProjectConfig.Compiler.jvmTarget.target exclude(defaultExcludes) @@ -66,6 +70,10 @@ class DetektPlugin : Plugin { } withType().configureEach { + if (name.contains("androidHostTest", ignoreCase = true)) { + enabled = false + } + jvmTarget = ProjectConfig.Compiler.jvmTarget.target exclude(defaultExcludes) @@ -98,6 +106,7 @@ private val defaultExcludes = listOf( "**/.gradle/**", "**/.idea/**", "**/build/**", + "**/generated/**", ".github/**", "gradle/**", ) diff --git a/cli/weblate-cli/src/main/kotlin/net/thunderbird/cli/weblate/ComponentConfigDiff.kt b/cli/weblate-cli/src/main/kotlin/net/thunderbird/cli/weblate/ComponentConfigDiff.kt index d0a51c21fd4..ef05bdb7674 100644 --- a/cli/weblate-cli/src/main/kotlin/net/thunderbird/cli/weblate/ComponentConfigDiff.kt +++ b/cli/weblate-cli/src/main/kotlin/net/thunderbird/cli/weblate/ComponentConfigDiff.kt @@ -97,7 +97,12 @@ private class SetField( val actualValue = selector(actual) return if (expectedValue.toSet() != actualValue.toSet()) { - listDiff(name, expectedValue, actualValue, indentLevel) + listDiff( + name = name, + expected = expectedValue, + actual = actualValue, + indentLevel = indentLevel, + ) } else { null } @@ -142,7 +147,12 @@ private class MultilineField( val actualValue = selector(actual) return if (expectedValue != actualValue) { - multilineDiff(name, expectedValue, actualValue, indentLevel) + multilineDiff( + name = name, + expected = expectedValue, + actual = actualValue, + indentLevel = indentLevel, + ) } else { null } diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StatePrettyPrinterVocabulary.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StatePrettyPrinterVocabulary.kt index bff6530cfcb..a7e4038bcde 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StatePrettyPrinterVocabulary.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StatePrettyPrinterVocabulary.kt @@ -9,6 +9,7 @@ import net.thunderbird.core.common.state.debug.extension.prependIndent * Provides constants and formatting functions used by [StatePrettyPrinter] to produce * human-readable representations of state machine transitions and history dumps. */ +@Suppress("TooManyFunctions") internal object StatePrettyPrinterVocabulary { const val STATE_CHANGE_MARKER = "->" const val STATE_NO_CHANGE_MARKER = "⟳" diff --git a/core/configstore/api/src/commonMain/kotlin/net/thunderbird/core/configstore/ConfigDefinition.kt b/core/configstore/api/src/commonMain/kotlin/net/thunderbird/core/configstore/ConfigDefinition.kt index 6ba084c035b..5c77f70efd3 100644 --- a/core/configstore/api/src/commonMain/kotlin/net/thunderbird/core/configstore/ConfigDefinition.kt +++ b/core/configstore/api/src/commonMain/kotlin/net/thunderbird/core/configstore/ConfigDefinition.kt @@ -4,10 +4,13 @@ package net.thunderbird.core.configstore * A definition of how to manage a configuration for specific type. * * The id of the configuration is used as a unique identifier to distinguish it from other configurations and - * is used by the [net.thunderbird.core.configstore.backend.ConfigBackendProvider] to retrieve the correct configuration backend. + * is used by the [net.thunderbird.core.configstore.backend.ConfigBackendProvider] to retrieve the correct + * configuration backend. * - * This allows configurations to selectively share a backend, which can be useful for performance or organizational purposes. - * It also allows for the creation of multiple configurations that can be managed independently, even if they share the same backend. + * This allows configurations to selectively share a backend, which can be useful for performance or organizational + * purposes. + * It also allows for the creation of multiple configurations that can be managed independently, even if they share + * the same backend. * * @param T The type of the configuration object. * @@ -22,7 +25,8 @@ interface ConfigDefinition { /** * The id of the configuration. * - * It is used by the [net.thunderbird.core.configstore.backend.ConfigBackendProvider] to retrieve the correct configuration backend. + * It is used by the [net.thunderbird.core.configstore.backend.ConfigBackendProvider] to retrieve the correct + * configuration backend. */ val id: ConfigId diff --git a/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt b/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt index a10c84ff3fd..a1794d5611b 100644 --- a/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt +++ b/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt @@ -39,6 +39,7 @@ class DefaultDataStoreConfigBackend( config } + @Suppress("CyclomaticComplexMethod") override suspend fun update(keys: List>, transform: (Config) -> Config) { val current = read(keys).first() val updated = transform(current) diff --git a/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CopyCommand.kt b/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CopyCommand.kt index bdfd624cf18..5c9be77a394 100644 --- a/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CopyCommand.kt +++ b/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CopyCommand.kt @@ -2,6 +2,9 @@ package net.thunderbird.core.file.command import com.eygraber.uri.Uri import kotlinx.io.Buffer +import kotlinx.io.IOException +import kotlinx.io.RawSink +import kotlinx.io.RawSource import net.thunderbird.core.file.FileOperationError import net.thunderbird.core.file.FileSystemManager import net.thunderbird.core.file.WriteMode @@ -15,46 +18,77 @@ internal class CopyCommand( private val destinationUri: Uri, ) : FileCommand { override suspend fun invoke(fs: FileSystemManager): Outcome { - // Open endpoints val source = fs.openSource(sourceUri) - ?: return Outcome.Failure( + val sink = fs.openSink(destinationUri, WriteMode.Truncate) + + return if (source == null) { + Outcome.Failure( FileOperationError.Unavailable(sourceUri, "Unable to open source: $sourceUri"), ) - val sink = fs.openSink(destinationUri, WriteMode.Truncate) - ?: return Outcome.Failure( + } else if (sink == null) { + Outcome.Failure( FileOperationError.Unavailable(destinationUri, "Unable to open destination: $destinationUri"), ) + } else { + copy(source, sink) + } + } + + private fun copy(source: RawSource, sink: RawSink): Outcome { + val buffer = Buffer() return try { - val buffer = Buffer() - while (true) { - val read = try { - source.readAtMostTo(buffer, BUFFER_SIZE) - } catch (e: Exception) { - return Outcome.Failure(FileOperationError.ReadFailed(sourceUri, e.message), cause = e) - } - if (read <= 0L) break - try { - sink.write(buffer, read) - } catch (e: Exception) { - return Outcome.Failure(FileOperationError.WriteFailed(destinationUri, e.message), cause = e) - } - } - try { - sink.flush() - } catch (e: Exception) { - return Outcome.Failure(FileOperationError.WriteFailed(destinationUri, e.message), cause = e) - } + copyToSink(source, sink, buffer) + flushSink(sink) Outcome.Success(Unit) - } catch (e: Exception) { + } catch (e: FileOperationException) { + Outcome.Failure(e.error, cause = e.cause) + } catch (e: IOException) { Outcome.Failure(FileOperationError.Unknown(e.message), cause = e) } finally { + closeQuietly(source, sink) + } + } + + private fun copyToSink(source: RawSource, sink: RawSink, buffer: Buffer) { + while (true) { + val read = try { + source.readAtMostTo(buffer, BUFFER_SIZE) + } catch (e: IOException) { + throw FileOperationException(FileOperationError.ReadFailed(sourceUri, e.message), e) + } + + if (read <= 0L) break + try { - source.close() - } catch (_: Exception) {} - try { - sink.close() - } catch (_: Exception) {} + sink.write(buffer, read) + } catch (e: IOException) { + throw FileOperationException(FileOperationError.WriteFailed(destinationUri, e.message), e) + } + } + } + + private fun flushSink(sink: RawSink) { + try { + sink.flush() + } catch (e: IOException) { + throw FileOperationException(FileOperationError.WriteFailed(destinationUri, e.message), e) + } + } + + private class FileOperationException( + val error: FileOperationError, + override val cause: Throwable?, + ) : Exception(error.toString(), cause) + + private fun closeQuietly(source: AutoCloseable?, sink: AutoCloseable?) { + try { + source?.close() + } catch (_: Exception) { + } + try { + sink?.close() + } catch (_: Exception) { } } diff --git a/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CreateDirectoriesCommand.kt b/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CreateDirectoriesCommand.kt index 32671520f14..85f5e91eb41 100644 --- a/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CreateDirectoriesCommand.kt +++ b/core/file/src/commonMain/kotlin/net/thunderbird/core/file/command/CreateDirectoriesCommand.kt @@ -1,6 +1,7 @@ package net.thunderbird.core.file.command import com.eygraber.uri.Uri +import kotlinx.io.IOException import net.thunderbird.core.file.FileOperationError import net.thunderbird.core.file.FileSystemManager import net.thunderbird.core.outcome.Outcome @@ -15,7 +16,7 @@ internal class CreateDirectoriesCommand( return try { fs.createDirectories(dirUri) Outcome.Success(Unit) - } catch (e: Exception) { + } catch (e: IOException) { Outcome.Failure( error = FileOperationError.Unavailable(dirUri, e.message ?: "Unable to create directory: $dirUri"), cause = e, diff --git a/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmFileSystemManager.kt b/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmFileSystemManager.kt index bb909bbe5da..d9d9a33dbd1 100644 --- a/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmFileSystemManager.kt +++ b/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmFileSystemManager.kt @@ -46,11 +46,12 @@ class JvmFileSystemManager : FileSystemManager { if (!file.delete() && file.exists()) { throw IOException("Unable to delete file at: $uri") } - } catch (error: Exception) { + } catch (error: IOException) { throw IOException("Unable to delete file at: $uri", error) } } + @Suppress("TooGenericExceptionCaught") override fun createDirectories(uri: Uri) { try { val file = File(uri.toURI()) diff --git a/core/logging/api/src/commonMain/kotlin/net/thunderbird/core/logging/LogLevel.kt b/core/logging/api/src/commonMain/kotlin/net/thunderbird/core/logging/LogLevel.kt index b34267296c3..e02798cc1a1 100644 --- a/core/logging/api/src/commonMain/kotlin/net/thunderbird/core/logging/LogLevel.kt +++ b/core/logging/api/src/commonMain/kotlin/net/thunderbird/core/logging/LogLevel.kt @@ -14,6 +14,7 @@ package net.thunderbird.core.logging * * @param priority The priority of the log level, where a lower priority indicates a more verbose level. */ +@Suppress("MagicNumber") enum class LogLevel( val priority: Int, ) { diff --git a/core/logging/impl-legacy/src/commonMain/kotlin/net/thunderbird/core/logging/legacy/Log.kt b/core/logging/impl-legacy/src/commonMain/kotlin/net/thunderbird/core/logging/legacy/Log.kt index 09fb946fca8..151d54547df 100644 --- a/core/logging/impl-legacy/src/commonMain/kotlin/net/thunderbird/core/logging/legacy/Log.kt +++ b/core/logging/impl-legacy/src/commonMain/kotlin/net/thunderbird/core/logging/legacy/Log.kt @@ -6,7 +6,8 @@ import net.thunderbird.core.logging.LogTag import net.thunderbird.core.logging.Logger /** - * A static logging utility that implements [net.thunderbird.core.logging.Logger] and delegates to a [net.thunderbird.core.logging.Logger] implementation. + * A static logging utility that implements [net.thunderbird.core.logging.Logger] and delegates to a + * [net.thunderbird.core.logging.Logger] implementation. * * You can initialize it in your application startup code, for example: * @@ -27,6 +28,7 @@ import net.thunderbird.core.logging.Logger message = "Use a net.thunderbird.core.logging.Logger instance via dependency injection instead. " + "This class will be removed in a future release.", ) +@Suppress("TooManyFunctions") object Log : Logger { lateinit var logger: Logger @@ -143,6 +145,7 @@ object Log : Logger { logger.error(message = { formatMessage(message, args) }, throwable = t) } + @Suppress("SpreadOperator", "TooGenericExceptionCaught") private fun formatMessage(message: String?, args: Array): String { return if (message == null) { "" diff --git a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/interaction/InteractionSettings.kt b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/interaction/InteractionSettings.kt index ac9097f89a7..d345a49607e 100644 --- a/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/interaction/InteractionSettings.kt +++ b/core/preference/api/src/commonMain/kotlin/net/thunderbird/core/preference/interaction/InteractionSettings.kt @@ -21,7 +21,7 @@ const val INTERACTION_SETTINGS_DEFAULT_CONFIRM_MARK_ALL_READ = true data class InteractionSettings( val useVolumeKeysForNavigation: Boolean = INTERACTION_SETTINGS_DEFAULT_USE_VOLUME_KEYS_NAVIGATION, val messageViewPostRemoveNavigation: String = INTERACTION_SETTINGS_DEFAULT_MESSAGE_VIEW_POST_REMOVE_NAVIGATION, - var messageViewPostMarkAsUnreadNavigation: PostMarkAsUnreadNavigation = + val messageViewPostMarkAsUnreadNavigation: PostMarkAsUnreadNavigation = INTERACTION_SETTINGS_DEFAULT_MESSAGE_VIEW_POST_MARK_AS_UNREAD_NAVIGATION, val swipeActions: SwipeActions = INTERACTION_SETTINGS_DEFAULT_SWIPE_ACTION, val isConfirmDelete: Boolean = INTERACTION_SETTINGS_DEFAULT_CONFIRM_DELETE, diff --git a/core/testing/src/commonMain/kotlin/assertk/assertions/ValueExtensions.kt b/core/testing/src/commonMain/kotlin/assertk/assertions/ValueExtensions.kt index 5b0f9a3d65d..59e17bc427b 100644 --- a/core/testing/src/commonMain/kotlin/assertk/assertions/ValueExtensions.kt +++ b/core/testing/src/commonMain/kotlin/assertk/assertions/ValueExtensions.kt @@ -16,6 +16,7 @@ fun Assert.isOneOf(vararg expectedValues: T) = given { actual -> /** * Asserts that the value is one of the expected values. */ +@Suppress("SpreadOperator") inline fun Assert.isOneOf(expectedValues: Collection) { isOneOf(*expectedValues.toTypedArray()) } From ebb378bd5b78996b862ddca3529b93ac7889fda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Tue, 26 May 2026 18:49:33 +0200 Subject: [PATCH 4/4] fix: formatting issues --- .../thunderbird/app/common/account/AccountCreator.kt | 2 ++ .../ui/catalog/ui/page/atom/items/TabItems.kt | 1 + .../gradle/plugin/app/badging/BadgingPlugin.kt | 4 ++-- .../gradle/plugin/app/badging/GenerateBadgingTask.kt | 2 +- .../plugin/app/versioning/PrintVersionInfoTask.kt | 8 ++++---- .../gradle/plugin/app/versioning/VersioningPlugin.kt | 2 -- .../plugin/quality/coverage/filter/ComposeFilter.kt | 2 +- .../app/k9mail/cli/autodiscovery/SerialRunner.kt | 3 +++ .../k9mail/core/android/common/compat/BundleCompat.kt | 1 + .../android/permissions/AndroidPermissionChecker.kt | 1 + .../net/thunderbird/core/common/mail/BaseParser.kt | 2 +- .../core/common/mail/EmailAddressParser.kt | 4 ++++ .../thunderbird/core/common/mail/EmailDomainParser.kt | 2 ++ .../kotlin/net/thunderbird/core/common/mail/Flag.kt | 1 + .../core/common/state/debug/StateDiffer.kt | 1 + .../core/common/state/debug/ValueFormatter.kt | 3 +++ .../backend/DefaultDataStoreConfigBackend.kt | 4 ++++ .../net/thunderbird/core/file/JvmDirectoryProvider.kt | 2 ++ .../thunderbird/core/logging/file/FakeFileManager.kt | 1 + .../common/padding/CalculateResponsivePadding.kt | 2 -- .../ui/compose/common/window/FoldableStateObserver.kt | 3 +++ .../designsystem/template/ResponsiveContent.kt | 2 ++ .../designsystem/molecule/swipe/SwipeableRowState.kt | 8 ++++++++ .../designsystem/molecule/swipe/fork/Draggable.kt | 2 +- .../feature/account/oauth/ui/AccountOAuthViewModel.kt | 6 +++--- .../settings/domain/usecase/ValidateImapPrefix.kt | 1 - .../settings/domain/usecase/ValidatePassword.kt | 1 - .../settings/domain/usecase/ValidateUsername.kt | 1 - .../settings/ui/common/ProtectedPasswordInput.kt | 2 ++ .../ui/common/ProtectedTextFieldOutlinedPassword.kt | 1 + .../common/mapper/ConnectionSecurityStringMapper.kt | 2 ++ .../ui/incoming/IncomingServerSettingsViewModel.kt | 9 +++++++++ .../ui/outgoing/OutgoingServerSettingsViewModel.kt | 7 +++++++ .../domain/usecase/ValidateServerSettings.kt | 4 ++++ .../impl/ui/general/GeneralSettingsBuilder.kt | 1 + .../impl/ui/general/GeneralSettingsContent.kt | 3 +++ .../settings/impl/ui/general/GeneralSettingsScreen.kt | 1 + .../ui/readingMail/ReadingMailSettingsViewModel.kt | 2 ++ .../settings/impl/ui/search/SearchSettingContent.kt | 1 + .../impl/ui/search/SearchSettingsViewModel.kt | 1 + .../impl/domain/usecase/UpdateAvatarImageTest.kt | 2 ++ .../account/setup/domain/AutoDiscoveryMapper.kt | 2 -- .../ui/autodiscovery/AccountAutoDiscoveryViewModel.kt | 8 ++++++++ .../ui/autodiscovery/AutoDiscoveryStringMapper.kt | 1 + .../ui/options/display/DisplayOptionsViewModel.kt | 1 + .../setup/ui/options/sync/SyncOptionsViewModel.kt | 1 + .../ui/specialfolders/SpecialFoldersStringMapper.kt | 2 ++ .../ui/specialfolders/SpecialFoldersViewModel.kt | 2 -- .../legacy/serializer/ServerSettingsDtoSerializer.kt | 8 ++++++++ .../autodiscovery/autoconfig/RealAutoconfigFetcher.kt | 1 + .../autodiscovery/autoconfig/RealAutoconfigParser.kt | 11 +++++++++++ .../googleplay/ui/contribution/ContributionError.kt | 4 +++- .../ui/contribution/list/ContributionListSlice.kt | 2 -- .../mail/message/export/eml/EmlMessageExporter.kt | 4 ++++ .../ui/component/molecule/MessageItemAvatarCircle.kt | 1 + .../message/list/ui/component/organism/MessageItem.kt | 2 -- .../ui/component/MessageItemSwipeBackground.kt | 7 +++++++ .../internal/ui/dialog/SetupArchiveFolderDialog.kt | 2 ++ .../ui/dialog/SetupArchiveFolderDialogViewModel.kt | 5 ----- .../list/internal/fakes/FakeBackendFolderUpdater.kt | 2 ++ .../migration/qrcode/payload/IntValueMapper.kt | 7 ++++++- .../migration/qrcode/ui/QrCodeScannerContent.kt | 3 +++ .../navigation/drawer/dropdown/ui/DrawerView.kt | 5 +++++ .../navigation/drawer/dropdown/ui/DrawerViewModel.kt | 7 +++++++ .../drawer/dropdown/ui/folder/FolderListItem.kt | 1 + .../api/ui/action/ResolvedNotificationActionButton.kt | 1 + .../onboarding/main/navigation/OnboardingNavHost.kt | 1 + .../onboarding/permissions/ui/PermissionBox.kt | 1 + .../feature/search/legacy/SearchConditionTreeNode.kt | 1 + .../settings/import/ui/SettingsImportFragment.kt | 1 + .../common/domain/CreateAccountStateUseCase.kt | 3 +++ .../common/navigation/DefaultThundermailNavigation.kt | 2 ++ .../internal/common/ui/ThundermailOAuthViewModel.kt | 1 + .../feature/widget/message/list/MessageListLoader.kt | 9 ++++++++- .../feature/widget/message/list/MessageListLoader.kt | 9 ++++++++- .../unread/UnreadWidgetConfigurationFragment.kt | 1 + .../feature/widget/unread/UnreadWidgetDataProvider.kt | 1 + 77 files changed, 191 insertions(+), 37 deletions(-) diff --git a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt index cb1cf9c1173..c5d88e68606 100644 --- a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt +++ b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt @@ -143,9 +143,11 @@ internal class AccountCreator( is SpecialFolderOption.None -> { if (isAutomatic) SpecialFolderSelection.AUTOMATIC else SpecialFolderSelection.MANUAL } + is SpecialFolderOption.Regular -> { SpecialFolderSelection.MANUAL } + is SpecialFolderOption.Special -> { if (isAutomatic) SpecialFolderSelection.AUTOMATIC else SpecialFolderSelection.MANUAL } diff --git a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/TabItems.kt b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/TabItems.kt index 79d8766ad7b..288b9d8122c 100644 --- a/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/TabItems.kt +++ b/app-ui-catalog/src/main/kotlin/net/thunderbird/ui/catalog/ui/page/atom/items/TabItems.kt @@ -31,6 +31,7 @@ fun LazyGridScope.tabItems() { icon = { when (tabItem) { PrimaryTabItems.TextOnly -> null + PrimaryTabItems.TextWithIcon, PrimaryTabItems.TextWithIconAndBadge -> Icon(imageVector = requireNotNull(tabItem.icon)) } diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/BadgingPlugin.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/BadgingPlugin.kt index 9a93c3f5da8..8843c137633 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/BadgingPlugin.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/BadgingPlugin.kt @@ -2,12 +2,12 @@ package net.thunderbird.gradle.plugin.app.badging import com.android.build.api.artifact.SingleArtifact import com.android.build.api.variant.Aapt2 +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import org.gradle.api.Plugin import org.gradle.api.Project +import org.gradle.api.tasks.Copy import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.configure -import com.android.build.api.variant.ApplicationAndroidComponentsExtension -import org.gradle.api.tasks.Copy import org.gradle.kotlin.dsl.register private val variantsToCheck = listOf("release", "beta", "daily") diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/GenerateBadgingTask.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/GenerateBadgingTask.kt index 2f9428f575b..0d65939d9ca 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/GenerateBadgingTask.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/badging/GenerateBadgingTask.kt @@ -15,7 +15,7 @@ import org.gradle.api.tasks.TaskAction import org.gradle.process.ExecOperations @CacheableTask -abstract class GenerateBadgingTask : DefaultTask() { +abstract class GenerateBadgingTask : DefaultTask() { @get:OutputFile abstract val badging: RegularFileProperty diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/PrintVersionInfoTask.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/PrintVersionInfoTask.kt index 2c6807cee99..70d6cf9b0c2 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/PrintVersionInfoTask.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/PrintVersionInfoTask.kt @@ -1,5 +1,9 @@ package net.thunderbird.gradle.plugin.app.versioning +import java.io.File +import javax.xml.parsers.DocumentBuilderFactory +import javax.xml.xpath.XPathConstants +import javax.xml.xpath.XPathFactory import org.gradle.api.DefaultTask import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.RegularFileProperty @@ -10,10 +14,6 @@ import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.Optional import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.TaskAction -import java.io.File -import javax.xml.parsers.DocumentBuilderFactory -import javax.xml.xpath.XPathConstants -import javax.xml.xpath.XPathFactory abstract class PrintVersionInfoTask : DefaultTask() { @get:Input diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/VersioningPlugin.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/VersioningPlugin.kt index 33bef7fa172..a2f4813b58a 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/VersioningPlugin.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/app/versioning/VersioningPlugin.kt @@ -74,5 +74,3 @@ class VersioningPlugin : Plugin { if (it.isLowerCase()) it.titlecase() else it.toString() } } - - diff --git a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/coverage/filter/ComposeFilter.kt b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/coverage/filter/ComposeFilter.kt index fab97b17827..3be91dbbd97 100644 --- a/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/coverage/filter/ComposeFilter.kt +++ b/build-plugin/plugin/src/main/kotlin/net/thunderbird/gradle/plugin/quality/coverage/filter/ComposeFilter.kt @@ -9,7 +9,7 @@ internal fun KoverReportFiltersConfig.composeFilter() { classes( // Compose Resources "*.Res", - "*.ActualResourceCollectorsKt" + "*.ActualResourceCollectorsKt", ) annotatedBy( diff --git a/cli/autodiscovery-cli/src/main/kotlin/app/k9mail/cli/autodiscovery/SerialRunner.kt b/cli/autodiscovery-cli/src/main/kotlin/app/k9mail/cli/autodiscovery/SerialRunner.kt index d499f8792d4..965072cdfcf 100644 --- a/cli/autodiscovery-cli/src/main/kotlin/app/k9mail/cli/autodiscovery/SerialRunner.kt +++ b/cli/autodiscovery-cli/src/main/kotlin/app/k9mail/cli/autodiscovery/SerialRunner.kt @@ -21,13 +21,16 @@ class SerialRunner(private val runnables: List) { is Settings -> { return discoveryResult } + is NetworkError -> { networkErrorCount++ if (networkError == null) { networkError = discoveryResult } } + NoUsableSettingsFound -> { } + is UnexpectedException -> { Log.w(discoveryResult.exception, "Unexpected exception") } diff --git a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/compat/BundleCompat.kt b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/compat/BundleCompat.kt index 180a9b3e93a..8d4c49936c3 100644 --- a/core/android/common/src/main/kotlin/app/k9mail/core/android/common/compat/BundleCompat.kt +++ b/core/android/common/src/main/kotlin/app/k9mail/core/android/common/compat/BundleCompat.kt @@ -12,6 +12,7 @@ object BundleCompat { @JvmStatic fun getSerializable(bundle: Bundle, key: String?, clazz: Class): T? = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> bundle.getSerializable(key, clazz) + else -> { @Suppress("DEPRECATION") val serializable = bundle.getSerializable(key) diff --git a/core/android/permissions/src/main/kotlin/app/k9mail/core/android/permissions/AndroidPermissionChecker.kt b/core/android/permissions/src/main/kotlin/app/k9mail/core/android/permissions/AndroidPermissionChecker.kt index 371bf2db34d..ae213ac5400 100644 --- a/core/android/permissions/src/main/kotlin/app/k9mail/core/android/permissions/AndroidPermissionChecker.kt +++ b/core/android/permissions/src/main/kotlin/app/k9mail/core/android/permissions/AndroidPermissionChecker.kt @@ -18,6 +18,7 @@ class AndroidPermissionChecker( Permission.Contacts -> { checkSelfPermission(Manifest.permission.READ_CONTACTS) } + Permission.Notifications -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/BaseParser.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/BaseParser.kt index 2b3d0add43e..664be99b753 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/BaseParser.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/BaseParser.kt @@ -3,7 +3,6 @@ package net.thunderbird.core.common.mail import net.thunderbird.core.common.mail.EmailAddressParserError.UnexpectedCharacter import net.thunderbird.core.common.mail.EmailAddressParserError.UnexpectedEndOfInput -@Suppress("UnnecessaryAbstractClass") /** * Base class for string parsers. * @@ -13,6 +12,7 @@ import net.thunderbird.core.common.mail.EmailAddressParserError.UnexpectedEndOfI * @property startIndex The index to start parsing from (inclusive). * @property endIndex The index to stop parsing at (exclusive). */ +@Suppress("UnnecessaryAbstractClass") internal abstract class BaseParser(val input: String, startIndex: Int = 0, val endIndex: Int = input.length) { protected var currentIndex = startIndex diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailAddressParser.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailAddressParser.kt index 8c40d35d0d7..86607308617 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailAddressParser.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailAddressParser.kt @@ -83,6 +83,7 @@ internal class EmailAddressParser( character.isAtext -> { readDotString() } + character == DQUOTE -> { if (config.isQuotedLocalPartAllowed) { readQuotedString() @@ -90,6 +91,7 @@ internal class EmailAddressParser( parserError(QuotedStringInLocalPart) } } + else -> { parserError(InvalidLocalPart) } @@ -129,6 +131,7 @@ internal class EmailAddressParser( val character = peek() when { character.isQtext -> append(read()) + character == BACKSLASH -> { expect(BACKSLASH) val escapedCharacter = read() @@ -139,6 +142,7 @@ internal class EmailAddressParser( } character == DQUOTE -> break + else -> parserError(InvalidQuotedString) } } diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailDomainParser.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailDomainParser.kt index 7dbbe07ad63..3ac7e7b8262 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailDomainParser.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/EmailDomainParser.kt @@ -69,10 +69,12 @@ internal class EmailDomainParser( requireLetDig = true expect(HYPHEN) } + character.isLetDig -> { requireLetDig = false expectLetDig() } + else -> break } } diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/Flag.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/Flag.kt index e6e05eb842b..e51b092e94b 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/Flag.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/mail/Flag.kt @@ -13,6 +13,7 @@ enum class Flag { FORWARDED, // The following flags are for internal library use only. + /** * Delete and remove from the LocalStore immediately. */ diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StateDiffer.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StateDiffer.kt index eb9f3adb556..3edfa3e0ea2 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StateDiffer.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/StateDiffer.kt @@ -59,6 +59,7 @@ internal class StateDiffer( when { oldIsObject && newIsObject -> appendObjectDiff(key, oldVal, newVal, context) + oldVal is Map<*, *> && newVal is Map<*, *> -> appendMapDiff(key, oldVal, newVal, context) diff --git a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/ValueFormatter.kt b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/ValueFormatter.kt index e11d3a5dde9..6885a83d67a 100644 --- a/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/ValueFormatter.kt +++ b/core/common/src/commonMain/kotlin/net/thunderbird/core/common/state/debug/ValueFormatter.kt @@ -17,11 +17,14 @@ internal class ValueFormatter( StatePrettyPrinterVocabulary.formatCollectionItemsValue(value.size) is Collection<*> -> formatList(value) + is Map<*, *> if value.size > MAX_COLLECTION_SIZE_PRINT_THRESHOLD -> StatePrettyPrinterVocabulary.formatMapEntriesValue(value.size) is Map<*, *> -> formatMap(value) + null -> "null" + else -> { val formattedValue = customFormatter(value, ::format) if (formattedValue.length > maxLen) { diff --git a/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt b/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt index a1794d5611b..51c3f258ff3 100644 --- a/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt +++ b/core/configstore/impl-backend/src/commonMain/kotlin/net/thunderbird/core/configstore/backend/DefaultDataStoreConfigBackend.kt @@ -54,9 +54,13 @@ class DefaultDataStoreConfigBackend( } is ConfigKey.IntKey -> if (value is Int) preferences[intPreferencesKey(key.name)] = value + is ConfigKey.StringKey -> if (value is String) preferences[stringPreferencesKey(key.name)] = value + is ConfigKey.LongKey -> if (value is Long) preferences[longPreferencesKey(key.name)] = value + is ConfigKey.FloatKey -> if (value is Float) preferences[floatPreferencesKey(key.name)] = value + is ConfigKey.DoubleKey -> if (value is Double) preferences[doublePreferencesKey(key.name)] = value } } diff --git a/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmDirectoryProvider.kt b/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmDirectoryProvider.kt index 4943644f915..5c51f4707cd 100644 --- a/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmDirectoryProvider.kt +++ b/core/file/src/jvmMain/kotlin/net/thunderbird/core/file/JvmDirectoryProvider.kt @@ -18,6 +18,7 @@ class JvmDirectoryProvider( private val appDir: File = when { os.contains("mac") -> File(userHome, "Library/Application Support/$appName") + os.contains("win") -> { val roaming = System.getenv("APPDATA") if (roaming != null) { @@ -32,6 +33,7 @@ class JvmDirectoryProvider( private val cacheDir = when { os.contains("mac") -> File(userHome, "Library/Caches/$appName") + os.contains("win") -> { val localAppData = System.getenv("LOCALAPPDATA") if (localAppData != null) { diff --git a/core/logging/impl-file/src/androidHostTest/kotlin/net/thunderbird/core/logging/file/FakeFileManager.kt b/core/logging/impl-file/src/androidHostTest/kotlin/net/thunderbird/core/logging/file/FakeFileManager.kt index 3dfe6499e54..27305f6c451 100644 --- a/core/logging/impl-file/src/androidHostTest/kotlin/net/thunderbird/core/logging/file/FakeFileManager.kt +++ b/core/logging/impl-file/src/androidHostTest/kotlin/net/thunderbird/core/logging/file/FakeFileManager.kt @@ -24,6 +24,7 @@ class FakeFileManager : FileManager { val path = requireNotNull(androidUri.path) { "File URI without path: $androidUri" } File(path).readText(Charsets.UTF_8) } + else -> error("Unsupported scheme for FakeFileManager source: ${androidUri.scheme}") } exportedContent = content diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/padding/CalculateResponsivePadding.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/padding/CalculateResponsivePadding.kt index f620ed0448f..596ce3b8e1e 100644 --- a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/padding/CalculateResponsivePadding.kt +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/padding/CalculateResponsivePadding.kt @@ -11,9 +11,7 @@ fun calculateResponsiveWidthPadding(): PaddingValues { val windowSizeInfo = getWindowSizeInfo() val horizontalPadding = when (windowSizeInfo.screenWidthSizeClass) { WindowSizeClass.Small, WindowSizeClass.Compact -> 0.dp - WindowSizeClass.Medium -> (windowSizeInfo.screenWidth - WindowSizeClass.COMPACT_MAX_WIDTH.dp) / 2 - WindowSizeClass.Expanded -> (windowSizeInfo.screenWidth - WindowSizeClass.MEDIUM_MAX_WIDTH.dp) / 2 } return PaddingValues(horizontal = horizontalPadding) diff --git a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/window/FoldableStateObserver.kt b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/window/FoldableStateObserver.kt index 798e1709783..c144afd86db 100644 --- a/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/window/FoldableStateObserver.kt +++ b/core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/window/FoldableStateObserver.kt @@ -96,14 +96,17 @@ class FoldableStateObserver( // No folding feature means either not a foldable device or unable to detect FoldableState.UNKNOWN } + foldingFeature.state == FoldingFeature.State.HALF_OPENED -> { // Half-opened state (like a laptop mode) - treat as unfolded for split view FoldableState.UNFOLDED } + foldingFeature.state == FoldingFeature.State.FLAT -> { // Flat state means fully unfolded FoldableState.UNFOLDED } + else -> { // Unknown or other states - default to unknown FoldableState.UNKNOWN diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/template/ResponsiveContent.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/template/ResponsiveContent.kt index 00b40bc2892..99ad9123a97 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/template/ResponsiveContent.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/template/ResponsiveContent.kt @@ -74,7 +74,9 @@ private fun ExpandedContent( ) { when (getWindowSizeInfo().screenHeightSizeClass) { WindowSizeClass.Small -> CompactContent(modifier, content) + WindowSizeClass.Compact -> MediumContent(modifier, content) + WindowSizeClass.Medium -> { Box( modifier = Modifier diff --git a/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowState.kt b/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowState.kt index b4ae4e96313..bb7f35c5211 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowState.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/SwipeableRowState.kt @@ -187,6 +187,7 @@ class SwipeableRowState internal constructor( val targetBehaviour = if (targetOffset >= 0f) startToEndBehaviour else endToStartBehaviour val targetMaxOffset = when (targetBehaviour) { is SwipeBehaviour.Dismiss -> layoutWidth + is SwipeBehaviour.Reveal, is SwipeBehaviour.Action -> (targetBehaviour.percentageThreshold * layoutWidth) + SWIPE_BEHAVIOUR_REVEAL_EXTENSION @@ -248,6 +249,7 @@ class SwipeableRowState internal constructor( val intendedDirection = resolveIntendedDirection(velocity) return when { swipeState == SwipeState.Revealed && !isClosingGesture(velocity) -> false + isFlingDirectionAllowed(intendedDirection) -> { val behaviour = intendedDirection.behaviour val willSettlePastThreshold = willSettlePastThreshold(velocity, behaviour) @@ -293,7 +295,9 @@ class SwipeableRowState internal constructor( } is SwipeBehaviour.Reveal if willSettlePastThreshold -> swipeState = SwipeState.Revealed + is SwipeBehaviour.Dismiss if willSettlePastThreshold -> swipeState = SwipeState.Dismissed + else -> Unit } } @@ -317,8 +321,11 @@ class SwipeableRowState internal constructor( layoutWidth * SWIPE_BEHAVIOUR_DISMISS_OFFSCREEN_MULTIPLIER is SwipeBehaviour.Reveal if willSettlePastThreshold -> behaviour.percentageThreshold * layoutWidth + is SwipeBehaviour.Action if willSettlePastThreshold -> behaviour.percentageThreshold * layoutWidth + is SwipeBehaviour.Disabled -> 0f + else -> 0f } val directionMultiplier = when (intendedDirection) { @@ -339,6 +346,7 @@ class SwipeableRowState internal constructor( val resistanceStart = maxOffset * ELASTIC_RESISTANCE_START_FRACTION return when { currentAbsOffset >= maxOffset -> ELASTIC_RESISTANCE_MIN_FACTOR + currentAbsOffset >= resistanceStart -> { val progress = (currentAbsOffset - resistanceStart) / (maxOffset - resistanceStart) lerp(start = 1f, stop = ELASTIC_RESISTANCE_MIN_FACTOR, fraction = progress) diff --git a/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/fork/Draggable.kt b/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/fork/Draggable.kt index 29b25de14fb..1ca41236289 100644 --- a/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/fork/Draggable.kt +++ b/core/ui/compose/designsystem/src/main/kotlin/net/thunderbird/core/ui/compose/designsystem/molecule/swipe/fork/Draggable.kt @@ -428,7 +428,7 @@ internal class DraggableNode( channel = Channel(capacity = Channel.UNLIMITED) } - /** + /* * To preserve the original behavior we had (before the Modifier.Node migration) we need to * scope the DragStopped and DragCancel methods to the node's coroutine scope instead of * using the one provided by the pointer input modifier, this is to ensure that even when diff --git a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt index a6daad7bb43..93e33c711fb 100644 --- a/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt +++ b/feature/account/oauth/src/main/kotlin/app/k9mail/feature/account/oauth/ui/AccountOAuthViewModel.kt @@ -35,11 +35,8 @@ class AccountOAuthViewModel( override fun event(event: Event) { when (event) { is Event.OnOAuthResult -> onOAuthResult(event.resultCode, event.data) - Event.SignInClicked -> onSignIn() - Event.OnBackClicked -> navigateBack() - Event.OnRetryClicked -> onRetry() } } @@ -93,8 +90,11 @@ class AccountOAuthViewModel( viewModelScope.launch { when (val result = finishOAuthSignIn.execute(data)) { AuthorizationResult.BrowserNotAvailable -> updateErrorState(Error.BrowserNotAvailable) + AuthorizationResult.Canceled -> updateErrorState(Error.Canceled) + is AuthorizationResult.Failure -> updateErrorState(Error.Unknown(result.error)) + is AuthorizationResult.Success -> { updateState { state -> state.copy(isLoading = false) diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateImapPrefix.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateImapPrefix.kt index 80f4f94a01c..ec4a6160d10 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateImapPrefix.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateImapPrefix.kt @@ -11,7 +11,6 @@ internal class ValidateImapPrefix : UseCase.ValidateImapPrefix { return when { imapPrefix.isEmpty() -> Outcome.Success(Unit) imapPrefix.isBlank() -> Outcome.Failure(ValidateImapPrefixError.BlankImapPrefix) - else -> Outcome.Success(Unit) } } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidatePassword.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidatePassword.kt index c02e8de21e8..670f082ddf1 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidatePassword.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidatePassword.kt @@ -12,7 +12,6 @@ class ValidatePassword : UseCase.ValidatePassword { override fun execute(password: String): ValidationOutcome { return when { password.isBlank() -> Outcome.Failure(ValidatePasswordError.EmptyPassword) - else -> ValidationSuccess } } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateUsername.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateUsername.kt index 1c9c23547b7..7f4b114da50 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateUsername.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/domain/usecase/ValidateUsername.kt @@ -11,7 +11,6 @@ internal class ValidateUsername : UseCase.ValidateUsername { override fun execute(username: String): ValidationOutcome { return when { username.isBlank() -> Outcome.Failure(ValidateUsernameError.EmptyUsername) - else -> ValidationSuccess } } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedPasswordInput.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedPasswordInput.kt index 24a003c1b59..24840a45fe6 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedPasswordInput.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedPasswordInput.kt @@ -73,8 +73,10 @@ private fun AuthenticationError.mapToStringRes(): Int { return when (this) { AuthenticationError.NotAvailable -> R.string.account_server_settings_password_authentication_screen_lock_required + AuthenticationError.Failed -> R.string.account_server_settings_password_authentication_failed + AuthenticationError.UnableToStart -> R.string.account_server_settings_password_authentication_unable_to_start } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedTextFieldOutlinedPassword.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedTextFieldOutlinedPassword.kt index 637d1c476f2..460952ef6e9 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedTextFieldOutlinedPassword.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/ProtectedTextFieldOutlinedPassword.kt @@ -64,6 +64,7 @@ fun ProtectedTextFieldOutlinedPassword( onWarningChange(null) activity.setSecure(true) } + is Outcome.Failure -> { onWarningChange(outcome.error) } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/mapper/ConnectionSecurityStringMapper.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/mapper/ConnectionSecurityStringMapper.kt index 7d27104f20d..d19b209e8b9 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/mapper/ConnectionSecurityStringMapper.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/common/mapper/ConnectionSecurityStringMapper.kt @@ -7,9 +7,11 @@ import app.k9mail.feature.account.server.settings.R internal fun ConnectionSecurity.toResourceString(resources: Resources): String { return when (this) { ConnectionSecurity.None -> resources.getString(R.string.account_server_settings_connection_security_none) + ConnectionSecurity.StartTLS -> resources.getString( R.string.account_server_settings_connection_security_start_tls, ) + ConnectionSecurity.TLS -> resources.getString(R.string.account_server_settings_connection_security_ssl) } } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt index 80ee59cacfc..bbd783c1a58 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/incoming/IncomingServerSettingsViewModel.kt @@ -27,12 +27,19 @@ open class IncomingServerSettingsViewModel( Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState) is Event.ProtocolTypeChanged -> updateProtocolType(event.protocolType) + is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) } + is Event.SecurityChanged -> updateSecurity(event.security) + is Event.PortChanged -> updateState { it.copy(port = it.port.updateValue(event.port)) } + is Event.AuthenticationTypeChanged -> updateState { it.copy(authenticationType = event.authenticationType) } + is Event.UsernameChanged -> updateState { it.copy(username = it.username.updateValue(event.username)) } + is Event.PasswordChanged -> updateState { it.copy(password = it.password.updateValue(event.password)) } + is Event.ClientCertificateChanged -> updateState { it.copy(clientCertificateAlias = event.clientCertificateAlias) } @@ -46,9 +53,11 @@ open class IncomingServerSettingsViewModel( } is Event.ImapUseCompressionChanged -> updateState { it.copy(imapUseCompression = event.useCompression) } + is Event.ImapSendClientInfoChanged -> updateState { it.copy(imapSendClientInfo = event.sendClientInfo) } Event.OnNextClicked -> onNext() + Event.OnBackClicked -> onBack() } } diff --git a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt index 04b78d1d8cb..29203fe1c2f 100644 --- a/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt +++ b/feature/account/server/settings/src/main/kotlin/app/k9mail/feature/account/server/settings/ui/outgoing/OutgoingServerSettingsViewModel.kt @@ -24,16 +24,23 @@ open class OutgoingServerSettingsViewModel( Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState) is Event.ServerChanged -> updateState { it.copy(server = it.server.updateValue(event.server)) } + is Event.SecurityChanged -> updateSecurity(event.security) + is Event.PortChanged -> updateState { it.copy(port = it.port.updateValue(event.port)) } + is Event.AuthenticationTypeChanged -> updateState { it.copy(authenticationType = event.authenticationType) } + is Event.UsernameChanged -> updateState { it.copy(username = it.username.updateValue(event.username)) } + is Event.PasswordChanged -> updateState { it.copy(password = it.password.updateValue(event.password)) } + is Event.ClientCertificateChanged -> updateState { it.copy(clientCertificateAlias = event.clientCertificateAlias) } Event.OnNextClicked -> onNext() + Event.OnBackClicked -> onBack() } } diff --git a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/domain/usecase/ValidateServerSettings.kt b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/domain/usecase/ValidateServerSettings.kt index 41b22e9a078..a6bd9d9065c 100644 --- a/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/domain/usecase/ValidateServerSettings.kt +++ b/feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/domain/usecase/ValidateServerSettings.kt @@ -20,9 +20,13 @@ class ValidateServerSettings( return withContext(coroutineDispatcher) { when (settings.type) { "imap" -> imapValidator.checkServerSettings(settings, authStateStorage) + "pop3" -> pop3Validator.checkServerSettings(settings, authStateStorage) + "smtp" -> smtpValidator.checkServerSettings(settings, authStateStorage) + "demo" -> ServerSettingsValidationResult.Success + else -> { throw IllegalArgumentException("Unsupported server type: ${settings.type}") } diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsBuilder.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsBuilder.kt index c3a98accc33..95932b73e9e 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsBuilder.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsBuilder.kt @@ -57,6 +57,7 @@ internal class GeneralSettingsBuilder( when (val avatar = state.avatar) { is Avatar.Monogram -> settings += avatarMonogram(monogram = avatar.value) + is Avatar.Image -> settings += avatarImage( onSelectImageClick = { onEvent(Event.OnSelectAvatarImageClick) }, ) diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt index c7eab8d5869..ffc30a3edd4 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt @@ -70,11 +70,13 @@ private fun handleSegmentedChange(setting: SettingValue.SegmentedButton<*>, onEv val avatarOption = setting.value as SegmentedButtonOption onEvent(Event.OnAvatarChange(avatarOption.value)) } + GeneralSettingId.AVATAR_ICON -> { @Suppress("UNCHECKED_CAST") val iconOption = setting.value as SegmentedButtonOption onEvent(Event.OnAvatarChange(iconOption.value)) } + else -> Unit } } @@ -87,6 +89,7 @@ private fun handleIconListChange(setting: SettingValue.IconList, onEvent: (Event onEvent(Event.OnAvatarChange(Avatar.Icon(iconOption.id))) } } + else -> Unit } } diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt index b7bf238514b..adfb754db53 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt @@ -34,6 +34,7 @@ internal fun GeneralSettingsScreen( val (state, dispatch) = viewModel.observe { effect -> when (effect) { is Effect.NavigateBack -> onBack() + is Effect.OpenAvatarImagePicker -> { imagePicker.launch(arrayOf("image/jpeg", "image/png")) } diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/readingMail/ReadingMailSettingsViewModel.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/readingMail/ReadingMailSettingsViewModel.kt index 0b53de93948..a6b37a4f642 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/readingMail/ReadingMailSettingsViewModel.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/readingMail/ReadingMailSettingsViewModel.kt @@ -38,6 +38,7 @@ internal class ReadingMailSettingsViewModel( override fun event(event: Event) { when (event) { is Event.OnBackPressed -> emitEffect(Effect.NavigateBack) + is Event.OnShowPicturesChange -> { viewModelScope.launch { updateReadMailSettings( @@ -55,6 +56,7 @@ internal class ReadingMailSettingsViewModel( ) } } + is Event.OnIsMarkMessageAsReadOnViewToggle -> { viewModelScope.launch { updateReadMailSettings( diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingContent.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingContent.kt index 5c17ded5ecc..88b21af8a4e 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingContent.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingContent.kt @@ -93,6 +93,7 @@ private fun handleSettingChange( is SettingValue.Select -> onEvent( SearchSettingsContract.Event.OnServerSearchLimitChange(setting.value.id.toInt()), ) + else -> Unit } } diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingsViewModel.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingsViewModel.kt index 61edd6a61c1..c9d3d8aa3f6 100644 --- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingsViewModel.kt +++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/search/SearchSettingsViewModel.kt @@ -40,6 +40,7 @@ internal class SearchSettingsViewModel( override fun event(event: SearchSettingsContract.Event) { when (event) { is SearchSettingsContract.Event.OnBackPressed -> emitEffect(SearchSettingsContract.Effect.NavigateBack) + is SearchSettingsContract.Event.OnServerSearchLimitChange -> { viewModelScope.launch(dispatcher) { updateSearchSettings( diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateAvatarImageTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateAvatarImageTest.kt index e2f4ee7a3b3..caa0936d19a 100644 --- a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateAvatarImageTest.kt +++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateAvatarImageTest.kt @@ -40,6 +40,7 @@ class UpdateAvatarImageTest { assertThat(avatar).isInstanceOf(Avatar.Image::class) assertThat(avatar.uri).isEqualTo(repo.lastUpdatedUri?.toString()) } + else -> error("Expected Success but was $result") } // ensure repository received correct inputs @@ -90,6 +91,7 @@ class UpdateAvatarImageTest { assertThat(avatar).isInstanceOf(Avatar.Image::class) assertThat(avatar.uri).isEqualTo(repo.lastUpdatedUri?.toString()) } + else -> error("Expected Success but was $result") } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/AutoDiscoveryMapper.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/AutoDiscoveryMapper.kt index fa3abe17618..ba137d3c0eb 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/AutoDiscoveryMapper.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/AutoDiscoveryMapper.kt @@ -16,7 +16,6 @@ fun IncomingServerSettings.toServerSettings(password: String?): ServerSettings { return when (this) { is ImapServerSettings -> this.toImapServerSettings(password) is DemoServerSettings -> this.serverSettings - else -> throw IllegalArgumentException("Unknown server settings type: $this") } } @@ -49,7 +48,6 @@ fun OutgoingServerSettings.toServerSettings(password: String?): ServerSettings { return when (this) { is SmtpServerSettings -> this.toSmtpServerSettings(password) is DemoServerSettings -> this.serverSettings - else -> throw IllegalArgumentException("Unknown server settings type: $this") } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryViewModel.kt index 2d70e7c4679..eca061388bb 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryViewModel.kt @@ -41,13 +41,19 @@ internal class AccountAutoDiscoveryViewModel( override fun event(event: Event) { when (event) { is Event.EmailAddressChanged -> changeEmailAddress(event.emailAddress) + is Event.PasswordChanged -> changePassword(event.password) + is Event.ResultApprovalChanged -> changeConfigurationApproval(event.confirmed) + is Event.OnOAuthResult -> onOAuthResult(event.result) Event.OnNextClicked -> onNext() + Event.OnBackClicked -> onBack() + Event.OnRetryClicked -> onRetry() + Event.OnEditConfigurationClicked -> { navigateNext(isAutomaticConfig = false) } @@ -95,7 +101,9 @@ internal class AccountAutoDiscoveryViewModel( } ConfigStep.PASSWORD -> submitPassword() + ConfigStep.OAUTH -> Unit + ConfigStep.MANUAL_SETUP -> navigateNext(isAutomaticConfig = false) } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AutoDiscoveryStringMapper.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AutoDiscoveryStringMapper.kt index eed9858d4cc..96b18ec2074 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AutoDiscoveryStringMapper.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AutoDiscoveryStringMapper.kt @@ -30,6 +30,7 @@ internal fun AccountAutoDiscoveryContract.Error.toAutoDiscoveryErrorString(resou internal fun ValidationError.toAutoDiscoveryValidationErrorString(resources: Resources): String { return when (this) { is ValidateEmailAddress.ValidateEmailAddressError -> toEmailAddressErrorString(resources) + is ValidatePassword.ValidatePasswordError -> toPasswordErrorString(resources) is ValidateConfigurationApproval.ValidateConfigurationApprovalError -> toConfigurationApprovalErrorString( diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/display/DisplayOptionsViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/display/DisplayOptionsViewModel.kt index c36bd19c5ce..a41be4d9cf2 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/display/DisplayOptionsViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/display/DisplayOptionsViewModel.kt @@ -46,6 +46,7 @@ internal class DisplayOptionsViewModel( } Event.OnNextClicked -> submit() + Event.OnBackClicked -> navigateBack() } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/sync/SyncOptionsViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/sync/SyncOptionsViewModel.kt index c18d53481da..bda36c7db25 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/sync/SyncOptionsViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/options/sync/SyncOptionsViewModel.kt @@ -38,6 +38,7 @@ internal class SyncOptionsViewModel( } Event.OnNextClicked -> submit() + Event.OnBackClicked -> navigateBack() } } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersStringMapper.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersStringMapper.kt index 11a3f437cd2..27985c8bf48 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersStringMapper.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersStringMapper.kt @@ -13,7 +13,9 @@ internal fun SpecialFolderOption.toResourceString(resources: Resources) = when ( noneString } } + is SpecialFolderOption.Regular -> remoteFolder.displayName + is SpecialFolderOption.Special -> { if (isAutomatic) { resources.getString(R.string.account_setup_special_folders_folder_automatic, remoteFolder.displayName) diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersViewModel.kt index 6b39ad0e507..c3a1334df2b 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/specialfolders/SpecialFoldersViewModel.kt @@ -31,9 +31,7 @@ class SpecialFoldersViewModel( override fun event(event: Event) { when (event) { Event.LoadSpecialFolderOptions -> handleOneTimeEvent(event, ::onLoadSpecialFolderOptions) - is FormEvent -> onFormEvent(event) - Event.OnNextClicked -> onNextClicked() Event.OnBackClicked -> onBackClicked() Event.OnRetryClicked -> onRetryClicked() diff --git a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/serializer/ServerSettingsDtoSerializer.kt b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/serializer/ServerSettingsDtoSerializer.kt index 1fb8d985fa1..37dfbd7056c 100644 --- a/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/serializer/ServerSettingsDtoSerializer.kt +++ b/feature/account/storage/legacy/src/main/kotlin/net/thunderbird/feature/account/storage/legacy/serializer/ServerSettingsDtoSerializer.kt @@ -58,13 +58,21 @@ private class ServerSettingsAdapter : JsonAdapter() { while (reader.hasNext()) { when (reader.selectName(JSON_KEYS)) { 0 -> type = reader.nextString() + 1 -> host = reader.nextString() + 2 -> port = reader.nextInt() + 3 -> connectionSecurity = ConnectionSecurity.valueOf(reader.nextString()) + 4 -> authenticationType = AuthType.valueOf(reader.nextString()) + 5 -> username = reader.nextString() + 6 -> password = reader.nextStringOrNull() + 7 -> clientCertificateAlias = reader.nextStringOrNull() + else -> { val key = reader.nextName() val value = reader.nextStringOrNull() diff --git a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt index 36c58c81dbb..18d7b7ddae7 100644 --- a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt +++ b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt @@ -20,6 +20,7 @@ internal class RealAutoconfigFetcher( is SuccessResponse -> { parseSettings(fetchResult, email, autoconfigUrl) } + is ErrorResponse -> AutoDiscoveryResult.NoUsableSettingsFound } } catch (e: IOException) { diff --git a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt index 6e35f4ab441..a8f1634f832 100644 --- a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt +++ b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt @@ -66,6 +66,7 @@ private class ClientConfigParser( "clientConfig" -> { result = parseClientConfig() } + else -> skipElement() } } @@ -87,6 +88,7 @@ private class ClientConfigParser( "emailProvider" -> { result = parseEmailProvider() } + else -> skipElement() } } @@ -117,16 +119,19 @@ private class ClientConfigParser( domainFound = true } } + "incomingServer" -> { parseServer("imap", ::createImapServerSettings)?.let { serverSettings -> incomingServerSettings.add(serverSettings) } } + "outgoingServer" -> { parseServer("smtp", ::createSmtpServerSettings)?.let { serverSettings -> outgoingServerSettings.add(serverSettings) } } + else -> { skipElement() } @@ -226,8 +231,11 @@ private class ClientConfigParser( private fun String.toAuthenticationType(): AuthenticationType? { return when (this) { "OAuth2" -> OAuth2 + "password-cleartext" -> PasswordCleartext + "password-encrypted" -> PasswordEncrypted + else -> { Log.d("Ignoring unknown 'authentication' value '$this'") null @@ -253,9 +261,11 @@ private class ClientConfigParser( XmlPullParser.END_DOCUMENT -> { parserError("End of document reached while reading element '$tagName'") } + XmlPullParser.END_TAG -> { if (pullParser.name == tagName && pullParser.depth == depth) return } + else -> { block(eventType) } @@ -270,6 +280,7 @@ private class ClientConfigParser( XmlPullParser.TEXT -> { text = pullParser.text } + else -> { parserError("Expected text, but got ${XmlPullParser.TYPES[eventType]}") } diff --git a/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/ContributionError.kt b/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/ContributionError.kt index 1ea67864a5a..aa813c3e897 100644 --- a/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/ContributionError.kt +++ b/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/ContributionError.kt @@ -40,7 +40,9 @@ internal fun ContributionError( modifier = modifier, ) - is ContributionError.UserCancelled -> Unit // could be ignored + is ContributionError.UserCancelled -> Unit + + // could be ignored null -> Unit } } diff --git a/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/list/ContributionListSlice.kt b/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/list/ContributionListSlice.kt index 836df6d9d3e..303861cc368 100644 --- a/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/list/ContributionListSlice.kt +++ b/feature/funding/googleplay/src/main/kotlin/net/thunderbird/feature/funding/googleplay/ui/contribution/list/ContributionListSlice.kt @@ -31,9 +31,7 @@ internal class ContributionListSlice( override fun event(event: Event) = when (event) { is Event.TypeClicked -> onTypeClicked(event.type) - is Event.ItemClicked -> onItemClicked(event.contributionId) - Event.RetryClicked -> onRetryClicked() } diff --git a/feature/mail/message/export/impl-eml/src/commonMain/kotlin/net/thunderbird/feature/mail/message/export/eml/EmlMessageExporter.kt b/feature/mail/message/export/impl-eml/src/commonMain/kotlin/net/thunderbird/feature/mail/message/export/eml/EmlMessageExporter.kt index 97d04732ff7..c58ff8049fa 100644 --- a/feature/mail/message/export/impl-eml/src/commonMain/kotlin/net/thunderbird/feature/mail/message/export/eml/EmlMessageExporter.kt +++ b/feature/mail/message/export/impl-eml/src/commonMain/kotlin/net/thunderbird/feature/mail/message/export/eml/EmlMessageExporter.kt @@ -18,6 +18,7 @@ class EmlMessageExporter( val outcome = fileManager.copy(sourceUri = sourceUri, destinationUri = destinationUri) return when (outcome) { is Outcome.Success -> Outcome.Success(Unit) + is Outcome.Failure -> Outcome.Failure( error = mapError(outcome.error), cause = outcome.cause, @@ -33,8 +34,11 @@ class EmlMessageExporter( error.uri, error.message, ) + is FileOperationError.ReadFailed -> MessageExportError.Io(error.message) + is FileOperationError.WriteFailed -> MessageExportError.Io(error.message) + is FileOperationError.Unknown -> MessageExportError.Unknown(error.message) } } diff --git a/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/molecule/MessageItemAvatarCircle.kt b/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/molecule/MessageItemAvatarCircle.kt index 2adc5f80adb..1aa25203386 100644 --- a/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/molecule/MessageItemAvatarCircle.kt +++ b/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/molecule/MessageItemAvatarCircle.kt @@ -67,6 +67,7 @@ fun MessageItemAvatarCircle( ) is Avatar.Monogram -> TextTitleSmall(text = avatar.value) + is Avatar.Image -> RemoteImage( url = avatar.url, placeholder = { diff --git a/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/organism/MessageItem.kt b/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/organism/MessageItem.kt index 328c2418966..49b6e2145d8 100644 --- a/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/organism/MessageItem.kt +++ b/feature/mail/message/list/api/src/main/kotlin/net/thunderbird/feature/mail/message/list/ui/component/organism/MessageItem.kt @@ -210,9 +210,7 @@ private fun LeadingElements( ) { when (configuration.badgeStyle) { MessageBadgeStyle.New -> NewMessageBadge() - MessageBadgeStyle.Unread -> UnreadMessageBadge() - null -> Spacer(Modifier.width(MESSAGE_BADGE_SIZE.dp)) } AnimatedContent(targetState = selected) { selected -> diff --git a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/component/MessageItemSwipeBackground.kt b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/component/MessageItemSwipeBackground.kt index 685b43612ac..e64f116d07e 100644 --- a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/component/MessageItemSwipeBackground.kt +++ b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/component/MessageItemSwipeBackground.kt @@ -92,15 +92,22 @@ private fun rememberSwipeActionColorRoles(action: SwipeAction, toggled: Boolean) // TODO: move these colours to use the design system whenever we have them available. val actionAttrColor = when (action) { SwipeAction.None -> return@remember null + SwipeAction.ToggleSelection -> R.attr.messageListSwipeSelectColor + SwipeAction.ToggleRead -> R.attr.messageListSwipeToggleReadColor + SwipeAction.ToggleStar -> R.attr.messageListSwipeToggleStarColor + SwipeAction.Archive -> R.attr.messageListSwipeArchiveColor + SwipeAction.ArchiveDisabled, SwipeAction.ArchiveSetupArchiveFolder -> null SwipeAction.Delete -> R.attr.messageListSwipeDeleteColor + SwipeAction.Spam -> R.attr.messageListSwipeSpamColor + SwipeAction.Move -> R.attr.messageListSwipeMoveColor } val targetColor = if (actionAttrColor != null) { diff --git a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialog.kt b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialog.kt index 549c0a03cf6..4e75e71838e 100644 --- a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialog.kt +++ b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialog.kt @@ -94,7 +94,9 @@ internal fun SetupArchiveFolderDialog( BasicDialog( headlineText = when (state) { is State.ChooseArchiveFolder -> stringResource(R.string.setup_archive_folder_dialog_set_archive_folder) + is State.CreateArchiveFolder -> stringResource(R.string.setup_archive_folder_dialog_create_new_folder) + is State.EmailCantBeArchived -> stringResource(R.string.setup_archive_folder_dialog_email_can_not_be_archived) }, diff --git a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialogViewModel.kt b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialogViewModel.kt index 4b37d27cd3b..e134fdaf827 100644 --- a/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialogViewModel.kt +++ b/feature/mail/message/list/internal/src/main/kotlin/net/thunderbird/feature/mail/message/list/internal/ui/dialog/SetupArchiveFolderDialogViewModel.kt @@ -42,15 +42,10 @@ internal class SetupArchiveFolderDialogViewModel( override fun event(event: Event) { when (event) { Event.MoveNext -> onNext(state = state.value) - Event.OnDoneClicked -> onDoneClicked(state = state.value) - Event.OnDismissClicked -> onDismissClicked() - is Event.OnDoNotShowDialogAgainChanged -> onDoNotShowDialogAgainChanged(isChecked = event.isChecked) - is Event.OnCreateFolderClicked -> onCreateFolderClicked(newFolderName = event.newFolderName) - is Event.OnFolderSelected -> onFolderSelected(folder = event.folder) } } diff --git a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeBackendFolderUpdater.kt b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeBackendFolderUpdater.kt index 76d655e9ff9..a4f23231d35 100644 --- a/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeBackendFolderUpdater.kt +++ b/feature/mail/message/list/internal/src/test/kotlin/net/thunderbird/feature/mail/message/list/internal/fakes/FakeBackendFolderUpdater.kt @@ -12,7 +12,9 @@ internal open class FakeBackendFolderUpdater( override fun createFolders(folders: List): Set { return when { exception != null -> throw exception + returnEmptySetWhenCreatingFolders -> emptySet() + else -> ids.apply { var last = ids.lastOrNull() ?: 0 addAll(folders.map { ++last }) diff --git a/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/payload/IntValueMapper.kt b/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/payload/IntValueMapper.kt index e75df216958..45f7ac7175c 100644 --- a/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/payload/IntValueMapper.kt +++ b/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/payload/IntValueMapper.kt @@ -25,9 +25,14 @@ internal fun Int.toOutgoingServerProtocol(): OutgoingServerProtocol { internal fun Int.toConnectionSecurity(): ConnectionSecurity { return when (this) { 0 -> ConnectionSecurity.Plain - 1 -> ConnectionSecurity.AlwaysStartTls // TryStartTls, but we treat it like AlwaysStartTls + + // TryStartTls, but we treat it like AlwaysStartTls + 1 -> ConnectionSecurity.AlwaysStartTls + 2 -> ConnectionSecurity.AlwaysStartTls + 3 -> ConnectionSecurity.Tls + else -> throw IllegalArgumentException("Unsupported value: $this") } } diff --git a/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/ui/QrCodeScannerContent.kt b/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/ui/QrCodeScannerContent.kt index 908c96c7f57..5c180749b98 100644 --- a/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/ui/QrCodeScannerContent.kt +++ b/feature/migration/qrcode/src/main/kotlin/app/k9mail/feature/migration/qrcode/ui/QrCodeScannerContent.kt @@ -27,6 +27,7 @@ internal fun QrCodeScannerContent( UiPermissionState.Unknown -> { // Display empty surface while we're waiting for the camera permission request to return a result } + UiPermissionState.Granted -> { QrCodeScannerView( cameraUseCasesProvider = cameraUseCasesProvider, @@ -34,11 +35,13 @@ internal fun QrCodeScannerContent( onDoneClick = { onEvent(Event.DoneClicked) }, ) } + UiPermissionState.Denied -> { PermissionDeniedContent( onGoToSettingsClick = { onEvent(Event.GoToSettingsClicked) }, ) } + UiPermissionState.Waiting -> { // We've launched Android's app info screen and are now waiting for the user to return to our app. diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt index 35983a31d32..9e0c606aaaf 100644 --- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt +++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt @@ -25,15 +25,20 @@ internal fun DrawerView( val (state, dispatch) = viewModel.observe { effect -> when (effect) { is Effect.OpenAccount -> openAccount(effect.accountId) + is Effect.OpenFolder -> openFolder( effect.accountId, effect.folderId, ) Effect.OpenUnifiedFolder -> openUnifiedFolder() + is Effect.OpenManageFolders -> openManageFolders() + is Effect.OpenSettings -> openSettings() + Effect.OpenAddAccount -> openAddAccount() + Effect.CloseDrawer -> closeDrawer() } } diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt index e7f451f8aac..2780586f7cd 100644 --- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt +++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewModel.kt @@ -153,10 +153,13 @@ internal class DrawerViewModel( override fun event(event: Event) { when (event) { is Event.SelectAccount -> selectAccount(event.accountId) + is Event.SelectFolder -> selectFolder(event.folderId) is Event.OnAccountClick -> openAccount(event.account) + is Event.OnFolderClick -> openFolder(event.folder) + is Event.OnAccountViewClick -> { openAccount( state.value.accounts.nextOrFirst(event.account), @@ -176,9 +179,13 @@ internal class DrawerViewModel( } Event.OnManageFoldersClick -> emitEffect(Effect.OpenManageFolders) + Event.OnSettingsClick -> emitEffect(Effect.OpenSettings) + Event.OnSyncAccount -> onSyncAccount() + Event.OnSyncAllAccounts -> onSyncAllAccounts() + Event.OnAddAccountClick -> emitEffect(Effect.OpenAddAccount) } } diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt index 50c02923acf..9dcceea6256 100644 --- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt +++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/folder/FolderListItem.kt @@ -163,6 +163,7 @@ private fun mapFolderName( .removePrefix("$parentPrefix${displayFolder.pathDelimiter}") is UnifiedDisplayFolder -> mapUnifiedFolderName(displayFolder) + else -> throw IllegalArgumentException("Unknown display folder: $displayFolder") } } diff --git a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt index eb984a021d8..c75a143b388 100644 --- a/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt +++ b/feature/notification/api/src/androidMain/kotlin/net/thunderbird/feature/notification/api/ui/action/ResolvedNotificationActionButton.kt @@ -25,6 +25,7 @@ internal fun ResolvedNotificationActionButton( NotificationActionButton( text = when (val labelResource = action.labelResource) { null if action.label.isNotEmpty() -> action.label + null -> error( "You must specify at least one of labelResource or label in ${ action::class.simpleName diff --git a/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt b/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt index de9a431dfde..6e8b28db565 100644 --- a/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt +++ b/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt @@ -100,6 +100,7 @@ fun OnboardingNavHost( } AccountSetupRoute.ThundermailScanQrCode -> onFinish(OnboardingRoute.ThundermailScanQrCode) + AccountSetupRoute.ThundermailSignIn -> onFinish(OnboardingRoute.ThundermailSignIn) } }, diff --git a/feature/onboarding/permissions/src/main/kotlin/app/k9mail/feature/onboarding/permissions/ui/PermissionBox.kt b/feature/onboarding/permissions/src/main/kotlin/app/k9mail/feature/onboarding/permissions/ui/PermissionBox.kt index a46c9628538..69d75f445da 100644 --- a/feature/onboarding/permissions/src/main/kotlin/app/k9mail/feature/onboarding/permissions/ui/PermissionBox.kt +++ b/feature/onboarding/permissions/src/main/kotlin/app/k9mail/feature/onboarding/permissions/ui/PermissionBox.kt @@ -99,6 +99,7 @@ private fun IconWithPermissionStateOverlay( when (permissionState) { UiPermissionState.Unknown -> Unit + UiPermissionState.Granted -> { Icon( imageVector = Icons.Filled.CheckCircle, diff --git a/feature/search/impl-legacy/src/commonMain/kotlin/net/thunderbird/feature/search/legacy/SearchConditionTreeNode.kt b/feature/search/impl-legacy/src/commonMain/kotlin/net/thunderbird/feature/search/legacy/SearchConditionTreeNode.kt index 29f5f4fb8e3..cdd3d5c20df 100644 --- a/feature/search/impl-legacy/src/commonMain/kotlin/net/thunderbird/feature/search/legacy/SearchConditionTreeNode.kt +++ b/feature/search/impl-legacy/src/commonMain/kotlin/net/thunderbird/feature/search/legacy/SearchConditionTreeNode.kt @@ -102,6 +102,7 @@ class SearchConditionTreeNode private constructor( } Operator.CONDITION -> condition.toString() + Operator.NOT -> "(NOT ${left?.toString() ?: "null"})" } } diff --git a/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/SettingsImportFragment.kt b/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/SettingsImportFragment.kt index 5e05dca35f8..c1de167ebdd 100644 --- a/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/SettingsImportFragment.kt +++ b/feature/settings/import/src/main/kotlin/app/k9mail/feature/settings/import/ui/SettingsImportFragment.kt @@ -125,6 +125,7 @@ class SettingsImportFragment : Fragment() { } ButtonState.INVISIBLE -> importButton.isInvisible = true + ButtonState.GONE -> importButton.isGone = true } diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/domain/CreateAccountStateUseCase.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/domain/CreateAccountStateUseCase.kt index 76271894bcf..88a890297d5 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/domain/CreateAccountStateUseCase.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/domain/CreateAccountStateUseCase.kt @@ -31,8 +31,11 @@ internal class CreateAccountStateUseCase( return when { authState == null && error != null -> Outcome.failure(error) + idToken == null -> Outcome.failure(Failure.IdTokenMissing) + emailAddress.isNullOrBlank() -> Outcome.failure(Failure.MissingEmail(idToken.toString())) + else -> { val name = (idToken.additionalClaims["name"] ?: idToken.additionalClaims["given_name"])?.toString() val state = AccountState( diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt index 51d84d26160..b6f4452bc96 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/navigation/DefaultThundermailNavigation.kt @@ -74,6 +74,7 @@ class DefaultThundermailNavigation : ThundermailNavigation { ) AccountSetupRoute.ThundermailScanQrCode -> onFinish(ThundermailRoute.ScanQrCode) + AccountSetupRoute.ThundermailSignIn -> onFinish(ThundermailRoute.SignInWithThundermail) } }, @@ -109,6 +110,7 @@ private fun ThundermailOAuthRedirectScreen( val (state, dispatch) = viewModel.observe { effect -> when (effect) { is ThundermailContract.Effect.LaunchOAuth -> oAuthLauncher.launch(effect.intent) + is ThundermailContract.Effect.NavigateToIncomingServerSettings -> onFinish(ThundermailRoute.IncomingSettings) } diff --git a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt index 35dd25ba253..66af2c354a3 100644 --- a/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt +++ b/feature/thundermail/internal/common/src/main/kotlin/net/thunderbird/feature/thundermail/internal/common/ui/ThundermailOAuthViewModel.kt @@ -65,6 +65,7 @@ internal class ThundermailOAuthViewModel( ) AccountOAuthContract.Effect.NavigateBack -> Unit + is AccountOAuthContract.Effect.NavigateNext -> handleNavigateNext(effect) } } diff --git a/feature/widget/message-list-glance/src/main/kotlin/net/thunderbird/feature/widget/message/list/MessageListLoader.kt b/feature/widget/message-list-glance/src/main/kotlin/net/thunderbird/feature/widget/message/list/MessageListLoader.kt index ebfef71a5fd..e4b58c1b2c5 100644 --- a/feature/widget/message-list-glance/src/main/kotlin/net/thunderbird/feature/widget/message/list/MessageListLoader.kt +++ b/feature/widget/message-list-glance/src/main/kotlin/net/thunderbird/feature/widget/message/list/MessageListLoader.kt @@ -68,11 +68,18 @@ internal class MessageListLoader( private fun buildSortOrder(config: MessageListConfig): String { val sortColumn = when (config.sortType) { SortType.SORT_ARRIVAL -> MessageColumns.INTERNAL_DATE + SortType.SORT_ATTACHMENT -> "(${MessageColumns.ATTACHMENT_COUNT} < 1)" + SortType.SORT_FLAGGED -> "(${MessageColumns.FLAGGED} != 1)" - SortType.SORT_SENDER -> MessageColumns.SENDER_LIST // FIXME + + // FIXME + SortType.SORT_SENDER -> MessageColumns.SENDER_LIST + SortType.SORT_SUBJECT -> "${MessageColumns.SUBJECT} COLLATE NOCASE" + SortType.SORT_UNREAD -> MessageColumns.READ + SortType.SORT_DATE -> MessageColumns.DATE } diff --git a/feature/widget/message-list/src/main/kotlin/app/k9mail/feature/widget/message/list/MessageListLoader.kt b/feature/widget/message-list/src/main/kotlin/app/k9mail/feature/widget/message/list/MessageListLoader.kt index 1972fc27f95..0d428274f8c 100644 --- a/feature/widget/message-list/src/main/kotlin/app/k9mail/feature/widget/message/list/MessageListLoader.kt +++ b/feature/widget/message-list/src/main/kotlin/app/k9mail/feature/widget/message/list/MessageListLoader.kt @@ -68,11 +68,18 @@ internal class MessageListLoader( private fun buildSortOrder(config: MessageListConfig): String { val sortColumn = when (config.sortType) { SortType.SORT_ARRIVAL -> MessageColumns.INTERNAL_DATE + SortType.SORT_ATTACHMENT -> "(${MessageColumns.ATTACHMENT_COUNT} < 1)" + SortType.SORT_FLAGGED -> "(${MessageColumns.FLAGGED} != 1)" - SortType.SORT_SENDER -> MessageColumns.SENDER_LIST // FIXME + + // FIXME + SortType.SORT_SENDER -> MessageColumns.SENDER_LIST + SortType.SORT_SUBJECT -> "${MessageColumns.SUBJECT} COLLATE NOCASE" + SortType.SORT_UNREAD -> MessageColumns.READ + SortType.SORT_DATE -> MessageColumns.DATE } diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetConfigurationFragment.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetConfigurationFragment.kt index 12b9d8a1b9d..1ecabf57a54 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetConfigurationFragment.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetConfigurationFragment.kt @@ -106,6 +106,7 @@ class UnreadWidgetConfigurationFragment : PreferenceFragmentCompat() { } true } + else -> false } } diff --git a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt index c03555d7449..85e6133e19b 100644 --- a/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt +++ b/feature/widget/unread/src/main/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProvider.kt @@ -63,6 +63,7 @@ class UnreadWidgetDataProvider( title = coreResourceProvider.searchUnifiedFoldersTitle(), detail = coreResourceProvider.searchUnifiedFoldersDetail(), ) + else -> throw AssertionError("SearchAccount expected") }