From ad2a102f1f9a08000a4b1dd6f53f078b6b8482f5 Mon Sep 17 00:00:00 2001 From: Danil Nikolaev Date: Sat, 4 Oct 2025 02:14:37 +0300 Subject: [PATCH] improvements for kmp --- build.gradle.kts | 12 +- composeApp/build.gradle.kts | 113 +++++++----------- .../dev/meloda/overseerr/Platform.android.kt | 9 ++ .../dev/meloda/overseerr/common/AppGlobal.kt | 13 +- .../kotlin/dev/meloda/overseerr/App.kt | 26 ++-- .../kotlin/dev/meloda/overseerr/Platform.kt | 7 ++ .../overseerr/datastore/SettingsController.kt | 34 ++---- .../overseerr/datastore/di/DataStoreModule.kt | 4 +- .../overseerr/datastore/model/AppSettings.kt | 2 +- .../requests/presentation/RequestsScreen.kt | 1 - .../settings/presentation/SettingsScreen.kt | 1 - .../kotlin/{main.kt => MainViewController.kt} | 9 +- .../dev/meloda/overseerr/Platform.ios.kt | 9 ++ .../SettingsStoreProvider.ios.kt | 3 +- .../meloda/overseerr/model/Platform.ios.kt | 6 - .../HttpClientEngineFactoryProvider.ios.kt | 8 ++ .../network/model/HttpClientProvider.ios.kt | 8 -- .../dev/meloda/overseerr/Platform.js.kt | 7 ++ .../HttpClientEngineFactoryProvider.js.kt | 8 ++ composeApp/src/jsMain/kotlin/main.js.kt | 25 ++++ .../dev/meloda/overseerr/Platform.jvm.kt | 7 ++ composeApp/src/jvmMain/kotlin/main.kt | 10 +- .../dev/meloda/overseerr/Platform.wasmJs.kt | 7 ++ .../HttpClientEngineFactoryProvider.wasmJs.kt | 4 +- composeApp/src/wasmJsMain/kotlin/main.kt | 34 ------ .../src/wasmJsMain/kotlin/main.wasmJs.kt | 25 ++++ .../datastore/SettingsStoreProvider.web.kt} | 0 .../dev/meloda/overseerr/theme/Theme.web.kt} | 2 - composeApp/src/webMain/kotlin/main.kt | 16 +++ .../resources/index.html | 2 +- .../resources/styles.css | 0 composeApp/webpack.config.d/watch.js | 21 ++++ gradle.properties | 14 +-- gradle/libs.versions.toml | 4 +- settings.gradle.kts | 26 ++-- 35 files changed, 262 insertions(+), 215 deletions(-) create mode 100644 composeApp/src/androidMain/kotlin/dev/meloda/overseerr/Platform.android.kt create mode 100644 composeApp/src/commonMain/kotlin/dev/meloda/overseerr/Platform.kt rename composeApp/src/iosMain/kotlin/{main.kt => MainViewController.kt} (56%) create mode 100644 composeApp/src/iosMain/kotlin/dev/meloda/overseerr/Platform.ios.kt rename composeApp/src/iosMain/kotlin/dev/meloda/overseerr/{settings/model => datastore}/SettingsStoreProvider.ios.kt (77%) delete mode 100644 composeApp/src/iosMain/kotlin/dev/meloda/overseerr/model/Platform.ios.kt create mode 100644 composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.ios.kt delete mode 100644 composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/model/HttpClientProvider.ios.kt create mode 100644 composeApp/src/jsMain/kotlin/dev/meloda/overseerr/Platform.js.kt create mode 100644 composeApp/src/jsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.js.kt create mode 100644 composeApp/src/jsMain/kotlin/main.js.kt create mode 100644 composeApp/src/jvmMain/kotlin/dev/meloda/overseerr/Platform.jvm.kt create mode 100644 composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/Platform.wasmJs.kt delete mode 100644 composeApp/src/wasmJsMain/kotlin/main.kt create mode 100644 composeApp/src/wasmJsMain/kotlin/main.wasmJs.kt rename composeApp/src/{wasmJsMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.wasmJs.kt => webMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.web.kt} (100%) rename composeApp/src/{wasmJsMain/kotlin/dev/meloda/overseerr/theme/Theme.wasmJs.kt => webMain/kotlin/dev/meloda/overseerr/theme/Theme.web.kt} (73%) create mode 100644 composeApp/src/webMain/kotlin/main.kt rename composeApp/src/{wasmJsMain => webMain}/resources/index.html (91%) rename composeApp/src/{wasmJsMain => webMain}/resources/styles.css (100%) create mode 100644 composeApp/webpack.config.d/watch.js diff --git a/build.gradle.kts b/build.gradle.kts index 683ad7a..ab75065 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,8 +1,8 @@ plugins { - alias(libs.plugins.multiplatform).apply(false) - alias(libs.plugins.compose.compiler).apply(false) - alias(libs.plugins.compose).apply(false) - alias(libs.plugins.compose.hot.reload).apply(false) - alias(libs.plugins.android.application).apply(false) - alias(libs.plugins.kotlinx.serialization).apply(false) + alias(libs.plugins.multiplatform) apply false + alias(libs.plugins.compose.compiler) apply false + alias(libs.plugins.compose) apply false + alias(libs.plugins.compose.hot.reload) apply false + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlinx.serialization) apply false } diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index b832760..9fe0b04 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -2,8 +2,6 @@ import org.jetbrains.compose.ExperimentalComposeLibrary import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.util.* plugins { @@ -15,21 +13,6 @@ plugins { alias(libs.plugins.kotlinx.serialization) } -tasks.withType().configureEach { - compilerOptions { - freeCompilerArgs.addAll( - "-opt-in=kotlin.RequiresOptIn", - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=kotlinx.coroutines.FlowPreview", - "-Xexpect-actual-classes" - ) - } -} - -compose.resources { - nameOfResClass = "R" -} - kotlin { androidTarget { compilerOptions { @@ -39,53 +22,48 @@ kotlin { jvm() - if (providers.gradleProperty("include_ios").get().toBoolean()) { - listOf( - iosX64(), - iosArm64(), - iosSimulatorArm64() - ).forEach { iosTarget -> - iosTarget.binaries.framework { - baseName = "ComposeApp" - isStatic = true - } + listOf( + iosArm64(), + iosSimulatorArm64() + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true } } - if (providers.gradleProperty("include_wasm").get().toBoolean()) { - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - outputModuleName = "composeApp" - browser { - val rootDirPath = project.rootDir.path - val projectDirPath = project.projectDir.path - commonWebpackConfig { - outputFileName = "composeApp.js" - devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { - static = (static ?: mutableListOf()).apply { - // Serve sources to debug inside browser - add(rootDirPath) - add(projectDirPath) - } - } - } - } - binaries.executable() - } + js { + browser() + binaries.executable() + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + binaries.executable() } sourceSets { + androidMain.dependencies { + implementation(compose.preview) + implementation(libs.androidx.activityCompose) + implementation(libs.kotlinx.coroutines.android) + implementation(libs.ktor.client.okhttp) + implementation(libs.kstore.file) + } + commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) implementation(compose.materialIconsExtended) implementation(compose.components.resources) + implementation(compose.components.uiToolingPreview) implementation(libs.androidx.navigation.compose) implementation(libs.coil) implementation(libs.coil.network.ktor) implementation(libs.kotlinx.coroutines.core) - implementation(libs.ktor.core) + implementation(libs.ktor.client.core) implementation(libs.ktor.client.content.negotiation) implementation(libs.ktor.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json) @@ -102,20 +80,12 @@ kotlin { } commonTest.dependencies { - implementation(kotlin("test")) + implementation(libs.kotlin.test) @OptIn(ExperimentalComposeLibrary::class) implementation(compose.uiTest) implementation(libs.kotlinx.coroutines.test) } - androidMain.dependencies { - implementation(compose.uiTooling) - implementation(libs.androidx.activityCompose) - implementation(libs.kotlinx.coroutines.android) - implementation(libs.ktor.client.okhttp) - implementation(libs.kstore.file) - } - jvmMain.dependencies { implementation(compose.desktop.currentOs) implementation(libs.kotlinx.coroutines.swing) @@ -124,18 +94,14 @@ kotlin { implementation(libs.kstore.file) } - findByName("iosMain")?.run { - dependencies { - implementation(libs.ktor.client.darwin) - implementation(libs.kstore.file) - } + iosMain.dependencies { + implementation(libs.ktor.client.darwin) + implementation(libs.kstore.file) } - findByName("wasmJsMain")?.run { - dependencies { - implementation(libs.kstore.storage) - implementation(libs.ktor.client.js) - } + webMain.dependencies { + implementation(libs.kstore.storage) + implementation(libs.ktor.client.js) } } } @@ -211,6 +177,10 @@ dependencies { debugImplementation(compose.uiTooling) } +compose.resources { + nameOfResClass = "R" +} + compose.desktop { application { mainClass = "MainKt" @@ -222,3 +192,12 @@ compose.desktop { } } } + +kotlin { + compilerOptions.freeCompilerArgs.addAll( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.FlowPreview", + "-Xexpect-actual-classes" + ) +} diff --git a/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/Platform.android.kt b/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/Platform.android.kt new file mode 100644 index 0000000..20f7f8c --- /dev/null +++ b/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/Platform.android.kt @@ -0,0 +1,9 @@ +package dev.meloda.overseerr + +import android.os.Build + +class AndroidPlatform : Platform { + override val name: String = "Android ${Build.VERSION.SDK_INT}" +} + +actual fun getPlatform(): Platform = AndroidPlatform() diff --git a/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/common/AppGlobal.kt b/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/common/AppGlobal.kt index 9d8093b..2f433b3 100644 --- a/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/common/AppGlobal.kt +++ b/composeApp/src/androidMain/kotlin/dev/meloda/overseerr/common/AppGlobal.kt @@ -1,16 +1,5 @@ package dev.meloda.overseerr.common import android.app.Application -import dev.meloda.overseerr.di.appModule -import org.koin.core.context.startKoin -class AppGlobal : Application() { - - override fun onCreate() { - super.onCreate() - - startKoin { - modules(appModule) - } - } -} +class AppGlobal : Application() diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/App.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/App.kt index eb00e85..3f0fa0b 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/App.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/App.kt @@ -8,30 +8,34 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.meloda.overseerr.datastore.SettingsController +import dev.meloda.overseerr.di.appModule import dev.meloda.overseerr.screens.main.MainScreen import dev.meloda.overseerr.theme.AppTheme import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier +import org.koin.compose.KoinApplication import org.koin.compose.koinInject var appDir: String = "" @Composable internal fun App() { - LaunchedEffect(true) { - Napier.base(DebugAntilog()) - } + KoinApplication(application = { modules(appModule) }) { + LaunchedEffect(true) { + Napier.base(DebugAntilog()) + } - val settingsController: SettingsController = koinInject() - val settings by settingsController.settings.collectAsStateWithLifecycle() + val settingsController: SettingsController = koinInject() + val settings by settingsController.settings.collectAsStateWithLifecycle() - LaunchedEffect(true) { - settingsController.loadAppSettings() - } + LaunchedEffect(true) { + settingsController.loadAppSettings() + } - AppTheme(themeMode = settings.themeMode) { - Surface(modifier = Modifier.fillMaxSize()) { - MainScreen() + AppTheme(themeMode = settings.themeMode) { + Surface(modifier = Modifier.fillMaxSize()) { + MainScreen() + } } } } diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/Platform.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/Platform.kt new file mode 100644 index 0000000..72b1c38 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/Platform.kt @@ -0,0 +1,7 @@ +package dev.meloda.overseerr + +interface Platform { + val name: String +} + +expect fun getPlatform(): Platform diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/SettingsController.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/SettingsController.kt index a37060d..f81a739 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/SettingsController.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/SettingsController.kt @@ -1,43 +1,35 @@ package dev.meloda.overseerr.datastore -import dev.meloda.overseerr.ext.setValue import dev.meloda.overseerr.datastore.model.AppSettings import dev.meloda.overseerr.datastore.model.ThemeMode +import dev.meloda.overseerr.ext.setValue import io.github.xxfast.kstore.KStore import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow -interface SettingsController { - val settings: StateFlow - suspend fun saveAppSettings() - suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) - suspend fun loadAppSettings(): AppSettings +class SettingsController( + private val store: KStore, +) { + private val _settings = MutableStateFlow(AppSettings.EMPTY) + val settings: StateFlow = _settings.asStateFlow() - fun updateThemeMode(newThemeMode: ThemeMode) -} - -class SettingsControllerImpl( - private val store: KStore -) : SettingsController { - - override val settings = MutableStateFlow(AppSettings.EMPTY) - - override suspend fun saveAppSettings() { + suspend fun saveAppSettings() { store.set(settings.value) } - override suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) { + suspend fun updateAppSettings(update: (AppSettings) -> AppSettings) { store.set(update(settings.value)) } - override suspend fun loadAppSettings(): AppSettings { + suspend fun loadAppSettings(): AppSettings { val loadedSettings = store.get() ?: AppSettings.EMPTY - settings.setValue { loadedSettings } + _settings.setValue { loadedSettings } return loadedSettings } - override fun updateThemeMode(newThemeMode: ThemeMode) { - settings.setValue { old -> old.copy(themeMode = newThemeMode) } + fun updateThemeMode(newThemeMode: ThemeMode) { + _settings.setValue { old -> old.copy(themeMode = newThemeMode) } } } diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/di/DataStoreModule.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/di/DataStoreModule.kt index d85a062..49c9949 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/di/DataStoreModule.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/di/DataStoreModule.kt @@ -1,13 +1,11 @@ package dev.meloda.overseerr.datastore.di import dev.meloda.overseerr.datastore.SettingsController -import dev.meloda.overseerr.datastore.SettingsControllerImpl import dev.meloda.overseerr.datastore.SettingsStoreProvider import org.koin.core.module.dsl.singleOf -import org.koin.dsl.bind import org.koin.dsl.module val dataStoreModule = module { single { SettingsStoreProvider().provideStore() } - singleOf(::SettingsControllerImpl) bind SettingsController::class + singleOf(::SettingsController) } diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/model/AppSettings.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/model/AppSettings.kt index 0b148b4..80c777c 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/model/AppSettings.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/datastore/model/AppSettings.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable data class AppSettings( val url: String? = null, val plexToken: String? = null, - val themeMode: ThemeMode = ThemeMode.System, + val themeMode: ThemeMode = ThemeMode.System ) { companion object { val EMPTY: AppSettings = AppSettings() diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/requests/presentation/RequestsScreen.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/requests/presentation/RequestsScreen.kt index b67ca85..4bee5e8 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/requests/presentation/RequestsScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/requests/presentation/RequestsScreen.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import dev.chrisbanes.haze.hazeSource import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi -import dev.chrisbanes.haze.materials.HazeMaterials import dev.meloda.overseerr.screens.requests.RequestsViewModel import dev.meloda.overseerr.screens.requests.RequestsViewModelImpl import dev.meloda.overseerr.screens.requests.model.RequestsScreenState diff --git a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/settings/presentation/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/settings/presentation/SettingsScreen.kt index 40be9fe..630ce2e 100644 --- a/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/settings/presentation/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/dev/meloda/overseerr/screens/settings/presentation/SettingsScreen.kt @@ -6,7 +6,6 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType diff --git a/composeApp/src/iosMain/kotlin/main.kt b/composeApp/src/iosMain/kotlin/MainViewController.kt similarity index 56% rename from composeApp/src/iosMain/kotlin/main.kt rename to composeApp/src/iosMain/kotlin/MainViewController.kt index a0b9790..8bb405c 100644 --- a/composeApp/src/iosMain/kotlin/main.kt +++ b/composeApp/src/iosMain/kotlin/MainViewController.kt @@ -1,12 +1,5 @@ import androidx.compose.ui.window.ComposeUIViewController import dev.meloda.overseerr.App -import dev.meloda.overseerr.di.appModule -import org.koin.core.context.startKoin import platform.UIKit.UIViewController -fun MainViewController(): UIViewController = ComposeUIViewController { - startKoin { - modules(appModule) - } - App() -} +fun MainViewController(): UIViewController = ComposeUIViewController { App() } diff --git a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/Platform.ios.kt b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/Platform.ios.kt new file mode 100644 index 0000000..07e0dce --- /dev/null +++ b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/Platform.ios.kt @@ -0,0 +1,9 @@ +package dev.meloda.overseerr + +import platform.UIKit.UIDevice + +class IOSPlatform : Platform { + override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion +} + +actual fun getPlatform(): Platform = IOSPlatform() diff --git a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/settings/model/SettingsStoreProvider.ios.kt b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.ios.kt similarity index 77% rename from composeApp/src/iosMain/kotlin/dev/meloda/overseerr/settings/model/SettingsStoreProvider.ios.kt rename to composeApp/src/iosMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.ios.kt index 0991ef2..fef369a 100644 --- a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/settings/model/SettingsStoreProvider.ios.kt +++ b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.ios.kt @@ -1,6 +1,7 @@ -package dev.meloda.overseerr.settings.model +package dev.meloda.overseerr.datastore import dev.meloda.overseerr.appDir +import dev.meloda.overseerr.datastore.model.AppSettings import io.github.xxfast.kstore.KStore import io.github.xxfast.kstore.file.storeOf import kotlinx.io.files.Path diff --git a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/model/Platform.ios.kt b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/model/Platform.ios.kt deleted file mode 100644 index 9da7463..0000000 --- a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/model/Platform.ios.kt +++ /dev/null @@ -1,6 +0,0 @@ -package dev.meloda.overseerr.model - -internal actual class Platform actual constructor() { - actual val name: String - get() = "iOS" -} diff --git a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.ios.kt b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.ios.kt new file mode 100644 index 0000000..2fdc1c5 --- /dev/null +++ b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.ios.kt @@ -0,0 +1,8 @@ +package dev.meloda.overseerr.network + +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.engine.darwin.Darwin + +actual class HttpClientEngineFactoryProvider actual constructor() { + actual fun get(): HttpClientEngineFactory<*> = Darwin +} diff --git a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/model/HttpClientProvider.ios.kt b/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/model/HttpClientProvider.ios.kt deleted file mode 100644 index f683a69..0000000 --- a/composeApp/src/iosMain/kotlin/dev/meloda/overseerr/network/model/HttpClientProvider.ios.kt +++ /dev/null @@ -1,8 +0,0 @@ -package dev.meloda.overseerr.network.model - -import io.ktor.client.engine.* -import io.ktor.client.engine.darwin.* - -actual class HttpClientEngineFactoryProvider actual constructor() { - actual fun get(): HttpClientEngineFactory<*> = Darwin -} diff --git a/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/Platform.js.kt b/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/Platform.js.kt new file mode 100644 index 0000000..35aab31 --- /dev/null +++ b/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/Platform.js.kt @@ -0,0 +1,7 @@ +package dev.meloda.overseerr + +class JsPlatform : Platform { + override val name: String = "Web with Kotlin/JS" +} + +actual fun getPlatform(): Platform = JsPlatform() diff --git a/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.js.kt b/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.js.kt new file mode 100644 index 0000000..c4a3edf --- /dev/null +++ b/composeApp/src/jsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.js.kt @@ -0,0 +1,8 @@ +package dev.meloda.overseerr.network + +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.engine.js.Js + +actual class HttpClientEngineFactoryProvider actual constructor() { + actual fun get(): HttpClientEngineFactory<*> = Js +} diff --git a/composeApp/src/jsMain/kotlin/main.js.kt b/composeApp/src/jsMain/kotlin/main.js.kt new file mode 100644 index 0000000..1e3ae4d --- /dev/null +++ b/composeApp/src/jsMain/kotlin/main.js.kt @@ -0,0 +1,25 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlinx.browser.window + +@Composable +actual fun ResizableWindow(content: @Composable () -> Unit) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(window.innerWidth.coerceIn(360..600).dp) + .height(window.innerHeight.coerceIn(minimumValue = 360, maximumValue = null).dp) + ) { + content() + } + } +} diff --git a/composeApp/src/jvmMain/kotlin/dev/meloda/overseerr/Platform.jvm.kt b/composeApp/src/jvmMain/kotlin/dev/meloda/overseerr/Platform.jvm.kt new file mode 100644 index 0000000..3fb8d06 --- /dev/null +++ b/composeApp/src/jvmMain/kotlin/dev/meloda/overseerr/Platform.jvm.kt @@ -0,0 +1,7 @@ +package dev.meloda.overseerr + +class JVMPlatform : Platform { + override val name: String = "Java ${System.getProperty("java.version")}" +} + +actual fun getPlatform(): Platform = JVMPlatform() diff --git a/composeApp/src/jvmMain/kotlin/main.kt b/composeApp/src/jvmMain/kotlin/main.kt index 4616b6e..48e71bf 100644 --- a/composeApp/src/jvmMain/kotlin/main.kt +++ b/composeApp/src/jvmMain/kotlin/main.kt @@ -5,10 +5,8 @@ import androidx.compose.ui.window.application import androidx.compose.ui.window.rememberWindowState import dev.meloda.overseerr.App import dev.meloda.overseerr.appDir -import dev.meloda.overseerr.di.appModule import io.github.aakira.napier.Napier import net.harawata.appdirs.AppDirsFactory -import org.koin.compose.KoinApplication import java.awt.Dimension import java.io.File @@ -28,10 +26,10 @@ fun main() = application { state = state, onCloseRequest = ::exitApplication ) { - window.minimumSize = Dimension(320, 480) - - KoinApplication(application = { modules(appModule) }) { - App() + LaunchedEffect(Unit) { + window.minimumSize = Dimension(320, 480) } + + App() } } diff --git a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/Platform.wasmJs.kt b/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/Platform.wasmJs.kt new file mode 100644 index 0000000..c4c015f --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/Platform.wasmJs.kt @@ -0,0 +1,7 @@ +package dev.meloda.overseerr + +class WasmPlatform : Platform { + override val name: String = "Web with Kotlin/Wasm" +} + +actual fun getPlatform(): Platform = WasmPlatform() diff --git a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.wasmJs.kt b/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.wasmJs.kt index db74516..c4a3edf 100644 --- a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.wasmJs.kt +++ b/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/network/HttpClientEngineFactoryProvider.wasmJs.kt @@ -1,7 +1,7 @@ package dev.meloda.overseerr.network -import io.ktor.client.engine.* -import io.ktor.client.engine.js.* +import io.ktor.client.engine.HttpClientEngineFactory +import io.ktor.client.engine.js.Js actual class HttpClientEngineFactoryProvider actual constructor() { actual fun get(): HttpClientEngineFactory<*> = Js diff --git a/composeApp/src/wasmJsMain/kotlin/main.kt b/composeApp/src/wasmJsMain/kotlin/main.kt deleted file mode 100644 index c452e5e..0000000 --- a/composeApp/src/wasmJsMain/kotlin/main.kt +++ /dev/null @@ -1,34 +0,0 @@ -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.width -import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.ComposeViewport -import dev.meloda.overseerr.App -import dev.meloda.overseerr.di.appModule -import kotlinx.browser.document -import kotlinx.browser.window -import org.koin.compose.KoinApplication - -@OptIn(ExperimentalComposeUiApi::class) -fun main() { - ComposeViewport(document.body!!) { - KoinApplication(application = { modules(appModule) }) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Box( - modifier = Modifier - .width(window.innerWidth.coerceIn(360..600).dp) - .height(window.innerHeight.coerceIn(minimumValue = 360, maximumValue = null).dp) - ) { - App() - } - } - } - } -} diff --git a/composeApp/src/wasmJsMain/kotlin/main.wasmJs.kt b/composeApp/src/wasmJsMain/kotlin/main.wasmJs.kt new file mode 100644 index 0000000..1e3ae4d --- /dev/null +++ b/composeApp/src/wasmJsMain/kotlin/main.wasmJs.kt @@ -0,0 +1,25 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kotlinx.browser.window + +@Composable +actual fun ResizableWindow(content: @Composable () -> Unit) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Box( + modifier = Modifier + .width(window.innerWidth.coerceIn(360..600).dp) + .height(window.innerHeight.coerceIn(minimumValue = 360, maximumValue = null).dp) + ) { + content() + } + } +} diff --git a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.wasmJs.kt b/composeApp/src/webMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.web.kt similarity index 100% rename from composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.wasmJs.kt rename to composeApp/src/webMain/kotlin/dev/meloda/overseerr/datastore/SettingsStoreProvider.web.kt diff --git a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/theme/Theme.wasmJs.kt b/composeApp/src/webMain/kotlin/dev/meloda/overseerr/theme/Theme.web.kt similarity index 73% rename from composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/theme/Theme.wasmJs.kt rename to composeApp/src/webMain/kotlin/dev/meloda/overseerr/theme/Theme.web.kt index 2b7efe7..311581e 100644 --- a/composeApp/src/wasmJsMain/kotlin/dev/meloda/overseerr/theme/Theme.wasmJs.kt +++ b/composeApp/src/webMain/kotlin/dev/meloda/overseerr/theme/Theme.web.kt @@ -1,14 +1,12 @@ package dev.meloda.overseerr.theme import androidx.compose.runtime.Composable -import androidx.navigation.ExperimentalBrowserHistoryApi import androidx.navigation.NavController @Composable internal actual fun SystemAppearance(isDark: Boolean) { } -@OptIn(ExperimentalBrowserHistoryApi::class) @Composable internal actual fun NavigationSettings(navController: NavController) { } diff --git a/composeApp/src/webMain/kotlin/main.kt b/composeApp/src/webMain/kotlin/main.kt new file mode 100644 index 0000000..655e644 --- /dev/null +++ b/composeApp/src/webMain/kotlin/main.kt @@ -0,0 +1,16 @@ +import androidx.compose.runtime.Composable +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.ComposeViewport +import dev.meloda.overseerr.App + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + ComposeViewport { + ResizableWindow { + App() + } + } +} + +@Composable +expect fun ResizableWindow(content: @Composable () -> Unit) diff --git a/composeApp/src/wasmJsMain/resources/index.html b/composeApp/src/webMain/resources/index.html similarity index 91% rename from composeApp/src/wasmJsMain/resources/index.html rename to composeApp/src/webMain/resources/index.html index d88af02..ad6778e 100644 --- a/composeApp/src/wasmJsMain/resources/index.html +++ b/composeApp/src/webMain/resources/index.html @@ -3,7 +3,7 @@ - Wasm App + Web App diff --git a/composeApp/src/wasmJsMain/resources/styles.css b/composeApp/src/webMain/resources/styles.css similarity index 100% rename from composeApp/src/wasmJsMain/resources/styles.css rename to composeApp/src/webMain/resources/styles.css diff --git a/composeApp/webpack.config.d/watch.js b/composeApp/webpack.config.d/watch.js new file mode 100644 index 0000000..04713d2 --- /dev/null +++ b/composeApp/webpack.config.d/watch.js @@ -0,0 +1,21 @@ +/* + * Temporary workaround for [KT-80582](https://youtrack.jetbrains.com/issue/KT-80582) + * + * This file should be safe to be removed once the ticket is closed and the project is updated to Kotlin version which solves that issue. + */ +config.watchOptions = config.watchOptions || { + ignored: ["**/*.kt", "**/node_modules"] +} + +if (config.devServer) { + config.devServer.static = config.devServer.static.map(file => { + if (typeof file === "string") { + return { + directory: file, + watch: false, + } + } else { + return file + } + }) +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index f0a7214..2317839 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,22 +1,16 @@ +#Kotlin +kotlin.code.style=official +kotlin.daemon.jvmargs=-Xmx4G #Gradle -org.gradle.jvmargs=-Xmx4G +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 org.gradle.caching=true org.gradle.configuration-cache=true org.gradle.daemon=true org.gradle.parallel=true - -#Kotlin -kotlin.code.style=official -kotlin.daemon.jvmargs=-Xmx4G -kotlin.native.ignoreDisabledTargets=true -kotlin.native.enableKlibsCrossCompilation=true - #Android android.useAndroidX=true android.nonTransitiveRClass=true -include_wasm=true include_ios=false - #Flip this to false when including the ios targets org.gradle.unsafe.configuration-cache=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 02a78e1..e3f656c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -34,7 +34,7 @@ kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-c kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" } -ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } +ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" } ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" } ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" } @@ -52,6 +52,8 @@ kstore-storage = { module = "io.github.xxfast:kstore-storage", version.ref = "ks appdirs = { module = "net.harawata:appdirs", version.ref = "appdirs" } napier = { module = "io.github.aakira:napier", version.ref = "napier" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } + [plugins] multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index 49d7c2a..608c6c7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,32 +4,32 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") pluginManagement { repositories { google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - includeGroupByRegex("android.*") + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") } } gradlePluginPortal() mavenCentral() } } -plugins { - id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" -} dependencyResolutionManagement { repositories { google { - content { - includeGroupByRegex("com\\.android.*") - includeGroupByRegex("com\\.google.*") - includeGroupByRegex("androidx.*") - includeGroupByRegex("android.*") + mavenContent { + includeGroupAndSubgroups("androidx") + includeGroupAndSubgroups("com.android") + includeGroupAndSubgroups("com.google") } } mavenCentral() } } + +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" +} + include(":composeApp")