diff --git a/build.gradle.kts b/build.gradle.kts index afdb955..06a4f50 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - id("root.publication") - //trick: for the same plugin versions in all sub-modules +// id("root.publication") + // trick: for the same plugin versions in all sub-modules alias(libs.plugins.androidApplication).apply(false) alias(libs.plugins.androidLibrary).apply(false) alias(libs.plugins.kotlinMultiplatform).apply(false) @@ -8,4 +8,7 @@ plugins { alias(libs.plugins.kotlinJvm).apply(false) alias(libs.plugins.kotlinSerialization).apply(false) alias(libs.plugins.composeMultiplatform).apply(false) -} \ No newline at end of file + alias(libs.plugins.conventionPlugin).apply(false) +} + +rootPublicationSetup() diff --git a/calf-core/build.gradle.kts b/calf-core/build.gradle.kts index 95e9bde..26c7b3a 100644 --- a/calf-core/build.gradle.kts +++ b/calf-core/build.gradle.kts @@ -1,55 +1,15 @@ -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi - plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) - id("module.publication") } -kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - applyDefaultHierarchyTemplate { - common { - group("nonAndroid") { - withJvm() - withIos() - withJs() - } - } - } - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +composeMultiplatformSetup() +modulePublicationSetup() +kotlin { sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) } } - -android { - namespace = "com.mohamedrejeb.calf.core" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/calf-file-picker/build.gradle.kts b/calf-file-picker/build.gradle.kts index a589980..de4c872 100644 --- a/calf-file-picker/build.gradle.kts +++ b/calf-file-picker/build.gradle.kts @@ -2,36 +2,20 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) - id("module.publication") } -kotlin { - kotlin.applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +composeMultiplatformSetup() +modulePublicationSetup() - sourceSets.commonMain.get().dependencies { +kotlin { + sourceSets.commonMain.dependencies { api(projects.calfIo) implementation(compose.runtime) implementation(compose.foundation) } - sourceSets.commonTest.get().dependencies { + + sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) } @@ -39,15 +23,3 @@ kotlin { implementation(libs.activity.compose) } } - -android { - namespace = "com.mohamedrejeb.calf.picker" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/calf-file-picker/src/jsMain/kotlin/com.mohamedrejeb.calf.picker/FilePickerLauncher.js.kt b/calf-file-picker/src/jsMain/kotlin/com.mohamedrejeb.calf.picker/FilePickerLauncher.js.kt index 24b9279..9b07e68 100644 --- a/calf-file-picker/src/jsMain/kotlin/com.mohamedrejeb.calf.picker/FilePickerLauncher.js.kt +++ b/calf-file-picker/src/jsMain/kotlin/com.mohamedrejeb.calf.picker/FilePickerLauncher.js.kt @@ -41,7 +41,7 @@ actual fun rememberFilePickerLauncher( fileDialogVisible = false }) - js("fileInputElement.click();") + js("fileInputElement.click()") Unit }, diff --git a/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/ComposeResource.wasmJs.kt b/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/ComposeResource.wasmJs.kt new file mode 100644 index 0000000..5a94831 --- /dev/null +++ b/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/ComposeResource.wasmJs.kt @@ -0,0 +1,7 @@ +package com.mohamedrejeb.calf.picker + +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.toComposeImageBitmap +import org.jetbrains.skia.Image + +actual fun ByteArray.toImageBitmap(): ImageBitmap = Image.makeFromEncoded(this).toComposeImageBitmap() diff --git a/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/FilePickerLauncher.wasmJs.kt b/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/FilePickerLauncher.wasmJs.kt new file mode 100644 index 0000000..2bd513f --- /dev/null +++ b/calf-file-picker/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/picker/FilePickerLauncher.wasmJs.kt @@ -0,0 +1,72 @@ +package com.mohamedrejeb.calf.picker + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import com.mohamedrejeb.calf.io.KmpFile +import kotlinx.browser.document +import org.w3c.dom.Element +import org.w3c.files.File + +@Composable +actual fun rememberFilePickerLauncher( + type: FilePickerFileType, + selectionMode: FilePickerSelectionMode, + onResult: (List) -> Unit, +): FilePickerLauncher { + var fileDialogVisible by rememberSaveable { mutableStateOf(false) } + + return remember { + FilePickerLauncher( + type = type, + selectionMode = selectionMode, + onLaunch = { + val fileInputElement = document.createElement("input") + fileInputElement.setAttribute("style", "display='none'") + fileInputElement.setAttribute("type", "file") + fileInputElement.setAttribute("name", "file") + + fileInputElement.setAttribute("accept", type.value.joinToString(", ")) + + if (selectionMode == FilePickerSelectionMode.Multiple) + fileInputElement.setAttribute("multiple", "true") + else + fileInputElement.removeAttribute("multiple") + + fileInputElement.addEventListener("change") { + val filesCount = getInputElementFilesCount(fileInputElement) + val files = + List(filesCount) { index -> + getInputElementFile(fileInputElement, index) + } + onResult(files.map { KmpFile(it) }) + fileDialogVisible = false + } + + openFileDialog(fileInputElement) + }, + ) + } +} + +actual class FilePickerLauncher actual constructor( + type: FilePickerFileType, + selectionMode: FilePickerSelectionMode, + private val onLaunch: () -> Unit, +) { + actual fun launch() { + onLaunch() + } +} + +private fun getInputElementFilesCount(element: Element): Int = js("element.files.length") + +private fun getInputElementFile( + element: Element, + index: Int, +): File = js("element.files[index]") + +private fun openFileDialog(element: Element): Unit = js("element.click()") diff --git a/calf-geo/build.gradle.kts b/calf-geo/build.gradle.kts index 7fdd404..39d2ab8 100644 --- a/calf-geo/build.gradle.kts +++ b/calf-geo/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } kotlin { @@ -25,11 +24,11 @@ kotlin { iosArm64() iosSimulatorArm64() - sourceSets.commonMain.get().dependencies { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) } - sourceSets.androidMain.get().dependencies { + sourceSets.androidMain.dependencies { implementation(libs.appcompat) implementation(libs.lifecycle.extensions) implementation(libs.play.services.location) diff --git a/calf-io/build.gradle.kts b/calf-io/build.gradle.kts index 1d0b738..5fc10ae 100644 --- a/calf-io/build.gradle.kts +++ b/calf-io/build.gradle.kts @@ -1,44 +1,17 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.androidLibrary) - id("module.publication") } -kotlin { - kotlin.applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +kotlinMultiplatformSetup() +modulePublicationSetup() +kotlin { sourceSets.commonMain.dependencies { api(projects.calfCore) - - implementation(libs.documentfile) } -} -android { - namespace = "com.mohamedrejeb.calf.io" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceSets.androidMain.dependencies { + implementation(libs.documentfile) } } diff --git a/calf-io/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/io/KmpFile.wasmJs.kt b/calf-io/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/io/KmpFile.wasmJs.kt new file mode 100644 index 0000000..9a86c42 --- /dev/null +++ b/calf-io/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/io/KmpFile.wasmJs.kt @@ -0,0 +1,50 @@ +package com.mohamedrejeb.calf.io + +import com.mohamedrejeb.calf.core.PlatformContext +import org.khronos.webgl.ArrayBuffer +import org.khronos.webgl.Uint8Array +import org.khronos.webgl.get +import org.w3c.dom.events.Event +import org.w3c.files.File +import org.w3c.files.FileReader +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine + +/** + * Represents a file in a platform-agnostic way. + */ +actual class KmpFile( + val file: File, +) + +actual fun KmpFile.exists(context: PlatformContext) = true + +actual suspend fun KmpFile.readByteArray(context: PlatformContext): ByteArray = + suspendCoroutine { continuation -> + val fileReader = FileReader() + fileReader.readAsArrayBuffer(file) + fileReader.onloadend = { event -> + val readyState = getReadyState(event) + if (readyState == FileReader.DONE) { + val arrayBuffer: ArrayBuffer = getResult(event) + val array = Uint8Array(arrayBuffer) + val byteArray = + ByteArray(array.length) { index -> + array[index] + } + continuation.resume(byteArray) + } else { + continuation.resume(ByteArray(0)) + } + } + } + +actual fun KmpFile.getName(context: PlatformContext): String? = file.name + +actual fun KmpFile.getPath(context: PlatformContext): String? = file.name + +actual fun KmpFile.isDirectory(context: PlatformContext): Boolean = !file.name.contains(".") + +private fun getReadyState(event: Event): Short = js("event.target.readyState") + +private fun getResult(event: Event): ArrayBuffer = js("event.target.result") diff --git a/calf-maps/build.gradle.kts b/calf-maps/build.gradle.kts index 36ac623..38bfe9e 100644 --- a/calf-maps/build.gradle.kts +++ b/calf-maps/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } kotlin { @@ -25,12 +24,12 @@ kotlin { iosArm64() iosSimulatorArm64() - sourceSets.commonMain.get().dependencies { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } - sourceSets.commonTest.get().dependencies { + sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) } } diff --git a/calf-media/build.gradle.kts b/calf-media/build.gradle.kts index 601976d..447c199 100644 --- a/calf-media/build.gradle.kts +++ b/calf-media/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } kotlin { @@ -25,12 +24,12 @@ kotlin { iosArm64() iosSimulatorArm64() - sourceSets.commonMain.get().dependencies { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } - sourceSets.commonTest.get().dependencies { + sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) } } diff --git a/calf-navigation/build.gradle.kts b/calf-navigation/build.gradle.kts index 6c6d2c0..f343bd5 100644 --- a/calf-navigation/build.gradle.kts +++ b/calf-navigation/build.gradle.kts @@ -1,57 +1,19 @@ plugins { alias(libs.plugins.kotlinMultiplatform) -// alias(libs.plugins.kotlinSerialization) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } -kotlin { - kotlin.applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +composeMultiplatformSetup() - sourceSets.commonMain.get().dependencies { +kotlin { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) -// implementation(libs.kotlinx.serialization.json) } - sourceSets.commonTest.get().dependencies { - implementation(libs.kotlin.test) - } - sourceSets { - val androidMain by getting { - dependencies { - implementation(libs.navigation.compose) - } - } - } -} -android { - namespace = "com.mohamedrejeb.calf.navigation" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceSets.androidMain.dependencies { + implementation(libs.navigation.compose) } } diff --git a/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/AndroidBackHandler.wasmJs.kt b/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/AndroidBackHandler.wasmJs.kt new file mode 100644 index 0000000..77b6829 --- /dev/null +++ b/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/AndroidBackHandler.wasmJs.kt @@ -0,0 +1,11 @@ +package com.mohamedrejeb.calf.navigation + +import androidx.compose.runtime.Composable + +@Composable +actual fun AndroidBackHandler( + enabled: Boolean, + onBack: () -> Unit, +) { + // This is a no-op on web +} diff --git a/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/NavHostController.wasmJs.kt b/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/NavHostController.wasmJs.kt new file mode 100644 index 0000000..0e11b7f --- /dev/null +++ b/calf-navigation/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/navigation/NavHostController.wasmJs.kt @@ -0,0 +1,11 @@ +package com.mohamedrejeb.calf.navigation + +actual typealias NavHostController = AdaptiveNavHostController + +actual fun NavHostController.navigate(route: String) { + navigate(route) +} + +actual fun NavHostController.popBackStack(): Boolean { + return popBackStack() +} diff --git a/calf-notifications/build.gradle.kts b/calf-notifications/build.gradle.kts index 601976d..447c199 100644 --- a/calf-notifications/build.gradle.kts +++ b/calf-notifications/build.gradle.kts @@ -2,7 +2,6 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } kotlin { @@ -25,12 +24,12 @@ kotlin { iosArm64() iosSimulatorArm64() - sourceSets.commonMain.get().dependencies { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } - sourceSets.commonTest.get().dependencies { + sourceSets.commonTest.dependencies { implementation(libs.kotlin.test) } } diff --git a/calf-permissions/build.gradle.kts b/calf-permissions/build.gradle.kts index 9b1ba2f..8c4800a 100644 --- a/calf-permissions/build.gradle.kts +++ b/calf-permissions/build.gradle.kts @@ -2,54 +2,19 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) - id("module.publication") } -kotlin { - kotlin.applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +composeMultiplatformSetup() +modulePublicationSetup() - sourceSets.commonMain.get().dependencies { +kotlin { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } - sourceSets.commonTest.get().dependencies { - implementation(libs.kotlin.test) - } - sourceSets.androidMain.get().dependencies { - implementation(libs.activity.compose) - } -} -android { - namespace = "com.mohamedrejeb.calf.permissions" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 + sourceSets.androidMain.dependencies { + implementation(libs.activity.compose) } } diff --git a/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutableMultiplePermissionsState.wasmJs.kt b/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutableMultiplePermissionsState.wasmJs.kt new file mode 100644 index 0000000..24ab958 --- /dev/null +++ b/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutableMultiplePermissionsState.wasmJs.kt @@ -0,0 +1,82 @@ +package com.mohamedrejeb.calf.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember + +/** + * Creates a [MultiplePermissionsState] that is remembered across compositions. + * + * It's recommended that apps exercise the permissions workflow as described in the + * [documentation](https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions). + * + * @param permissions the permissions to control and observe. + * @param onPermissionsResult will be called with whether or not the user granted the permissions + * after [MultiplePermissionsState.launchMultiplePermissionRequest] is called. + */ +@ExperimentalPermissionsApi +@Composable +internal actual fun rememberMutableMultiplePermissionsState( + permissions: List, + onPermissionsResult: (Map) -> Unit, +): MultiplePermissionsState { + // Create mutable permissions that can be requested individually + val mutablePermissions = + permissions.map { permission -> + rememberMutablePermissionState(permission) { granted -> + onPermissionsResult(mapOf(permission to granted)) + } + } + + val multiplePermissionsState = + remember(permissions) { + MutableMultiplePermissionsState(mutablePermissions) + } + + return multiplePermissionsState +} + +/** + * A state object that can be hoisted to control and observe multiple permission status changes. + * + * In most cases, this will be created via [rememberMutableMultiplePermissionsState]. + * + * @param mutablePermissions list of mutable permissions to control and observe. + */ +@ExperimentalPermissionsApi +@Stable +internal actual class MutableMultiplePermissionsState actual constructor( + private val mutablePermissions: List, +) : MultiplePermissionsState { + override val permissions: List = mutablePermissions + + override val revokedPermissions: List by derivedStateOf { + permissions.filter { it.status != PermissionStatus.Granted } + } + + override val allPermissionsGranted: Boolean by derivedStateOf { + permissions.all { it.status.isGranted } || // Up to date when the lifecycle is resumed + revokedPermissions.isEmpty() // Up to date when the user launches the action + } + + override val shouldShowRationale: Boolean by derivedStateOf { + permissions.any { it.status.shouldShowRationale } + } + + override fun launchMultiplePermissionRequest() { + // Launch the permission request + } + + internal actual fun updatePermissionsStatus(permissionsStatus: Map) { + // Update all permissions with the result + for (permission in permissionsStatus.keys) { + mutablePermissions.firstOrNull { it.permission == permission }?.apply { + permissionsStatus[permission]?.let { + this.refreshPermissionStatus() + } + } + } + } +} diff --git a/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutablePermissionState.wasmJs.kt b/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutablePermissionState.wasmJs.kt new file mode 100644 index 0000000..53a6612 --- /dev/null +++ b/calf-permissions/src/wasmJsMain/kotlin/com.mohamedrejeb.calf/permissions/MutablePermissionState.wasmJs.kt @@ -0,0 +1,54 @@ +package com.mohamedrejeb.calf.permissions + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue + +/** + * Creates a [MutablePermissionState] that is remembered across compositions. + * + * It's recommended that apps exercise the permissions workflow as described in the + * [documentation](https://developer.android.com/training/permissions/requesting#workflow_for_requesting_permissions). + * + * @param permission the permission to control and observe. + * @param onPermissionResult will be called with whether or not the user granted the permission + * after [PermissionState.launchPermissionRequest] is called. + */ +@ExperimentalPermissionsApi +@Composable +internal actual fun rememberMutablePermissionState( + permission: Permission, + onPermissionResult: (Boolean) -> Unit, +): MutablePermissionState { + return remember(permission) { + MutablePermissionState(permission) + } +} + +/** + * A mutable state object that can be used to control and observe permission status changes. + * + * In most cases, this will be created via [rememberMutablePermissionState]. + * + * @param permission the permission to control and observe. + */ +@ExperimentalPermissionsApi +@Stable +internal actual class MutablePermissionState actual constructor( + override val permission: Permission, +) : PermissionState { + override var status: PermissionStatus by mutableStateOf(getPermissionStatus()) + + override fun launchPermissionRequest() {} + + override fun openAppSettings() {} + + internal actual fun refreshPermissionStatus() {} + + private fun getPermissionStatus(): PermissionStatus { + return PermissionStatus.Denied(false) + } +} diff --git a/calf-sf-symbols/build.gradle.kts b/calf-sf-symbols/build.gradle.kts index e039fda..c41cf71 100644 --- a/calf-sf-symbols/build.gradle.kts +++ b/calf-sf-symbols/build.gradle.kts @@ -2,47 +2,14 @@ plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) -// id("module.publication") } -kotlin { - kotlin.applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() +composeMultiplatformSetup() - sourceSets.commonMain.get().dependencies { +kotlin { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material) } - sourceSets.commonTest.get().dependencies { - implementation(libs.kotlin.test) - } -} - -android { - namespace = "com.mohamedrejeb.calf.sf.symbols" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } } diff --git a/calf-ui/build.gradle.kts b/calf-ui/build.gradle.kts index d9941b0..aad4251 100644 --- a/calf-ui/build.gradle.kts +++ b/calf-ui/build.gradle.kts @@ -1,56 +1,27 @@ import org.gradle.internal.os.OperatingSystem -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) alias(libs.plugins.androidLibrary) - id("module.publication") } +composeMultiplatformSetup() +modulePublicationSetup() + val os: OperatingSystem = OperatingSystem.current() val arch: String = System.getProperty("os.arch") val isAarch64: Boolean = arch.contains("aarch64") -println("arch: $arch") - -val platform = when { - os.isWindows -> "win" - os.isMacOsX -> "mac" - else -> "linux" -} + if (isAarch64) "-aarch64" else "" +val platform = + when { + os.isWindows -> "win" + os.isMacOsX -> "mac" + else -> "linux" + } + if (isAarch64) "-aarch64" else "" kotlin { - @OptIn(ExperimentalKotlinGradlePluginApi::class) - applyDefaultHierarchyTemplate { - common { - group("material") { - withAndroidTarget() - withJvm() - withJs() - } - } - } - - androidTarget { - publishLibraryVariants("release") - compilations.all { - kotlinOptions { - jvmTarget = "11" - } - } - } - jvm("desktop") { - jvmToolchain(11) - } - js(IR) { - browser() - } - iosX64() - iosArm64() - iosSimulatorArm64() - - sourceSets.commonMain.get().dependencies { + sourceSets.commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) implementation(compose.material3) @@ -62,7 +33,7 @@ kotlin { implementation(libs.kotlinx.coroutines.android) } - sourceSets.named("desktopMain").dependencies { + sourceSets.desktopMain.dependencies { implementation("org.openjfx:javafx-base:19:$platform") implementation("org.openjfx:javafx-graphics:19:$platform") implementation("org.openjfx:javafx-controls:19:$platform") @@ -72,15 +43,3 @@ kotlin { implementation(libs.kotlinx.coroutines.javafx) } } - -android { - namespace = "com.mohamedrejeb.calf.ui" - compileSdk = libs.versions.android.compileSdk.get().toInt() - defaultConfig { - minSdk = libs.versions.android.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_11 - targetCompatibility = JavaVersion.VERSION_11 - } -} diff --git a/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/datepicker/AdaptiveDatePickerState.wasmJs.kt b/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/datepicker/AdaptiveDatePickerState.wasmJs.kt new file mode 100644 index 0000000..babb068 --- /dev/null +++ b/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/datepicker/AdaptiveDatePickerState.wasmJs.kt @@ -0,0 +1,5 @@ +package com.mohamedrejeb.calf.ui.datepicker + +import androidx.compose.material3.CalendarLocale + +internal actual fun getCalendarLocalDefault(): CalendarLocale = CalendarLocale.current diff --git a/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/web/WebView.wasmJs.kt b/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/web/WebView.wasmJs.kt new file mode 100644 index 0000000..83c4530 --- /dev/null +++ b/calf-ui/src/wasmJsMain/kotlin/com.mohamedrejeb.calf.ui/web/WebView.wasmJs.kt @@ -0,0 +1,120 @@ +package com.mohamedrejeb.calf.ui.web + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.mapSaver +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * A wrapper around the Android View WebView to provide a basic WebView composable. + * + * If you require more customisation you are most likely better rolling your own and using this + * wrapper as an example. + * + * The WebView attempts to set the layoutParams based on the Compose modifier passed in. If it + * is incorrectly sizing, use the layoutParams composable function instead. + * + * @param state The webview state holder where the Uri to load is defined. + * @param modifier A compose modifier + * @param captureBackPresses Set to true to have this Composable capture back presses and navigate + * the WebView back. + * @param navigator An optional navigator object that can be used to control the WebView's + * navigation from outside the composable. + * @param onCreated Called when the WebView is first created, this can be used to set additional + * settings on the WebView. WebChromeClient and WebViewClient should not be set here as they will be + * subsequently overwritten after this lambda is called. + * @param onDispose Called when the WebView is destroyed. Provides a bundle which can be saved + * if you need to save and restore state in this WebView. + * @param client Provides access to WebViewClient via subclassing + * @param chromeClient Provides access to WebChromeClient via subclassing + * @param factory An optional WebView factory for using a custom subclass of WebView + * @sample com.google.accompanist.sample.webview.BasicWebViewSample + */ +@Composable +actual fun WebView( + state: WebViewState, + modifier: Modifier, + captureBackPresses: Boolean, + navigator: WebViewNavigator, + onCreated: () -> Unit, + onDispose: () -> Unit, +) { +} + +/** + * A state holder to hold the state for the WebView. In most cases this will be remembered + * using the rememberWebViewState(uri) function. + */ +@Stable +actual class WebViewState actual constructor(webContent: WebContent) { + actual var lastLoadedUrl: String? by mutableStateOf(null) + internal set + + /** + * The content being loaded by the WebView + */ + actual var content: WebContent by mutableStateOf(webContent) + + /** + * Whether the WebView is currently [LoadingState.Loading] data in its main frame (along with + * progress) or the data loading has [LoadingState.Finished]. See [LoadingState] + */ + actual var loadingState: LoadingState by mutableStateOf(LoadingState.Initializing) + internal set + + /** + * Whether the webview is currently loading data in its main frame + */ + actual val isLoading: Boolean + get() = loadingState !is LoadingState.Finished + + /** + * The title received from the loaded content of the current page + */ + actual var pageTitle: String? by mutableStateOf(null) + internal set + + actual val settings: WebSettings = + WebSettings( + onSettingsChanged = {}, + ) + + actual fun evaluateJavascript( + script: String, + callback: ((String?) -> Unit)?, + ) {} +} + +// Use Dispatchers.Main to ensure that the webview methods are called on UI thread +internal suspend fun WebViewNavigator.handleNavigationEvents(): Nothing = + withContext(Dispatchers.Main) { + navigationEvents.collect { event -> + } + } + +actual val WebStateSaver: Saver = + run { + val pageTitleKey = "pagetitle" + val lastLoadedUrlKey = "lastloaded" + + mapSaver( + save = { + mapOf( + pageTitleKey to it.pageTitle, + lastLoadedUrlKey to it.lastLoadedUrl, + ) + }, + restore = { + WebViewState(WebContent.NavigatorOnly).apply { + this.pageTitle = it[pageTitleKey] as String? + this.lastLoadedUrl = it[lastLoadedUrlKey] as String? + } + }, + ) + } diff --git a/convention-plugins/build.gradle.kts b/convention-plugins/build.gradle.kts index 08d502d..226f890 100644 --- a/convention-plugins/build.gradle.kts +++ b/convention-plugins/build.gradle.kts @@ -1,7 +1,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { - `kotlin-dsl` + `kotlin-dsl-base` + `java-gradle-plugin` } java { @@ -15,6 +16,22 @@ tasks.withType { } } +group = "com.mohamedrejeb.gradle" +version = "0.1.0" + dependencies { + implementation(libs.gradlePlugin.android) + implementation(libs.gradlePlugin.jetbrainsCompose) + implementation(libs.gradlePlugin.kotlin) implementation(libs.nexus.publish) + + // hack to access version catalogue https://github.com/gradle/gradle/issues/15383 + compileOnly(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) +} + +gradlePlugin { + plugins.create(project.name) { + id = "com.mohamedrejeb.gradle.setup" + implementationClass = "com.mohamedrejeb.gradle.GradleSetupPlugin" + } } diff --git a/convention-plugins/src/main/kotlin/Android.kt b/convention-plugins/src/main/kotlin/Android.kt new file mode 100644 index 0000000..0d8846a --- /dev/null +++ b/convention-plugins/src/main/kotlin/Android.kt @@ -0,0 +1,23 @@ +import com.android.build.api.dsl.LibraryExtension +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.JavaVersion +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.the + +fun Project.androidLibrarySetup() { + val libs = the() + + extensions.configure { + namespace = group.toString() + path.replace("-", "").split(":").joinToString(".") + compileSdk = libs.versions.android.compileSdk.get().toInt() + + defaultConfig { + minSdk = libs.versions.android.minSdk.get().toInt() + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + } +} diff --git a/convention-plugins/src/main/kotlin/ComposeMultiplatform.kt b/convention-plugins/src/main/kotlin/ComposeMultiplatform.kt new file mode 100644 index 0000000..1edb787 --- /dev/null +++ b/convention-plugins/src/main/kotlin/ComposeMultiplatform.kt @@ -0,0 +1,18 @@ +import org.gradle.api.Project + +fun Project.composeMultiplatformSetup() { + require(plugins.any { it.toString().startsWith("org.jetbrains.kotlin") }) { + "Kotlin plugin must be applied before to use Compose Multiplatform" + } + require(plugins.any { it.toString().startsWith("org.jetbrains.compose") }) { + "Compose plugin must be applied before to use Compose Multiplatform" + } + + kotlin { + applyHierarchyTemplate() + + applyTargets() + } + + androidLibrarySetup() +} diff --git a/convention-plugins/src/main/kotlin/GradleSetupPlugin.kt b/convention-plugins/src/main/kotlin/GradleSetupPlugin.kt new file mode 100644 index 0000000..3469fcc --- /dev/null +++ b/convention-plugins/src/main/kotlin/GradleSetupPlugin.kt @@ -0,0 +1,6 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project + +class GradleSetupPlugin : Plugin { + override fun apply(target: Project) {} +} diff --git a/convention-plugins/src/main/kotlin/Hirearchy.kt b/convention-plugins/src/main/kotlin/Hirearchy.kt new file mode 100644 index 0000000..a2656d2 --- /dev/null +++ b/convention-plugins/src/main/kotlin/Hirearchy.kt @@ -0,0 +1,35 @@ +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.NamedDomainObjectProvider +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet + +@OptIn(ExperimentalKotlinGradlePluginApi::class) +fun KotlinMultiplatformExtension.applyHierarchyTemplate() { + applyDefaultHierarchyTemplate { + common { + group("material") { + withAndroidTarget() + withJvm() + withJs() + withWasm() + } + + group("nonAndroid") { + withJvm() + withIos() + withJs() + withWasm() + } + } + } +} + +val NamedDomainObjectContainer.desktopMain: NamedDomainObjectProvider + get() = named("desktopMain") + +val NamedDomainObjectContainer.materialMain: NamedDomainObjectProvider + get() = named("materialMain") + +val NamedDomainObjectContainer.nonAndroidMain: NamedDomainObjectProvider + get() = named("nonAndroidMain") diff --git a/convention-plugins/src/main/kotlin/KotlinMultiplatform.kt b/convention-plugins/src/main/kotlin/KotlinMultiplatform.kt new file mode 100644 index 0000000..5bb2600 --- /dev/null +++ b/convention-plugins/src/main/kotlin/KotlinMultiplatform.kt @@ -0,0 +1,15 @@ +import org.gradle.api.Project + +fun Project.kotlinMultiplatformSetup() { + require(plugins.any { it.toString().startsWith("org.jetbrains.kotlin") }) { + "Kotlin plugin must be applied before to use Compose Multiplatform" + } + + kotlin { + applyHierarchyTemplate() + + applyTargets() + } + + androidLibrarySetup() +} diff --git a/convention-plugins/src/main/kotlin/Publication.kt b/convention-plugins/src/main/kotlin/Publication.kt new file mode 100644 index 0000000..152fa8c --- /dev/null +++ b/convention-plugins/src/main/kotlin/Publication.kt @@ -0,0 +1,92 @@ +import org.gradle.api.Project +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.publish.maven.tasks.AbstractPublishToMaven +import org.gradle.api.tasks.bundling.Jar +import org.gradle.kotlin.dsl.apply +import org.gradle.kotlin.dsl.register +import org.gradle.kotlin.dsl.withType +import org.gradle.plugins.signing.Sign + +fun Project.rootPublicationSetup() { + apply(plugin = "io.github.gradle-nexus.publish-plugin") + + allprojects { + group = "com.mohamedrejeb.calf" + version = "0.3.1" + } + + nexusPublishing { + // Configure maven central repository + // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh + repositories { + sonatype { + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + stagingProfileId.set(System.getenv("OSSRH_STAGING_PROFILE_ID")) + username.set(System.getenv("OSSRH_USERNAME")) + password.set(System.getenv("OSSRH_PASSWORD")) + } + } + } +} + +fun Project.modulePublicationSetup() { + apply(plugin = "maven-publish") + apply(plugin = "signing") + + publishing { + // Configure all publications + publications.withType { + // Stub javadoc.jar artifact + artifact( + tasks.register("${name}JavadocJar", Jar::class) { + archiveClassifier.set("javadoc") + archiveAppendix.set(this@withType.name) + }, + ) + + // Provide artifacts information required by Maven Central + pom { + name.set("Calf - Compose Adaptive Look & Feel") + description.set("Calf is a library that allows you to easily create adaptive UIs for your Compose Multiplatform apps.") + url.set("https://github.com/MohamedRejeb/Calf") + + licenses { + license { + name.set("Apache-2.0") + url.set("https://opensource.org/licenses/Apache-2.0") + } + } + developers { + developer { + id.set("MohamedRejeb") + name.set("Mohamed Rejeb") + email.set("mohamedrejeb445@gmail.com") + } + } + issueManagement { + system.set("Github") + url.set("https://github.com/MohamedRejeb/Calf/issues") + } + scm { + connection.set("https://github.com/MohamedRejeb/Calf.git") + url.set("https://github.com/MohamedRejeb/Calf") + } + } + } + } + + signing { + useInMemoryPgpKeys( + System.getenv("OSSRH_GPG_SECRET_KEY_ID"), + System.getenv("OSSRH_GPG_SECRET_KEY"), + System.getenv("OSSRH_GPG_SECRET_KEY_PASSWORD"), + ) + sign(publishing.publications) + } + + // TODO: remove after https://youtrack.jetbrains.com/issue/KT-46466 is fixed + project.tasks.withType(AbstractPublishToMaven::class.java).configureEach { + dependsOn(project.tasks.withType(Sign::class.java)) + } +} diff --git a/convention-plugins/src/main/kotlin/Targets.kt b/convention-plugins/src/main/kotlin/Targets.kt new file mode 100644 index 0000000..f59f9bf --- /dev/null +++ b/convention-plugins/src/main/kotlin/Targets.kt @@ -0,0 +1,30 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +fun KotlinMultiplatformExtension.applyTargets() { + androidTarget { + publishLibraryVariants("release") + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + jvm("desktop") { + jvmToolchain(11) + } + + js { + browser() + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + } + + iosX64() + iosArm64() + iosSimulatorArm64() +} diff --git a/convention-plugins/src/main/kotlin/Utils.kt b/convention-plugins/src/main/kotlin/Utils.kt new file mode 100644 index 0000000..641126e --- /dev/null +++ b/convention-plugins/src/main/kotlin/Utils.kt @@ -0,0 +1,68 @@ +import io.github.gradlenexus.publishplugin.NexusPublishExtension +import org.gradle.api.Action +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +/** + * Retrieves the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension] extension. + */ +internal val org.gradle.api.Project.kotlin: org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName( + "kotlin", + ) as org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension + +/** + * Configures the [kotlin][org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension] extension. + */ +internal fun org.gradle.api.Project.kotlin(configure: Action): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("kotlin", configure) + +/** + * Retrieves the [compose][org.jetbrains.compose.ComposePlugin.Dependencies] extension. + */ +internal val org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.compose: org.jetbrains.compose.ComposePlugin.Dependencies get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("compose") as org.jetbrains.compose.ComposePlugin.Dependencies + +/** + * Configures the [compose][org.jetbrains.compose.ComposePlugin.Dependencies] extension. + */ +internal fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.compose( + configure: Action, +): Unit = (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("compose", configure) + +/** + * Retrieves the [nexusPublishing][io.github.gradlenexus.publishplugin.NexusPublishExtension] extension. + */ +internal val org.gradle.api.Project.`nexusPublishing`: io.github.gradlenexus.publishplugin.NexusPublishExtension get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName( + "nexusPublishing", + ) as io.github.gradlenexus.publishplugin.NexusPublishExtension + +/** + * Configures the [nexusPublishing][io.github.gradlenexus.publishplugin.NexusPublishExtension] extension. + */ +internal fun org.gradle.api.Project.`nexusPublishing`(configure: Action): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("nexusPublishing", configure) + +/** + * Retrieves the [publishing][org.gradle.api.publish.PublishingExtension] extension. + */ +internal val org.gradle.api.Project.`publishing`: org.gradle.api.publish.PublishingExtension get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("publishing") as org.gradle.api.publish.PublishingExtension + +/** + * Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension. + */ +internal fun org.gradle.api.Project.`publishing`(configure: Action): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) + +/** + * Retrieves the [signing][org.gradle.plugins.signing.SigningExtension] extension. + */ +internal val org.gradle.api.Project.`signing`: org.gradle.plugins.signing.SigningExtension get() = + (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("signing") as org.gradle.plugins.signing.SigningExtension + +/** + * Configures the [signing][org.gradle.plugins.signing.SigningExtension] extension. + */ +internal fun org.gradle.api.Project.`signing`(configure: Action): Unit = + (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("signing", configure) diff --git a/convention-plugins/src/main/kotlin/module.publication.gradle.kts b/convention-plugins/src/main/kotlin/module.publication.gradle.kts deleted file mode 100644 index 24a27f7..0000000 --- a/convention-plugins/src/main/kotlin/module.publication.gradle.kts +++ /dev/null @@ -1,65 +0,0 @@ -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.bundling.Jar -import org.gradle.kotlin.dsl.`maven-publish` - -plugins { - `maven-publish` - signing -} - -publishing { - // Configure all publications - publications.withType { - // Stub javadoc.jar artifact - artifact(tasks.register("${name}JavadocJar", Jar::class) { - archiveClassifier.set("javadoc") - archiveAppendix.set(this@withType.name) - }) - - // Provide artifacts information required by Maven Central - pom { - name.set("Calf - Compose Adaptive Look & Feel") - description.set("Calf is a library that allows you to easily create adaptive UIs for your Compose Multiplatform apps.") - url.set("https://github.com/MohamedRejeb/Calf") - - licenses { - license { - name.set("Apache-2.0") - url.set("https://opensource.org/licenses/Apache-2.0") - } - } - developers { - developer { - id.set("MohamedRejeb") - name.set("Mohamed Rejeb") - email.set("mohamedrejeb445@gmail.com") - } - } - issueManagement { - system.set("Github") - url.set("https://github.com/MohamedRejeb/Calf/issues") - } - scm { - connection.set("https://github.com/MohamedRejeb/Calf.git") - url.set("https://github.com/MohamedRejeb/Calf") - } - } - } -} - -signing { -// if (project.hasProperty("signing.gnupg.keyName")) { -// useGpgCmd() -// } - useInMemoryPgpKeys( - System.getenv("OSSRH_GPG_SECRET_KEY_ID"), - System.getenv("OSSRH_GPG_SECRET_KEY"), - System.getenv("OSSRH_GPG_SECRET_KEY_PASSWORD"), - ) - sign(publishing.publications) -} - -// TODO: remove after https://youtrack.jetbrains.com/issue/KT-46466 is fixed -project.tasks.withType(AbstractPublishToMaven::class.java).configureEach { - dependsOn(project.tasks.withType(Sign::class.java)) -} \ No newline at end of file diff --git a/convention-plugins/src/main/kotlin/root.publication.gradle.kts b/convention-plugins/src/main/kotlin/root.publication.gradle.kts deleted file mode 100644 index ad65ab2..0000000 --- a/convention-plugins/src/main/kotlin/root.publication.gradle.kts +++ /dev/null @@ -1,22 +0,0 @@ -plugins { - id("io.github.gradle-nexus.publish-plugin") -} - -allprojects { - group = "com.mohamedrejeb.calf" - version = "0.3.1" -} - -nexusPublishing { - // Configure maven central repository - // https://github.com/gradle-nexus/publish-plugin#publishing-to-maven-central-via-sonatype-ossrh - repositories { - sonatype { - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - stagingProfileId.set(System.getenv("OSSRH_STAGING_PROFILE_ID")) - username.set(System.getenv("OSSRH_USERNAME")) - password.set(System.getenv("OSSRH_PASSWORD")) - } - } -} diff --git a/gradle.properties b/gradle.properties index 5dd79c4..00c5d60 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,12 +14,15 @@ kotlin.mpp.enableCInteropCommonization=true android.useAndroidX=true android.nonTransitiveRClass=true -#Web +#iOS +org.jetbrains.compose.experimental.uikit.enabled=true + +#Js kotlin.js.compiler=ir org.jetbrains.compose.experimental.jscanvas.enabled=true -#iOS -org.jetbrains.compose.experimental.uikit.enabled=true +#Wasm +org.jetbrains.compose.experimental.wasm.enabled=true #macOS org.jetbrains.compose.experimental.macos.enabled=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9886250..d9255c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,6 +6,8 @@ kotlinx-serialization-json = "1.6.2" nexus-publish = "2.0.0-rc-1" documentfile = "1.0.1" +conventionPlugin = "0.1.0" + # For sample compose-compiler = "1.5.4" activity-compose = "1.8.2" @@ -18,12 +20,17 @@ android-lifecycle = "2.2.0" play-services-location = "21.1.0" [libraries] +gradlePlugin-android = { module = "com.android.tools.build:gradle", version.ref = "agp" } +gradlePlugin-jetbrainsCompose = { module = "org.jetbrains.compose:compose-gradle-plugin", version.ref = "compose" } +gradlePlugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } + kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "android-lifecycle" } play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "play-services-location" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" } 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-javafx = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-javafx", version.ref = "kotlinx-coroutines" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } nexus-publish = { module = "io.github.gradle-nexus.publish-plugin:io.github.gradle-nexus.publish-plugin.gradle.plugin", version.ref = "nexus-publish" } @@ -43,3 +50,5 @@ kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlinJvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose" } + +conventionPlugin = { id = "com.mohamedrejeb.gradle.setup", version.ref = "conventionPlugin" } \ No newline at end of file diff --git a/sample/common/build.gradle.kts b/sample/common/build.gradle.kts index 2066068..62ef624 100644 --- a/sample/common/build.gradle.kts +++ b/sample/common/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { alias(libs.plugins.kotlinMultiplatform) alias(libs.plugins.composeMultiplatform) @@ -16,7 +18,11 @@ kotlin { jvm("desktop") { jvmToolchain(11) } - js(IR) { + js { + browser() + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { browser() } diff --git a/sample/desktop/build.gradle.kts b/sample/desktop/build.gradle.kts index b7fe420..7c804b9 100644 --- a/sample/desktop/build.gradle.kts +++ b/sample/desktop/build.gradle.kts @@ -1,4 +1,3 @@ -import org.gradle.internal.os.OperatingSystem import org.jetbrains.compose.desktop.application.dsl.TargetFormat plugins { @@ -11,14 +10,10 @@ kotlin { jvmToolchain(11) withJava() } - sourceSets { - val jvmMain by getting { - dependencies { - implementation(project(":sample:common")) - implementation(compose.desktop.currentOs) - } - } - val jvmTest by getting + + sourceSets.jvmMain.dependencies { + implementation(projects.sample.common) + implementation(compose.desktop.currentOs) } } @@ -27,8 +22,8 @@ compose.desktop { mainClass = "MainKt" nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "compose-richeditor" + packageName = "Calf" packageVersion = "1.0.0" } } -} \ No newline at end of file +} diff --git a/sample/web/build.gradle.kts b/sample/web-js/build.gradle.kts similarity index 78% rename from sample/web/build.gradle.kts rename to sample/web-js/build.gradle.kts index fb119f3..5aa6380 100644 --- a/sample/web/build.gradle.kts +++ b/sample/web-js/build.gradle.kts @@ -4,7 +4,7 @@ plugins { } kotlin { - js(IR) { + js { browser() binaries.executable() } @@ -12,8 +12,9 @@ kotlin { sourceSets.jsMain.dependencies { implementation(projects.sample.common) + implementation(compose.runtime) + implementation(compose.foundation) implementation(compose.ui) - implementation(compose.html.core) } } diff --git a/sample/web/src/jsMain/kotlin/Main.kt b/sample/web-js/src/jsMain/kotlin/Main.kt similarity index 100% rename from sample/web/src/jsMain/kotlin/Main.kt rename to sample/web-js/src/jsMain/kotlin/Main.kt diff --git a/sample/web/src/jsMain/resources/index.html b/sample/web-js/src/jsMain/resources/index.html similarity index 100% rename from sample/web/src/jsMain/resources/index.html rename to sample/web-js/src/jsMain/resources/index.html diff --git a/sample/web/webpack.config.d/fs.js b/sample/web-js/webpack.config.d/fs.js similarity index 100% rename from sample/web/webpack.config.d/fs.js rename to sample/web-js/webpack.config.d/fs.js diff --git a/sample/web-wasm/build.gradle.kts b/sample/web-wasm/build.gradle.kts new file mode 100644 index 0000000..6e136ce --- /dev/null +++ b/sample/web-wasm/build.gradle.kts @@ -0,0 +1,31 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.composeMultiplatform) +} + +kotlin { + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "web" + browser { + commonWebpackConfig { + outputFileName = "web.js" + } + } + binaries.executable() + } + + sourceSets.commonMain.dependencies { + implementation(projects.sample.common) + + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.ui) + } +} + +compose.experimental { + web.application {} +} diff --git a/sample/web-wasm/src/wasmJsMain/kotlin/Main.kt b/sample/web-wasm/src/wasmJsMain/kotlin/Main.kt new file mode 100644 index 0000000..4de0c60 --- /dev/null +++ b/sample/web-wasm/src/wasmJsMain/kotlin/Main.kt @@ -0,0 +1,20 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.CanvasBasedWindow +import com.mohamedrejeb.calf.sample.App + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + CanvasBasedWindow( + title = "Calf", + canvasElementId = "ComposeTarget", + ) { + Box( + modifier = Modifier.fillMaxSize(), + ) { + App() + } + } +} diff --git a/sample/web-wasm/src/wasmJsMain/resources/index.html b/sample/web-wasm/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000..56add1a --- /dev/null +++ b/sample/web-wasm/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + Calf + + + + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index c15c016..dd5b3dd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -38,6 +38,7 @@ include( ":sample:android", ":sample:desktop", ":sample:web", + ":sample:web-wasm", ":sample:common", )