From a91510b9730db9745d1a513d97dedeac15699a32 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 22:56:34 +0800 Subject: [PATCH 01/13] Rename examples/ to app/ and cli/ for production app structure - examples/app -> app/shared, examples/androidApp -> app/android, examples/desktopApp -> app/desktop, examples/webApp -> app/web, examples/iosApp -> app/ios, examples/cli -> cli - Rename packages: com.linroid.kdown.examples -> com.linroid.kdown.app (cli uses com.linroid.kdown.cli) - Update Gradle module names, namespaces, applicationId, and iOS framework name (ExamplesApp -> KDownApp) Co-Authored-By: Claude Opus 4.6 --- .../android}/build.gradle.kts | 6 +- .../android}/src/main/AndroidManifest.xml | 2 +- .../com/linroid/kdown/app}/MainActivity.kt | 8 +- .../android}/src/main/res/values/strings.xml | 0 .../desktop}/build.gradle.kts | 6 +- .../kotlin/com/linroid/kdown/app}/main.kt | 10 +- .../ios}/iosApp/ContentView.swift | 2 +- .../iosApp => app/ios}/iosApp/iOSApp.swift | 2 +- {examples/app => app/shared}/build.gradle.kts | 4 +- .../kotlin/com/linroid/kdown/app}/App.kt | 10 +- .../kdown/app}/backend/BackendConfig.kt | 2 +- .../kdown/app}/backend/BackendEntry.kt | 2 +- .../kdown/app}/backend/BackendFactory.kt | 2 +- .../kdown/app}/backend/BackendManager.kt | 2 +- .../kdown/app}/backend/LocalServerHandle.kt | 2 +- .../linroid/kdown/app}/backend/ServerState.kt | 2 +- .../linroid/kdown/app}/BackendManagerTest.kt | 6 +- .../linroid/kdown/app}/FakeBackendFactory.kt | 8 +- .../com/linroid/kdown/app}/FakeKDownApi.kt | 2 +- .../linroid/kdown/app}/MainViewController.kt | 6 +- {examples/webApp => app/web}/build.gradle.kts | 2 +- .../kotlin/com/linroid/kdown/app}/main.kt | 6 +- .../web}/src/wasmJsMain/resources/index.html | 0 {examples/cli => cli}/build.gradle.kts | 2 +- .../kotlin/com/linroid/kdown/cli}/Main.kt | 2 +- .../architecture/multi-backend-example-app.md | 603 ++++++++++++++++++ settings.gradle.kts | 12 +- 27 files changed, 657 insertions(+), 54 deletions(-) rename {examples/androidApp => app/android}/build.gradle.kts (85%) rename {examples/androidApp => app/android}/src/main/AndroidManifest.xml (91%) rename {examples/androidApp/src/main/kotlin/com/linroid/kdown/examples => app/android/src/main/kotlin/com/linroid/kdown/app}/MainActivity.kt (87%) rename {examples/androidApp => app/android}/src/main/res/values/strings.xml (100%) rename {examples/desktopApp => app/desktop}/build.gradle.kts (82%) rename {examples/desktopApp/src/main/kotlin/com/linroid/kdown/examples => app/desktop/src/main/kotlin/com/linroid/kdown/app}/main.kt (83%) rename {examples/iosApp => app/ios}/iosApp/ContentView.swift (95%) rename {examples/iosApp => app/ios}/iosApp/iOSApp.swift (86%) rename {examples/app => app/shared}/build.gradle.kts (96%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/App.kt (99%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/BackendConfig.kt (85%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/BackendEntry.kt (87%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/BackendFactory.kt (98%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/BackendManager.kt (99%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/LocalServerHandle.kt (92%) rename {examples/app/src/commonMain/kotlin/com/linroid/kdown/examples => app/shared/src/commonMain/kotlin/com/linroid/kdown/app}/backend/ServerState.kt (81%) rename {examples/app/src/commonTest/kotlin/com/linroid/kdown/examples => app/shared/src/commonTest/kotlin/com/linroid/kdown/app}/BackendManagerTest.kt (99%) rename {examples/app/src/commonTest/kotlin/com/linroid/kdown/examples => app/shared/src/commonTest/kotlin/com/linroid/kdown/app}/FakeBackendFactory.kt (86%) rename {examples/app/src/commonTest/kotlin/com/linroid/kdown/examples => app/shared/src/commonTest/kotlin/com/linroid/kdown/app}/FakeKDownApi.kt (97%) rename {examples/app/src/iosMain/kotlin/com/linroid/kdown/examples => app/shared/src/iosMain/kotlin/com/linroid/kdown/app}/MainViewController.kt (78%) rename {examples/webApp => app/web}/build.gradle.kts (93%) rename {examples/webApp/src/wasmJsMain/kotlin/com/linroid/kdown/examples => app/web/src/wasmJsMain/kotlin/com/linroid/kdown/app}/main.kt (78%) rename {examples/webApp => app/web}/src/wasmJsMain/resources/index.html (100%) rename {examples/cli => cli}/build.gradle.kts (80%) rename {examples/cli/src/main/kotlin/com/linroid/kdown/examples => cli/src/main/kotlin/com/linroid/kdown/cli}/Main.kt (99%) create mode 100644 docs/architecture/multi-backend-example-app.md diff --git a/examples/androidApp/build.gradle.kts b/app/android/build.gradle.kts similarity index 85% rename from examples/androidApp/build.gradle.kts rename to app/android/build.gradle.kts index 724db5f3..366eb58c 100644 --- a/examples/androidApp/build.gradle.kts +++ b/app/android/build.gradle.kts @@ -4,11 +4,11 @@ plugins { } android { - namespace = "com.linroid.kdown.examples.android" + namespace = "com.linroid.kdown.app.android" compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { - applicationId = "com.linroid.kdown.examples.android" + applicationId = "com.linroid.kdown.app" minSdk = libs.versions.android.minSdk.get().toInt() targetSdk = libs.versions.android.targetSdk.get().toInt() versionCode = 1 @@ -38,7 +38,7 @@ android { } dependencies { - implementation(projects.examples.app) + implementation(projects.app.shared) implementation(projects.library.sqlite) implementation(projects.server) implementation(libs.androidx.activity.compose) diff --git a/examples/androidApp/src/main/AndroidManifest.xml b/app/android/src/main/AndroidManifest.xml similarity index 91% rename from examples/androidApp/src/main/AndroidManifest.xml rename to app/android/src/main/AndroidManifest.xml index be8ce7e5..387453a9 100644 --- a/examples/androidApp/src/main/AndroidManifest.xml +++ b/app/android/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light.NoActionBar"> diff --git a/examples/androidApp/src/main/kotlin/com/linroid/kdown/examples/MainActivity.kt b/app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt similarity index 87% rename from examples/androidApp/src/main/kotlin/com/linroid/kdown/examples/MainActivity.kt rename to app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt index 53f2470c..285541e2 100644 --- a/examples/androidApp/src/main/kotlin/com/linroid/kdown/examples/MainActivity.kt +++ b/app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import android.os.Bundle import androidx.activity.ComponentActivity @@ -6,9 +6,9 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember -import com.linroid.kdown.examples.backend.BackendFactory -import com.linroid.kdown.examples.backend.BackendManager -import com.linroid.kdown.examples.backend.LocalServerHandle +import com.linroid.kdown.app.backend.BackendFactory +import com.linroid.kdown.app.backend.BackendManager +import com.linroid.kdown.app.backend.LocalServerHandle import com.linroid.kdown.server.KDownServer import com.linroid.kdown.server.KDownServerConfig import com.linroid.kdown.sqlite.DriverFactory diff --git a/examples/androidApp/src/main/res/values/strings.xml b/app/android/src/main/res/values/strings.xml similarity index 100% rename from examples/androidApp/src/main/res/values/strings.xml rename to app/android/src/main/res/values/strings.xml diff --git a/examples/desktopApp/build.gradle.kts b/app/desktop/build.gradle.kts similarity index 82% rename from examples/desktopApp/build.gradle.kts rename to app/desktop/build.gradle.kts index 92f13612..c8f59bdd 100644 --- a/examples/desktopApp/build.gradle.kts +++ b/app/desktop/build.gradle.kts @@ -7,7 +7,7 @@ plugins { } dependencies { - implementation(projects.examples.app) + implementation(projects.app.shared) implementation(projects.server) implementation(projects.library.ktor) implementation(projects.library.sqlite) @@ -17,11 +17,11 @@ dependencies { compose.desktop { application { - mainClass = "com.linroid.kdown.examples.MainKt" + mainClass = "com.linroid.kdown.app.MainKt" nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "KDown Examples" + packageName = "KDown" packageVersion = "1.0.0" } } diff --git a/examples/desktopApp/src/main/kotlin/com/linroid/kdown/examples/main.kt b/app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt similarity index 83% rename from examples/desktopApp/src/main/kotlin/com/linroid/kdown/examples/main.kt rename to app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt index cefa3ae2..f73388bc 100644 --- a/examples/desktopApp/src/main/kotlin/com/linroid/kdown/examples/main.kt +++ b/app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt @@ -1,12 +1,12 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import com.linroid.kdown.examples.backend.BackendFactory -import com.linroid.kdown.examples.backend.BackendManager -import com.linroid.kdown.examples.backend.LocalServerHandle +import com.linroid.kdown.app.backend.BackendFactory +import com.linroid.kdown.app.backend.BackendManager +import com.linroid.kdown.app.backend.LocalServerHandle import com.linroid.kdown.server.KDownServer import com.linroid.kdown.server.KDownServerConfig import com.linroid.kdown.sqlite.DriverFactory @@ -42,7 +42,7 @@ fun main() = application { } Window( onCloseRequest = ::exitApplication, - title = "KDown Examples" + title = "KDown" ) { App(backendManager) } diff --git a/examples/iosApp/iosApp/ContentView.swift b/app/ios/iosApp/ContentView.swift similarity index 95% rename from examples/iosApp/iosApp/ContentView.swift rename to app/ios/iosApp/ContentView.swift index 0f57c4fa..ed6bc758 100644 --- a/examples/iosApp/iosApp/ContentView.swift +++ b/app/ios/iosApp/ContentView.swift @@ -1,6 +1,6 @@ import UIKit import SwiftUI -import ExamplesApp +import KDownApp struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { diff --git a/examples/iosApp/iosApp/iOSApp.swift b/app/ios/iosApp/iOSApp.swift similarity index 86% rename from examples/iosApp/iosApp/iOSApp.swift rename to app/ios/iosApp/iOSApp.swift index b3b19723..e83eb218 100644 --- a/examples/iosApp/iosApp/iOSApp.swift +++ b/app/ios/iosApp/iOSApp.swift @@ -1,5 +1,5 @@ import SwiftUI -import ExamplesApp +import KDownApp @main struct iOSApp: App { diff --git a/examples/app/build.gradle.kts b/app/shared/build.gradle.kts similarity index 96% rename from examples/app/build.gradle.kts rename to app/shared/build.gradle.kts index 6da2d6eb..0ab20822 100644 --- a/examples/app/build.gradle.kts +++ b/app/shared/build.gradle.kts @@ -10,7 +10,7 @@ plugins { kotlin { androidLibrary { - namespace = "com.linroid.kdown.examples.app" + namespace = "com.linroid.kdown.app.shared" compileSdk = libs.versions.android.compileSdk.get().toInt() minSdk = libs.versions.android.minSdk.get().toInt() @@ -24,7 +24,7 @@ kotlin { iosSimulatorArm64() ).forEach { iosTarget -> iosTarget.binaries.framework { - baseName = "ExamplesApp" + baseName = "KDownApp" isStatic = true } } diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/App.kt similarity index 99% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/App.kt index 18962a2b..641fc1c7 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/App.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/App.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -72,11 +72,11 @@ import com.linroid.kdown.api.DownloadState import com.linroid.kdown.api.DownloadTask import com.linroid.kdown.api.KDownApi import com.linroid.kdown.api.SpeedLimit -import com.linroid.kdown.examples.backend.BackendConfig -import com.linroid.kdown.examples.backend.BackendEntry +import com.linroid.kdown.app.backend.BackendConfig +import com.linroid.kdown.app.backend.BackendEntry import com.linroid.kdown.remote.ConnectionState -import com.linroid.kdown.examples.backend.BackendManager -import com.linroid.kdown.examples.backend.ServerState +import com.linroid.kdown.app.backend.BackendManager +import com.linroid.kdown.app.backend.ServerState import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendConfig.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendConfig.kt similarity index 85% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendConfig.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendConfig.kt index 62a85025..e6b875a5 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendConfig.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendConfig.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend sealed class BackendConfig { data object Embedded : BackendConfig() diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendEntry.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendEntry.kt similarity index 87% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendEntry.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendEntry.kt index d6f35b69..7517abd9 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendEntry.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendEntry.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend import com.linroid.kdown.remote.ConnectionState import kotlinx.coroutines.flow.StateFlow diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendFactory.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendFactory.kt similarity index 98% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendFactory.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendFactory.kt index fa9e770d..40c8e0b6 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendFactory.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendFactory.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend import com.linroid.kdown.api.KDownApi import com.linroid.kdown.core.DownloadConfig diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendManager.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendManager.kt similarity index 99% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendManager.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendManager.kt index 616b23fb..4a05db69 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/BackendManager.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/BackendManager.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend import com.linroid.kdown.api.DownloadRequest import com.linroid.kdown.api.DownloadTask diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/LocalServerHandle.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/LocalServerHandle.kt similarity index 92% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/LocalServerHandle.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/LocalServerHandle.kt index 1b925601..ab42c848 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/LocalServerHandle.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/LocalServerHandle.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend /** * Represents a running local server. Defined in commonMain so diff --git a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/ServerState.kt b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/ServerState.kt similarity index 81% rename from examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/ServerState.kt rename to app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/ServerState.kt index 78481bf6..707fc6bc 100644 --- a/examples/app/src/commonMain/kotlin/com/linroid/kdown/examples/backend/ServerState.kt +++ b/app/shared/src/commonMain/kotlin/com/linroid/kdown/app/backend/ServerState.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples.backend +package com.linroid.kdown.app.backend /** State of the optional HTTP server exposing the embedded backend. */ sealed class ServerState { diff --git a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/BackendManagerTest.kt b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/BackendManagerTest.kt similarity index 99% rename from examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/BackendManagerTest.kt rename to app/shared/src/commonTest/kotlin/com/linroid/kdown/app/BackendManagerTest.kt index 5c917f9f..a4561f88 100644 --- a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/BackendManagerTest.kt +++ b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/BackendManagerTest.kt @@ -1,8 +1,8 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app -import com.linroid.kdown.examples.backend.BackendConfig +import com.linroid.kdown.app.backend.BackendConfig import com.linroid.kdown.remote.ConnectionState -import com.linroid.kdown.examples.backend.BackendEntry +import com.linroid.kdown.app.backend.BackendEntry import kotlinx.coroutines.flow.MutableStateFlow import kotlin.test.Test import kotlin.test.assertEquals diff --git a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeBackendFactory.kt b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeBackendFactory.kt similarity index 86% rename from examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeBackendFactory.kt rename to app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeBackendFactory.kt index 9180a643..7953f71e 100644 --- a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeBackendFactory.kt +++ b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeBackendFactory.kt @@ -1,11 +1,11 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import com.linroid.kdown.api.KDownApi -import com.linroid.kdown.examples.backend.BackendConfig +import com.linroid.kdown.app.backend.BackendConfig /** - * A fake [com.linroid.kdown.examples.backend.BackendFactory] for testing - * [com.linroid.kdown.examples.backend.BackendManager]. + * A fake [com.linroid.kdown.app.backend.BackendFactory] for testing + * [com.linroid.kdown.app.backend.BackendManager]. * * Usage: * ``` diff --git a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeKDownApi.kt b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeKDownApi.kt similarity index 97% rename from examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeKDownApi.kt rename to app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeKDownApi.kt index a2009c1d..c2758702 100644 --- a/examples/app/src/commonTest/kotlin/com/linroid/kdown/examples/FakeKDownApi.kt +++ b/app/shared/src/commonTest/kotlin/com/linroid/kdown/app/FakeKDownApi.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import com.linroid.kdown.api.DownloadRequest import com.linroid.kdown.api.DownloadTask diff --git a/examples/app/src/iosMain/kotlin/com/linroid/kdown/examples/MainViewController.kt b/app/shared/src/iosMain/kotlin/com/linroid/kdown/app/MainViewController.kt similarity index 78% rename from examples/app/src/iosMain/kotlin/com/linroid/kdown/examples/MainViewController.kt rename to app/shared/src/iosMain/kotlin/com/linroid/kdown/app/MainViewController.kt index d9575fa9..a6fe8aed 100644 --- a/examples/app/src/iosMain/kotlin/com/linroid/kdown/examples/MainViewController.kt +++ b/app/shared/src/iosMain/kotlin/com/linroid/kdown/app/MainViewController.kt @@ -1,10 +1,10 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.window.ComposeUIViewController -import com.linroid.kdown.examples.backend.BackendFactory -import com.linroid.kdown.examples.backend.BackendManager +import com.linroid.kdown.app.backend.BackendFactory +import com.linroid.kdown.app.backend.BackendManager import com.linroid.kdown.sqlite.DriverFactory import com.linroid.kdown.sqlite.createSqliteTaskStore diff --git a/examples/webApp/build.gradle.kts b/app/web/build.gradle.kts similarity index 93% rename from examples/webApp/build.gradle.kts rename to app/web/build.gradle.kts index 643571b6..9fcf244b 100644 --- a/examples/webApp/build.gradle.kts +++ b/app/web/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { sourceSets { wasmJsMain.dependencies { - implementation(projects.examples.app) + implementation(projects.app.shared) implementation(libs.compose.ui) implementation(libs.compose.runtime) implementation(libs.compose.foundation) diff --git a/examples/webApp/src/wasmJsMain/kotlin/com/linroid/kdown/examples/main.kt b/app/web/src/wasmJsMain/kotlin/com/linroid/kdown/app/main.kt similarity index 78% rename from examples/webApp/src/wasmJsMain/kotlin/com/linroid/kdown/examples/main.kt rename to app/web/src/wasmJsMain/kotlin/com/linroid/kdown/app/main.kt index 271742da..a2aa505d 100644 --- a/examples/webApp/src/wasmJsMain/kotlin/com/linroid/kdown/examples/main.kt +++ b/app/web/src/wasmJsMain/kotlin/com/linroid/kdown/app/main.kt @@ -1,11 +1,11 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.app import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.window.ComposeViewport -import com.linroid.kdown.examples.backend.BackendFactory -import com.linroid.kdown.examples.backend.BackendManager +import com.linroid.kdown.app.backend.BackendFactory +import com.linroid.kdown.app.backend.BackendManager import kotlinx.browser.document @OptIn(ExperimentalComposeUiApi::class) diff --git a/examples/webApp/src/wasmJsMain/resources/index.html b/app/web/src/wasmJsMain/resources/index.html similarity index 100% rename from examples/webApp/src/wasmJsMain/resources/index.html rename to app/web/src/wasmJsMain/resources/index.html diff --git a/examples/cli/build.gradle.kts b/cli/build.gradle.kts similarity index 80% rename from examples/cli/build.gradle.kts rename to cli/build.gradle.kts index f3fa4f48..b6f3a249 100644 --- a/examples/cli/build.gradle.kts +++ b/cli/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } application { - mainClass.set("com.linroid.kdown.examples.MainKt") + mainClass.set("com.linroid.kdown.cli.MainKt") } dependencies { diff --git a/examples/cli/src/main/kotlin/com/linroid/kdown/examples/Main.kt b/cli/src/main/kotlin/com/linroid/kdown/cli/Main.kt similarity index 99% rename from examples/cli/src/main/kotlin/com/linroid/kdown/examples/Main.kt rename to cli/src/main/kotlin/com/linroid/kdown/cli/Main.kt index 2ff6aacd..a0e29eb2 100644 --- a/examples/cli/src/main/kotlin/com/linroid/kdown/examples/Main.kt +++ b/cli/src/main/kotlin/com/linroid/kdown/cli/Main.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.examples +package com.linroid.kdown.cli import com.linroid.kdown.api.DownloadPriority import com.linroid.kdown.api.DownloadRequest diff --git a/docs/architecture/multi-backend-example-app.md b/docs/architecture/multi-backend-example-app.md new file mode 100644 index 00000000..603a5f2f --- /dev/null +++ b/docs/architecture/multi-backend-example-app.md @@ -0,0 +1,603 @@ +# Multi-Backend Example App Architecture + +## Overview + +The example app (`app/shared`) currently creates a hardcoded `KDown` instance. This +design adds support for three backend modes, all working through the existing `KDownApi` +interface: + +1. **Embedded** (default) -- in-process `KDown` instance, works on all platforms +2. **Remote server** -- connects to an existing KDown daemon via `RemoteKDown` +3. **Local server** -- starts `KDownServer` in-process (JVM/Desktop only) + +Users can configure multiple backends (e.g., several remote servers) and switch between +them. The embedded backend is always present and cannot be removed. + +## Design Principles + +- **`KDownApi` is the only abstraction the UI needs.** No new download interfaces. +- **Backend list model.** Users manage a list of configured backends (like bookmarks). + The embedded backend is always present. Remote servers can be added/removed freely. +- **Lambda injection over expect/actual.** Local server support is JVM-only. Instead + of expect/actual declarations on every platform, use a lambda parameter injected from + the JVM entry point. CommonMain checks `lambda != null` to gate UI visibility. +- **Compose state drives everything.** The UI observes `StateFlow`s; backend switching + is a state change, not a navigation event. + +--- + +## 1. Data Model + +### BackendConfig + +Describes how to create a backend. Sealed class in `commonMain`: + +```kotlin +// app/shared/src/commonMain/.../backend/BackendConfig.kt +package com.linroid.kdown.app.backend + +sealed class BackendConfig { + /** In-process KDown instance. Default on all platforms. */ + data object Embedded : BackendConfig() + + /** Connect to an existing KDown daemon. Works on all platforms. */ + data class Remote( + val host: String, + val port: Int = 8642, + val apiToken: String? = null + ) : BackendConfig() { + val baseUrl: String get() = "http://$host:$port" + } + + /** Start a local KDownServer in-process. JVM only. */ + data class LocalServer( + val port: Int = 8642, + val apiToken: String? = null + ) : BackendConfig() +} +``` + +### BackendType + +Simple enum for UI display logic: + +```kotlin +enum class BackendType { + EMBEDDED, + REMOTE, + LOCAL_SERVER +} +``` + +### BackendEntry + +A configured backend in the list. Combines config with runtime identity and state: + +```kotlin +data class BackendEntry( + val id: String, + val type: BackendType, + val label: String, + val config: BackendConfig, + val connectionState: StateFlow +) +``` + +- `id`: Stable identifier. `"embedded"` for the built-in backend. UUID for user-added + backends. +- `label`: Human-readable display name. "Embedded" for the built-in backend. For remote, + derived from host:port (e.g., "192.168.1.5:8642"). For local server, "Local Server". +- `connectionState`: Per-backend connection status. For embedded, always `Connected`. + For remote, mirrors `RemoteKDown.connectionState`. For local server, `Connected` once + the server is listening. + +### BackendConnectionState + +UI-friendly connection status, unified across backend types: + +```kotlin +sealed class BackendConnectionState { + data object Connected : BackendConnectionState() + data object Connecting : BackendConnectionState() + data class Disconnected(val reason: String? = null) : BackendConnectionState() +} +``` + +This maps from `RemoteKDown.connectionState` for remote backends and is always +`Connected` for embedded/local-server backends. + +### Platform availability (no expect/actual needed) + +Local server support is determined at runtime via lambda injection rather than +expect/actual. See section 3 (BackendFactory) for details. The UI checks +`backendManager.isLocalServerSupported` which is simply `localServerFactory != null`. + +--- + +## 2. BackendManager + +Manages the list of configured backends, the active backend, and lifecycle transitions. +Lives in `commonMain`. + +```kotlin +class BackendManager( + private val factory: BackendFactory +) { + /** All configured backends. Embedded is always first. */ + val backends: StateFlow> + + /** The currently active backend entry. */ + val activeBackend: StateFlow + + /** The KDownApi for the active backend. UI observes this for tasks. */ + val activeApi: StateFlow + + /** + * Switch to a different configured backend by ID. + * Closes the old backend's KDownApi, creates a new one. + * Throws on failure (old backend remains active). + */ + suspend fun switchTo(id: String) + + /** + * Add a new remote server to the backend list. + * Does NOT activate it -- call switchTo() afterward. + * Returns the new BackendEntry. + */ + fun addRemote(host: String, port: Int = 8642, token: String? = null): BackendEntry + + /** + * Remove a backend by ID. Cannot remove the embedded backend. + * If the removed backend is active, switches to embedded first. + */ + suspend fun removeBackend(id: String) + + /** Whether the current platform supports starting a local server. */ + val isLocalServerSupported: Boolean // delegates to factory.isLocalServerSupported + + /** + * Start a local server and add it to the backend list. JVM only. + * Throws UnsupportedOperationException if !isLocalServerSupported. + * Returns the new BackendEntry. + */ + fun addLocalServer(port: Int = 8642, token: String? = null): BackendEntry + + /** Close the active backend and release all resources. */ + fun close() +} +``` + +### Key behaviors + +1. **Embedded is always present.** The backend list is initialized with a single embedded + entry (`id = "embedded"`). It cannot be removed. +2. **Adding backends does not activate them.** `addRemote()` and `addLocalServer()` only + add to the list. The user must explicitly `switchTo()` to activate. +3. **Removing the active backend auto-switches to embedded.** If the user removes the + currently active remote backend, the manager switches to embedded before removing it. +4. **Only one active backend at a time.** Switching closes the previous backend's + `KDownApi` and factory resources. +5. **Connection state is per-entry.** Each `BackendEntry` has its own + `connectionState` StateFlow. For inactive remote entries, the state is `Disconnected` + (connection is only established on activation). For the active entry, it reflects + actual connection status. + +### Why `addRemote` / `addLocalServer` instead of `switchTo(BackendConfig)` + +The UX design calls for a backend list where users can add servers and switch between +them later. Separating "add" from "activate" means: +- The backend selector sheet can show all configured servers with their status +- Users can remove servers they no longer need +- The add-remote dialog is separate from the switch action + +--- + +## 3. BackendFactory (lambda injection, no expect/actual) + +Per kmp-expert review: instead of expect/actual classes with 4 platform files, we use +**lambda injection** to provide the JVM-only local server capability. This keeps all +factory logic in `commonMain` with zero platform-specific files. + +### LocalServerHandle + +A simple interface representing a running local server. Defined in `commonMain` so +`BackendManager` can manage its lifecycle without referencing `KDownServer` directly: + +```kotlin +// commonMain +interface LocalServerHandle { + val api: KDownApi // The core KDown instance the server wraps + fun stop() +} +``` + +### BackendFactory + +A regular class in `commonMain` (not expect/actual). Takes an optional lambda for +local server creation: + +```kotlin +// commonMain +class BackendFactory( + private val localServerFactory: ((BackendConfig.LocalServer) -> LocalServerHandle)? = null +) { + /** Whether this platform supports starting a local server. */ + val isLocalServerSupported: Boolean + get() = localServerFactory != null + + private var localServer: LocalServerHandle? = null + + fun create(config: BackendConfig): KDownApi { + return when (config) { + is BackendConfig.Embedded -> createEmbeddedKDown() + is BackendConfig.Remote -> + RemoteKDown(config.baseUrl, config.apiToken) + is BackendConfig.LocalServer -> { + val factory = localServerFactory + ?: throw UnsupportedOperationException( + "Local server not supported on this platform" + ) + val handle = factory(config) + localServer = handle + handle.api // UI interacts with core KDown directly + } + } + } + + fun closeResources() { + localServer?.stop() + localServer = null + } + + private fun createEmbeddedKDown(): KDown { + return KDown( + httpEngine = KtorHttpEngine(), + config = DownloadConfig( + maxConnections = 4, + retryCount = 3, + queueConfig = QueueConfig(maxConcurrentDownloads = 3) + ), + logger = Logger.console() + ) + } +} +``` + +### JVM entry point provides the lambda + +Only the JVM/Desktop entry point passes the `localServerFactory`. All other platforms +pass `null` (the default): + +```kotlin +// app/desktop/src/main/kotlin/.../main.kt (JVM only) +fun main() = application { + val backendManager = remember { + BackendManager( + BackendFactory(localServerFactory = { config -> + val kdown = KDown( + httpEngine = KtorHttpEngine(), + logger = Logger.console() + ) + val server = KDownServer( + kdown, + KDownServerConfig( + port = config.port, + apiToken = config.apiToken + ) + ) + server.start(wait = false) + object : LocalServerHandle { + override val api: KDownApi = kdown + override fun stop() = server.stop() + } + }) + ) + } + // ... +} + +// iOS, Android, wasmJs entry points: +BackendManager(BackendFactory()) // no lambda = no local server +``` + +### Why lambda injection over expect/actual + +- **Eliminates 8 files.** No `BackendFactory.jvm.kt`, `BackendFactory.ios.kt`, + `BackendFactory.android.kt`, `BackendFactory.wasmJs.kt`, and no `Platform.*.kt` files. +- **All factory logic in one place.** Easier to understand and maintain. +- **Server dependency stays in jvmMain.** Only `app/desktop` (pure JVM) references + `KDownServer` and `KDownServerConfig`. The `app/shared` KMP module never touches them. +- **Runtime capability check is natural.** `isLocalServerSupported` = `lambda != null`. + +**Note on LocalServer backend:** When running a local server, the UI interacts with +the core `KDown` instance directly (same process). The server is started alongside it +so external clients (browser, other devices) can also connect. This avoids HTTP +round-trip overhead for local UI operations. + +--- + +## 4. State Flow & Switching Sequence + +### Data flow + +``` +BackendManager + | + |-- backends: StateFlow> <-- backend selector list + |-- activeBackend: StateFlow <-- current selection + |-- activeApi: StateFlow <-- UI task list + | + | BackendEntry + | |-- id, type, label, config + | |-- connectionState: StateFlow +``` + +### switchTo(id) sequence + +``` +User taps a backend in the selector sheet + --> BackendManager.switchTo(id) + 1. Find entry in backends list by id + 2. If same as active, no-op + 3. Close current activeApi (api.close()) + 4. Close factory resources (factory.closeResources()) + 5. Create new KDownApi via factory.create(entry.config) + 6. Update activeBackend, activeApi flows + 7. If embedded/local: call (api as KDown).loadTasks() + 8. If remote: start observing RemoteKDown.connectionState + --> map to entry's connectionState flow + 9. Emit Connected (or Connecting for remote) + --> UI recomposes with new activeApi.tasks +``` + +### addRemote() sequence + +``` +User fills in "Add Remote Server" dialog, taps Add + --> BackendManager.addRemote(host, port, token) + 1. Create BackendEntry with UUID id, type=REMOTE, config=Remote(...) + 2. Entry's connectionState = Disconnected (not yet connected) + 3. Add to backends list + 4. Return the entry + --> UI shows new entry in backend selector sheet + --> User can tap it to switchTo(entry.id) and activate +``` + +--- + +## 5. Compose Integration + +### App composable changes + +`App()` accepts a `BackendManager` instead of creating `KDown` directly: + +```kotlin +@Composable +fun App(backendManager: BackendManager) { + val activeApi by backendManager.activeApi.collectAsState() + val activeBackend by backendManager.activeBackend.collectAsState() + val backends by backendManager.backends.collectAsState() + val tasks by activeApi.tasks.collectAsState() + val connectionState by activeBackend.connectionState.collectAsState() + val version by activeApi.version.collectAsState() + + // ... rest of UI uses `activeApi` instead of `kdown` +} +``` + +### Platform entry points + +```kotlin +// Desktop main.kt (JVM -- provides localServerFactory lambda) +fun main() = application { + val backendManager = remember { + BackendManager( + BackendFactory(localServerFactory = { config -> + val kdown = KDown(httpEngine = KtorHttpEngine(), logger = Logger.console()) + val server = KDownServer(kdown, KDownServerConfig( + port = config.port, apiToken = config.apiToken + )) + server.start(wait = false) + object : LocalServerHandle { + override val api: KDownApi = kdown + override fun stop() = server.stop() + } + }) + ) + } + DisposableEffect(Unit) { + onDispose { backendManager.close() } + } + Window( + onCloseRequest = ::exitApplication, + title = "KDown Examples" + ) { + App(backendManager) + } +} + +// iOS, Android, wasmJs entry points -- no local server support +val backendManager = BackendManager(BackendFactory()) +App(backendManager) +``` + +### Top app bar + +Shows current backend and connection status: + +``` +[TopAppBar] + Title: "KDown" + Subtitle: clickable chip showing "v1.0.0 · Core" + or "v1.0.0 · Remote · 192.168.1.5:8642" + Trailing: Connection status dot + - Green = Connected + - Yellow/Amber = Connecting + - Red = Disconnected + Tap subtitle chip --> opens backend selector bottom sheet +``` + +### Backend selector (ModalBottomSheet) + +``` +[Backend Selector Sheet] + -- Backend List -- + [*] Embedded [Connected] + [ ] Remote · 192.168.1.5:8642 [Disconnected] [X remove] + [ ] Remote · nas.local:8642 [Disconnected] [X remove] + [ ] Local Server · :8642 [Connected] [X remove] + + -- Actions -- + [+ Add Remote Server] + [+ Start Local Server] (only shown when backendManager.isLocalServerSupported) +``` + +Tapping a backend entry calls `switchTo(entry.id)`. The [X] button calls +`removeBackend(entry.id)`. + +### Add Remote Server (AlertDialog) + +``` +[Add Remote Server] + Host: [________________] + Port: [8642____________] + Token: [________________] (optional) + + [Cancel] [Add] +``` + +Tapping "Add" calls `addRemote(host, port, token)`. The new entry appears in the +backend selector sheet but is not activated until the user taps it. + +--- + +## 6. File / Package Layout + +All new code in `app/shared/src/commonMain/` under a `backend` sub-package. +**No platform-specific source sets needed** thanks to lambda injection: + +``` +app/shared/src/ + commonMain/kotlin/com/linroid/kdown/examples/ + App.kt (modified: accept BackendManager param) + backend/ + BackendConfig.kt (sealed class: Embedded, Remote, LocalServer) + BackendConnectionState.kt (sealed class: Connected, Connecting, Disconnected) + BackendEntry.kt (data class with id, type, label, config, connectionState) + BackendType.kt (enum: EMBEDDED, REMOTE, LOCAL_SERVER) + BackendManager.kt (manages backend list, active backend, switching) + BackendFactory.kt (regular class with optional localServerFactory lambda) + LocalServerHandle.kt (interface: api + stop()) + +app/desktop/src/main/kotlin/com/linroid/kdown/examples/ + main.kt (modified: create BackendFactory with localServerFactory lambda) +``` + +Note: The `localServerFactory` lambda is only provided in the JVM desktop entry point +(`app/desktop`), which already depends on the `server` module. No changes +needed in iOS, Android, or wasmJs entry points. + +--- + +## 7. Dependency Changes + +### app/shared/build.gradle.kts + +```kotlin +commonMain.dependencies { + // existing + implementation(projects.library.ktor) + // add + implementation(projects.library.remote) // RemoteKDown +} +// No jvmMain changes -- server dependency lives in desktopApp, not here +``` + +### app/desktop/build.gradle.kts + +```kotlin +dependencies { + implementation(projects.app.shared) + implementation(compose.desktop.currentOs) + implementation(libs.kotlinx.coroutinesSwing) + // add + implementation(projects.server) // KDownServer -- only referenced in main.kt lambda +} +``` + +The `server` module dependency belongs in `desktopApp` (not `app/shared/jvmMain`) +because only the desktop entry point provides the `localServerFactory` lambda that +references `KDownServer`. The `app/shared` KMP module never imports server classes. + +No changes needed for iOS, Android, or wasmJs -- they only use `library.remote` which +is already KMP. + +### All platform entry points need updating + +Per kmp-expert review, all entry points must create `BackendManager` and pass to `App()`: +- `app/desktop/main.kt` -- with `localServerFactory` lambda (shown in section 5) +- `app/android` -- `BackendManager(BackendFactory())` +- iOS `MainViewController.kt` -- `BackendManager(BackendFactory())` +- wasmJs entry point -- `BackendManager(BackendFactory())` + +--- + +## 8. Error Handling + +### Backend switch failures + +If `switchTo()` fails (e.g., remote server unreachable, port already in use), the +manager: + +1. Keeps the old backend active (does not close it before the new one succeeds) +2. Throws the exception, which the UI catches and shows as an error snackbar + +```kotlin +scope.launch { + try { + backendManager.switchTo(entry.id) + } catch (e: Exception) { + errorMessage = "Failed to switch backend: ${e.message}" + } +} +``` + +### Remote disconnection + +When a remote backend disconnects, its `connectionState` transitions to `Disconnected`. +The UI shows a connection indicator change but does NOT auto-switch. `RemoteKDown` +handles reconnection with exponential backoff. The user can manually switch to another +backend if they prefer. + +### Removing the active backend + +If the user removes the currently active backend, the manager switches to embedded +first, then removes the entry. This ensures there is always an active backend. + +--- + +## 9. What This Design Does NOT Do + +- **No ViewModel layer.** The example app is intentionally simple. `BackendManager` + holds the StateFlows directly. A production app would wrap this in a ViewModel. +- **No persistent backend list.** The backend list is not saved to disk. The app + always starts with only the embedded backend. A production app might persist + configured remote servers. +- **No simultaneous active backends.** Only one backend is active at a time. Switching + closes the previous one. +- **No new modules.** All code lives in the existing `app/shared` and + `app/desktop` modules. No platform-specific source sets needed in + `app/shared` -- lambda injection handles the JVM-only local server case. + +--- + +## 10. Summary of Changes + +| Area | Change | +|------|--------| +| `app/shared/commonMain` | Add `backend/` package: `BackendConfig`, `BackendConnectionState`, `BackendEntry`, `BackendType`, `BackendManager`, `BackendFactory`, `LocalServerHandle` (all in commonMain, no expect/actual) | +| `app/shared/commonMain/App.kt` | Accept `BackendManager` param, use `activeApi` StateFlow, add backend indicator chip in TopAppBar, add backend selector bottom sheet | +| `app/shared/build.gradle.kts` | Add `library.remote` to commonMain dependencies | +| `app/desktop/main.kt` | Create `BackendFactory` with `localServerFactory` lambda, pass `BackendManager` to `App()` | +| `app/desktop/build.gradle.kts` | Add `projects.server` dependency | +| All platform entry points | Create `BackendManager(BackendFactory())` and pass to `App()` | diff --git a/settings.gradle.kts b/settings.gradle.kts index 3e27fe90..c099b08d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,9 +41,9 @@ include(":library:kermit") include(":library:sqlite") include(":server") -// Example modules -include(":examples:app") -include(":examples:androidApp") -include(":examples:desktopApp") -include(":examples:webApp") -include(":examples:cli") +// App modules +include(":app:shared") +include(":app:android") +include(":app:desktop") +include(":app:web") +include(":cli") From 5c91de7e526d350d49ddb7b345564eadafce5309 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:03:37 +0800 Subject: [PATCH 02/13] Update Android minSdk to 26 --- app/android/build.gradle.kts | 6 ++++++ gradle/libs.versions.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/android/build.gradle.kts b/app/android/build.gradle.kts index 366eb58c..dce0f380 100644 --- a/app/android/build.gradle.kts +++ b/app/android/build.gradle.kts @@ -35,6 +35,12 @@ android { buildFeatures { compose = true } + + packaging { + resources { + excludes += "/META-INF/{INDEX.LIST,io.netty.versions.properties}" + } + } } dependencies { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1498d2ee..5bd4c125 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "9.0.0" android-compileSdk = "36" -android-minSdk = "24" +android-minSdk = "26" android-targetSdk = "36" androidx-activity = "1.12.3" androidx-appcompat = "1.7.1" From b4876078d69c7823818d6ddd45956f68040894d3 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:13:18 +0800 Subject: [PATCH 03/13] Add iOS Xcode project file for app/ios Co-Authored-By: Claude Opus 4.6 --- app/ios/iosApp.xcodeproj/project.pbxproj | 374 +++++++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 app/ios/iosApp.xcodeproj/project.pbxproj diff --git a/app/ios/iosApp.xcodeproj/project.pbxproj b/app/ios/iosApp.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0ac21ac9 --- /dev/null +++ b/app/ios/iosApp.xcodeproj/project.pbxproj @@ -0,0 +1,374 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 56; + objects = { + +/* Begin PBXBuildFile section */ + A001 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B001; }; + A002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B002; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + B001 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; + B002 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + B003 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + C001 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D001 = { + isa = PBXGroup; + children = ( + D002 /* iosApp */, + D003 /* Products */, + ); + sourceTree = ""; + }; + D002 /* iosApp */ = { + isa = PBXGroup; + children = ( + B001 /* iOSApp.swift */, + B002 /* ContentView.swift */, + ); + path = iosApp; + sourceTree = ""; + }; + D003 /* Products */ = { + isa = PBXGroup; + children = ( + B003 /* iosApp.app */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + E001 /* iosApp */ = { + isa = PBXNativeTarget; + buildConfigurationList = F003 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildPhases = ( + E002 /* Compile Kotlin Framework */, + E003 /* Sources */, + C001 /* Frameworks */, + E004 /* Embed Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iosApp; + productName = iosApp; + productReference = B003 /* iosApp.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + F001 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1540; + LastUpgradeCheck = 1540; + }; + buildConfigurationList = F002 /* Build configuration list for PBXProject "iosApp" */; + compatibilityVersion = "Xcode 14.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = D001; + productRefGroup = D003 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + E001 /* iosApp */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXShellScriptBuildPhase section */ + E002 /* Compile Kotlin Framework */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Compile Kotlin Framework"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "cd \"$SRCROOT/../..\"\n./gradlew :app:shared:embedAndSignAppleFrameworkForXcode\n"; + }; + E004 /* Embed Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Embed Frameworks"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = ""; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + E003 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A001 /* iOSApp.swift in Sources */, + A002 /* ContentView.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + G001 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + G002 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + G003 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KDownApp, + ); + PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + G004 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", + ); + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; + INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; + INFOPLIST_KEY_UILaunchScreen_Generation = YES; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-framework", + KDownApp, + ); + PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + F002 /* Build configuration list for PBXProject "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + G001 /* Debug */, + G002 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + F003 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + G003 /* Debug */, + G004 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + + }; + rootObject = F001 /* Project object */; +} From a5dee12482e8dd515d84045d51736a2ea5fc0dd3 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:15:57 +0800 Subject: [PATCH 04/13] Remove nested iosApp directory, move Swift sources to app/ios/ Co-Authored-By: Claude Opus 4.6 --- app/ios/{iosApp => }/ContentView.swift | 0 app/ios/{iosApp => }/iOSApp.swift | 0 app/ios/iosApp.xcodeproj/project.pbxproj | 4 ++-- 3 files changed, 2 insertions(+), 2 deletions(-) rename app/ios/{iosApp => }/ContentView.swift (100%) rename app/ios/{iosApp => }/iOSApp.swift (100%) diff --git a/app/ios/iosApp/ContentView.swift b/app/ios/ContentView.swift similarity index 100% rename from app/ios/iosApp/ContentView.swift rename to app/ios/ContentView.swift diff --git a/app/ios/iosApp/iOSApp.swift b/app/ios/iOSApp.swift similarity index 100% rename from app/ios/iosApp/iOSApp.swift rename to app/ios/iOSApp.swift diff --git a/app/ios/iosApp.xcodeproj/project.pbxproj b/app/ios/iosApp.xcodeproj/project.pbxproj index 0ac21ac9..44aa3fb5 100644 --- a/app/ios/iosApp.xcodeproj/project.pbxproj +++ b/app/ios/iosApp.xcodeproj/project.pbxproj @@ -36,13 +36,13 @@ ); sourceTree = ""; }; - D002 /* iosApp */ = { + D002 /* Sources */ = { isa = PBXGroup; children = ( B001 /* iOSApp.swift */, B002 /* ContentView.swift */, ); - path = iosApp; + name = Sources; sourceTree = ""; }; D003 /* Products */ = { From 1c09ca6200fff195a71e14d3517382820fa45569 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:17:40 +0800 Subject: [PATCH 05/13] Rename iOS Xcode project from iosApp to KDown Co-Authored-By: Claude Opus 4.6 --- .../project.pbxproj | 24 +++++++++---------- .../contents.xcworkspacedata | 7 ++++++ 2 files changed, 19 insertions(+), 12 deletions(-) rename app/ios/{iosApp.xcodeproj => KDown.xcodeproj}/project.pbxproj (95%) create mode 100644 app/ios/KDown.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/app/ios/iosApp.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj similarity index 95% rename from app/ios/iosApp.xcodeproj/project.pbxproj rename to app/ios/KDown.xcodeproj/project.pbxproj index 44aa3fb5..690a4d7f 100644 --- a/app/ios/iosApp.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -14,7 +14,7 @@ /* Begin PBXFileReference section */ B001 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; B002 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; - B003 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; + B003 /* KDown.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KDown.app; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -31,7 +31,7 @@ D001 = { isa = PBXGroup; children = ( - D002 /* iosApp */, + D002 /* KDown */, D003 /* Products */, ); sourceTree = ""; @@ -48,7 +48,7 @@ D003 /* Products */ = { isa = PBXGroup; children = ( - B003 /* iosApp.app */, + B003 /* KDown.app */, ); name = Products; sourceTree = ""; @@ -56,9 +56,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - E001 /* iosApp */ = { + E001 /* KDown */ = { isa = PBXNativeTarget; - buildConfigurationList = F003 /* Build configuration list for PBXNativeTarget "iosApp" */; + buildConfigurationList = F003 /* Build configuration list for PBXNativeTarget "KDown" */; buildPhases = ( E002 /* Compile Kotlin Framework */, E003 /* Sources */, @@ -69,9 +69,9 @@ ); dependencies = ( ); - name = iosApp; - productName = iosApp; - productReference = B003 /* iosApp.app */; + name = KDown; + productName = KDown; + productReference = B003 /* KDown.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -84,7 +84,7 @@ LastSwiftUpdateCheck = 1540; LastUpgradeCheck = 1540; }; - buildConfigurationList = F002 /* Build configuration list for PBXProject "iosApp" */; + buildConfigurationList = F002 /* Build configuration list for PBXProject "KDown" */; compatibilityVersion = "Xcode 14.0"; developmentRegion = en; hasScannedForEncodings = 0; @@ -97,7 +97,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - E001 /* iosApp */, + E001 /* KDown */, ); }; /* End PBXProject section */ @@ -349,7 +349,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - F002 /* Build configuration list for PBXProject "iosApp" */ = { + F002 /* Build configuration list for PBXProject "KDown" */ = { isa = XCConfigurationList; buildConfigurations = ( G001 /* Debug */, @@ -358,7 +358,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - F003 /* Build configuration list for PBXNativeTarget "iosApp" */ = { + F003 /* Build configuration list for PBXNativeTarget "KDown" */ = { isa = XCConfigurationList; buildConfigurations = ( G003 /* Debug */, diff --git a/app/ios/KDown.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/app/ios/KDown.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/app/ios/KDown.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + From 7006fe3eece1fa74e80d59da7db9616355a0da01 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:18:38 +0800 Subject: [PATCH 06/13] Update XCode project file --- app/ios/KDown.xcodeproj/project.pbxproj | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index 690a4d7f..f63210d0 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -7,8 +7,8 @@ objects = { /* Begin PBXBuildFile section */ - A001 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B001; }; - A002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B002; }; + A001 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B001 /* iOSApp.swift */; }; + A002 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B002 /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -31,7 +31,7 @@ D001 = { isa = PBXGroup; children = ( - D002 /* KDown */, + D002 /* Sources */, D003 /* Products */, ); sourceTree = ""; @@ -281,7 +281,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WHHN7J96UQ; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", @@ -317,7 +317,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = WHHN7J96UQ; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", @@ -368,7 +368,6 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ - }; rootObject = F001 /* Project object */; } From cc349e18a203ba0b0e1424686b3b78a07f7bc98f Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:19:30 +0800 Subject: [PATCH 07/13] Link sqlite3 system library for iOS build Co-Authored-By: Claude Opus 4.6 --- app/ios/KDown.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index f63210d0..f68d9221 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -301,6 +301,7 @@ "$(inherited)", "-framework", KDownApp, + "-lsqlite3", ); PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -337,6 +338,7 @@ "$(inherited)", "-framework", KDownApp, + "-lsqlite3", ); PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; PRODUCT_NAME = "$(TARGET_NAME)"; From e77fb9c4853de63529d597efb79b7197da1cb150 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:39:58 +0800 Subject: [PATCH 08/13] Fix iOS bundle ID and development team Co-Authored-By: Claude Opus 4.6 --- app/ios/KDown.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index f68d9221..6a63b247 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -281,7 +281,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = WHHN7J96UQ; + DEVELOPMENT_TEAM = MJ7RE83LT9; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", @@ -303,7 +303,7 @@ KDownApp, "-lsqlite3", ); - PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; + PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -340,7 +340,7 @@ KDownApp, "-lsqlite3", ); - PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.app.ios; + PRODUCT_BUNDLE_IDENTIFIER = com.linroid.kdown.ios; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; From 8991ecec31917b6ae090bc205db39eb298fc03db Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:40:32 +0800 Subject: [PATCH 09/13] Change Android namespace to com.linroid.kdown.android Co-Authored-By: Claude Opus 4.6 --- app/android/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/android/build.gradle.kts b/app/android/build.gradle.kts index dce0f380..35d8fd36 100644 --- a/app/android/build.gradle.kts +++ b/app/android/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } android { - namespace = "com.linroid.kdown.app.android" + namespace = "com.linroid.kdown.android" compileSdk = libs.versions.android.compileSdk.get().toInt() defaultConfig { From 115972c1656a4f1bf6ba1903f842ff9fef178a97 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:44:32 +0800 Subject: [PATCH 10/13] Update packages for app --- app/android/src/main/AndroidManifest.xml | 2 +- .../kotlin/com/linroid/kdown/{app => android}/MainActivity.kt | 3 ++- .../src/main/kotlin/com/linroid/kdown/{app => desktop}/main.kt | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) rename app/android/src/main/kotlin/com/linroid/kdown/{app => android}/MainActivity.kt (96%) rename app/desktop/src/main/kotlin/com/linroid/kdown/{app => desktop}/main.kt (95%) diff --git a/app/android/src/main/AndroidManifest.xml b/app/android/src/main/AndroidManifest.xml index 387453a9..ba11c420 100644 --- a/app/android/src/main/AndroidManifest.xml +++ b/app/android/src/main/AndroidManifest.xml @@ -9,7 +9,7 @@ android:supportsRtl="true" android:theme="@android:style/Theme.Material.Light.NoActionBar"> diff --git a/app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt b/app/android/src/main/kotlin/com/linroid/kdown/android/MainActivity.kt similarity index 96% rename from app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt rename to app/android/src/main/kotlin/com/linroid/kdown/android/MainActivity.kt index 285541e2..af0b1a41 100644 --- a/app/android/src/main/kotlin/com/linroid/kdown/app/MainActivity.kt +++ b/app/android/src/main/kotlin/com/linroid/kdown/android/MainActivity.kt @@ -1,4 +1,4 @@ -package com.linroid.kdown.app +package com.linroid.kdown.android import android.os.Bundle import androidx.activity.ComponentActivity @@ -6,6 +6,7 @@ import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember +import com.linroid.kdown.app.App import com.linroid.kdown.app.backend.BackendFactory import com.linroid.kdown.app.backend.BackendManager import com.linroid.kdown.app.backend.LocalServerHandle diff --git a/app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt b/app/desktop/src/main/kotlin/com/linroid/kdown/desktop/main.kt similarity index 95% rename from app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt rename to app/desktop/src/main/kotlin/com/linroid/kdown/desktop/main.kt index f73388bc..6a2937c4 100644 --- a/app/desktop/src/main/kotlin/com/linroid/kdown/app/main.kt +++ b/app/desktop/src/main/kotlin/com/linroid/kdown/desktop/main.kt @@ -1,9 +1,10 @@ -package com.linroid.kdown.app +package com.linroid.kdown.desktop import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.window.Window import androidx.compose.ui.window.application +import com.linroid.kdown.app.App import com.linroid.kdown.app.backend.BackendFactory import com.linroid.kdown.app.backend.BackendManager import com.linroid.kdown.app.backend.LocalServerHandle From d8dd0177dc030f02b57aeba73017733bc308b4da Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:48:09 +0800 Subject: [PATCH 11/13] Enable CADisableMinimumFrameDurationOnPhone for iOS Required by Compose Multiplatform for ProMotion display support. Co-Authored-By: Claude Opus 4.6 --- app/ios/KDown.xcodeproj/project.pbxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index 6a63b247..c141f266 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -287,6 +287,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -324,6 +325,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; From 6e9074c29b4474f0888c82fbc41a683173428916 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:50:49 +0800 Subject: [PATCH 12/13] Add CADisableMinimumFrameDurationOnPhone --- app/ios/KDown-Info.plist | 8 ++++++++ app/ios/KDown.xcodeproj/project.pbxproj | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 app/ios/KDown-Info.plist diff --git a/app/ios/KDown-Info.plist b/app/ios/KDown-Info.plist new file mode 100644 index 00000000..11845e1d --- /dev/null +++ b/app/ios/KDown-Info.plist @@ -0,0 +1,8 @@ + + + + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index c141f266..cb6d0dfd 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 8676F78D2F3CDCA000DE65CF /* KDown-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = "KDown-Info.plist"; sourceTree = ""; }; B001 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; B002 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; B003 /* KDown.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KDown.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -31,6 +32,7 @@ D001 = { isa = PBXGroup; children = ( + 8676F78D2F3CDCA000DE65CF /* KDown-Info.plist */, D002 /* Sources */, D003 /* Products */, ); @@ -287,6 +289,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "KDown-Info.plist"; INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; @@ -325,6 +328,7 @@ "$(SRCROOT)/../shared/build/xcode-frameworks/$(CONFIGURATION)/$(SDK_NAME)", ); GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "KDown-Info.plist"; INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; From 8fa70375d656a31c4106a53f57d599d34ba85182 Mon Sep 17 00:00:00 2001 From: Lin Zhang Date: Wed, 11 Feb 2026 23:52:07 +0800 Subject: [PATCH 13/13] Revert "Enable CADisableMinimumFrameDurationOnPhone for iOS" This reverts commit d8dd0177dc030f02b57aeba73017733bc308b4da. --- app/ios/KDown.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/ios/KDown.xcodeproj/project.pbxproj b/app/ios/KDown.xcodeproj/project.pbxproj index cb6d0dfd..ef784798 100644 --- a/app/ios/KDown.xcodeproj/project.pbxproj +++ b/app/ios/KDown.xcodeproj/project.pbxproj @@ -290,7 +290,6 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "KDown-Info.plist"; - INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES; @@ -329,7 +328,6 @@ ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "KDown-Info.plist"; - INFOPLIST_KEY_CADisableMinimumFrameDurationOnPhone = YES; INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; INFOPLIST_KEY_UILaunchScreen_Generation = YES;