diff --git a/gradle.properties b/gradle.properties index 5d17721..755b00d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,3 +22,7 @@ android.enableJetifier=false kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.mpp.androidGradlePluginCompatibility.nowarn=true org.jetbrains.compose.experimental.uikit.enabled=true + +#Compose for Web is Experimental +org.jetbrains.compose.experimental.jscanvas.enabled=true +org.jetbrains.compose.experimental.wasm.enabled=true diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 34ec08b..772aeaf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,7 @@ agp = "8.1.4" androidx-compose-ui = "1.6.0" # https://developer.android.com/jetpack/androidx/releases/compose-ui androidx-compose-ui-material3 = "1.1.2" androidx-compose-compiler = "1.5.8" # https://developer.android.com/jetpack/androidx/releases/compose-compiler -compose-multiplatform = "1.5.12" # https://github.com/JetBrains/compose-multiplatform/releases +compose-multiplatform = "1.6.0" # https://github.com/JetBrains/compose-multiplatform/releases androidx-appcompat = "1.6.1" androidx-activity = "1.8.2" # https://developer.android.com/jetpack/androidx/releases/activity androidx-savedstate = "1.2.1" # https://developer.android.com/jetpack/androidx/releases/savedstate diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock new file mode 100644 index 0000000..e69de29 diff --git a/library/build.gradle.kts b/library/build.gradle.kts index d2719c8..2c76077 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.multiplatform) @@ -15,6 +17,13 @@ kotlin { iosX64() iosArm64() iosSimulatorArm64() + js(IR) { + browser() + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + } sourceSets { val commonMain by getting { diff --git a/sample/.gitignore b/sample/androidApp/.gitignore similarity index 100% rename from sample/.gitignore rename to sample/androidApp/.gitignore diff --git a/sample/build.gradle.kts b/sample/androidApp/build.gradle.kts similarity index 96% rename from sample/build.gradle.kts rename to sample/androidApp/build.gradle.kts index dd80ee4..be83674 100644 --- a/sample/build.gradle.kts +++ b/sample/androidApp/build.gradle.kts @@ -30,6 +30,7 @@ android { dependencies { implementation(projects.library) + implementation(projects.sample.shared) implementation(libs.androidx.appcompat) implementation(libs.androidx.activity) implementation(libs.compose.foundation) diff --git a/sample/src/main/AndroidManifest.xml b/sample/androidApp/src/main/AndroidManifest.xml similarity index 100% rename from sample/src/main/AndroidManifest.xml rename to sample/androidApp/src/main/AndroidManifest.xml diff --git a/sample/androidApp/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt b/sample/androidApp/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt new file mode 100644 index 0000000..5291411 --- /dev/null +++ b/sample/androidApp/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt @@ -0,0 +1,51 @@ +package me.saket.swipe.sample + +import android.os.Build +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.* +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.core.view.WindowCompat +import com.google.accompanist.systemuicontroller.rememberSystemUiController +import me.saket.swipe.sample.theme.DarkTheme +import me.saket.swipe.sample.theme.LightTheme +import me.saket.swipe.sample.theme.MainContent + +@OptIn(ExperimentalMaterial3Api::class) +class SampleActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + val uiController = rememberSystemUiController() + val systemInDarkTheme = isSystemInDarkTheme() + LaunchedEffect(Unit) { + WindowCompat.setDecorFitsSystemWindows(window, false) + uiController.setSystemBarsColor(Color.Transparent, darkIcons = !systemInDarkTheme) + uiController.setNavigationBarColor(Color.Transparent) + } + + val colors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (systemInDarkTheme) dynamicDarkColorScheme(this) else dynamicLightColorScheme(this) + } else { + if (systemInDarkTheme) DarkTheme else LightTheme + } + + MaterialTheme(colors) { + Scaffold( + topBar = { + TopAppBar(title = { Text(stringResource(R.string.app_name)) }) + } + ) { contentPadding -> + MainContent(modifier = Modifier.padding(contentPadding)) + } + } + } + } +} diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/androidApp/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from sample/src/main/res/drawable/ic_launcher_background.xml rename to sample/androidApp/src/main/res/drawable/ic_launcher_background.xml diff --git a/sample/src/main/res/drawable/ic_launcher_foreground.xml b/sample/androidApp/src/main/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from sample/src/main/res/drawable/ic_launcher_foreground.xml rename to sample/androidApp/src/main/res/drawable/ic_launcher_foreground.xml diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to sample/androidApp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/sample/src/main/res/values/strings.xml b/sample/androidApp/src/main/res/values/strings.xml similarity index 100% rename from sample/src/main/res/values/strings.xml rename to sample/androidApp/src/main/res/values/strings.xml diff --git a/sample/src/main/res/values/styles.xml b/sample/androidApp/src/main/res/values/styles.xml similarity index 100% rename from sample/src/main/res/values/styles.xml rename to sample/androidApp/src/main/res/values/styles.xml diff --git a/sample/shared/build.gradle.kts b/sample/shared/build.gradle.kts new file mode 100644 index 0000000..f7bb493 --- /dev/null +++ b/sample/shared/build.gradle.kts @@ -0,0 +1,72 @@ +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.multiplatform) +} + +kotlin { + @Suppress("OPT_IN_USAGE") + targetHierarchy.default() + + androidTarget() + jvm() + iosX64() + iosArm64() + iosSimulatorArm64() + js(IR) { + browser() + } + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + browser() + } + + sourceSets { + val commonMain by getting { + dependencies { + implementation(projects.library) + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + implementation(compose.materialIconsExtended) + @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) + implementation(compose.components.resources) + } + } + + val jsWasmMain by creating { + dependsOn(commonMain) + } + + val jsMain by getting { + dependsOn(jsWasmMain) + } + + val wasmJsMain by getting { + dependsOn(jsWasmMain) + } + } +} + +android { + namespace = "me.saket.swipe" + + defaultConfig { + minSdk = libs.versions.minSdk.get().toInt() + compileSdk = libs.versions.compileSdk.get().toInt() + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get() + } + java { + toolchain.languageVersion.set(JavaLanguageVersion.of(11)) + } + lint { + abortOnError = true + } +} diff --git a/sample/shared/src/commonMain/composeResources/values/strings.xml b/sample/shared/src/commonMain/composeResources/values/strings.xml new file mode 100644 index 0000000..ffc7d62 --- /dev/null +++ b/sample/shared/src/commonMain/composeResources/values/strings.xml @@ -0,0 +1,3 @@ + + Swipe + diff --git a/sample/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt b/sample/shared/src/commonMain/kotlin/me/saket/swipe/sample/theme/MainContent.kt similarity index 60% rename from sample/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt rename to sample/shared/src/commonMain/kotlin/me/saket/swipe/sample/theme/MainContent.kt index aad42ec..f16ca8f 100644 --- a/sample/src/main/kotlin/me/saket/swipe/sample/SampleActivity.kt +++ b/sample/shared/src/commonMain/kotlin/me/saket/swipe/sample/theme/MainContent.kt @@ -1,19 +1,8 @@ -package me.saket.swipe.sample +package me.saket.swipe.sample.theme -import android.os.Build -import android.os.Bundle -import androidx.activity.compose.setContent -import androidx.appcompat.app.AppCompatActivity import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background -import androidx.compose.foundation.isSystemInDarkTheme -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape @@ -21,16 +10,10 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Archive import androidx.compose.material.icons.twotone.ReplyAll import androidx.compose.material.icons.twotone.Snooze -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text -import androidx.compose.material3.TopAppBar -import androidx.compose.material3.dynamicDarkColorScheme -import androidx.compose.material3.dynamicLightColorScheme import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -39,50 +22,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.rememberVectorPainter -import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.view.WindowCompat -import com.google.accompanist.systemuicontroller.rememberSystemUiController import me.saket.swipe.SwipeAction import me.saket.swipe.SwipeableActionsBox -import me.saket.swipe.sample.theme.DarkTheme -import me.saket.swipe.sample.theme.LightTheme -@OptIn(ExperimentalMaterial3Api::class) -class SampleActivity : AppCompatActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - val uiController = rememberSystemUiController() - val systemInDarkTheme = isSystemInDarkTheme() - LaunchedEffect(Unit) { - WindowCompat.setDecorFitsSystemWindows(window, false) - uiController.setSystemBarsColor(Color.Transparent, darkIcons = !systemInDarkTheme) - uiController.setNavigationBarColor(Color.Transparent) - } - - val colors = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (systemInDarkTheme) dynamicDarkColorScheme(this) else dynamicLightColorScheme(this) - } else { - if (systemInDarkTheme) DarkTheme else LightTheme - } +@Composable +fun MainContent(modifier: Modifier = Modifier) { - MaterialTheme(colors) { - Scaffold( - topBar = { - TopAppBar(title = { Text(stringResource(R.string.app_name)) }) - } - ) { contentPadding -> - LazyColumn(Modifier.padding(contentPadding).fillMaxSize()) { - items(20) { index -> - SwipeableBoxPreview( - Modifier.fillMaxWidth() - ) - } - } - } - } + LazyColumn(modifier.fillMaxSize()) { + items(20) { index -> + SwipeableBoxPreview( + Modifier.fillMaxWidth() + ) } } } @@ -125,7 +77,7 @@ private fun SwipeableBoxPreview(modifier: Modifier = Modifier) { } @Composable -private fun BatmanIpsumItem( +fun BatmanIpsumItem( modifier: Modifier = Modifier, isSnoozed: Boolean ) { diff --git a/sample/src/main/kotlin/me/saket/swipe/sample/theme/Theme.kt b/sample/shared/src/commonMain/kotlin/me/saket/swipe/sample/theme/Theme.kt similarity index 100% rename from sample/src/main/kotlin/me/saket/swipe/sample/theme/Theme.kt rename to sample/shared/src/commonMain/kotlin/me/saket/swipe/sample/theme/Theme.kt diff --git a/sample/webApp/README.md b/sample/webApp/README.md new file mode 100644 index 0000000..06fba71 --- /dev/null +++ b/sample/webApp/README.md @@ -0,0 +1,7 @@ +# Web app + +To start the web app, run: + +```shell +./gradlew :sample:webApp:wasmJsBrowserDevelopmentRun +``` diff --git a/sample/webApp/build.gradle.kts b/sample/webApp/build.gradle.kts new file mode 100644 index 0000000..fc3d27a --- /dev/null +++ b/sample/webApp/build.gradle.kts @@ -0,0 +1,58 @@ +import org.jetbrains.compose.ExperimentalComposeLibrary +import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.compose.multiplatform) +} + + +kotlin { + js(IR) { + moduleName = "sample" + browser { + commonWebpackConfig { + outputFileName = "sample.js" + } + } + binaries.executable() + } + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "sample" + browser { + commonWebpackConfig { + outputFileName = "sample.js" + } + } + binaries.executable() + } + + sourceSets { + val jsWasmMain by creating { + dependencies { + implementation(projects.library) + implementation(projects.sample.shared) + implementation(compose.runtime) + implementation(compose.ui) + implementation(compose.foundation) + implementation(compose.material3) + @OptIn(ExperimentalComposeLibrary::class) + implementation(compose.components.resources) + } + } + val jsMain by getting { + dependsOn(jsWasmMain) + } + val wasmJsMain by getting { + dependsOn(jsWasmMain) + } + } +} + + +compose.experimental { + web.application {} +} diff --git a/sample/webApp/src/jsMain/kotlin/me/saket/swipe/sample/main.kt b/sample/webApp/src/jsMain/kotlin/me/saket/swipe/sample/main.kt new file mode 100644 index 0000000..271b6cb --- /dev/null +++ b/sample/webApp/src/jsMain/kotlin/me/saket/swipe/sample/main.kt @@ -0,0 +1,14 @@ +package me.saket.swipe.sample + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.CanvasBasedWindow +import org.jetbrains.skiko.wasm.onWasmReady + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + onWasmReady { + CanvasBasedWindow("Sample") { + MainContentWeb() + } + } +} diff --git a/sample/webApp/src/jsMain/resources/index.html b/sample/webApp/src/jsMain/resources/index.html new file mode 100644 index 0000000..3857eee --- /dev/null +++ b/sample/webApp/src/jsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + Compose App + + + + + + + diff --git a/sample/webApp/src/jsWasmMain/kotlin/me/saket/swipe/sample/Common.kt b/sample/webApp/src/jsWasmMain/kotlin/me/saket/swipe/sample/Common.kt new file mode 100644 index 0000000..785f0b8 --- /dev/null +++ b/sample/webApp/src/jsWasmMain/kotlin/me/saket/swipe/sample/Common.kt @@ -0,0 +1,9 @@ +package me.saket.swipe.sample + +import androidx.compose.runtime.Composable +import me.saket.swipe.sample.theme.MainContent + +@Composable +internal fun MainContentWeb() { + MainContent() +} diff --git a/sample/webApp/src/wasmJsMain/kotlin/me/saket/swipe/sample/Main.wasm.kt b/sample/webApp/src/wasmJsMain/kotlin/me/saket/swipe/sample/Main.wasm.kt new file mode 100644 index 0000000..f03b6bd --- /dev/null +++ b/sample/webApp/src/wasmJsMain/kotlin/me/saket/swipe/sample/Main.wasm.kt @@ -0,0 +1,13 @@ +package me.saket.swipe.sample + +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.window.CanvasBasedWindow +import me.saket.swipe.sample.theme.MainContent + + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + CanvasBasedWindow(canvasElementId = "ComposeTarget") { + MainContent() + } +} diff --git a/sample/webApp/src/wasmJsMain/resources/index.html b/sample/webApp/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000..3857eee --- /dev/null +++ b/sample/webApp/src/wasmJsMain/resources/index.html @@ -0,0 +1,12 @@ + + + + + Compose App + + + + + + + diff --git a/settings.gradle b/settings.gradle index cdef391..eedd38f 100644 --- a/settings.gradle +++ b/settings.gradle @@ -14,7 +14,9 @@ dependencyResolutionManagement { } include ':library' -include ':sample' +include ':sample:androidApp' +include ':sample:webApp' +include ':sample:shared' rootProject.name = "swipe" enableFeaturePreview('TYPESAFE_PROJECT_ACCESSORS')