Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 54 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,66 @@ buildAndroid: cleanBuild
$(GRADLEW) androidApp:app:installDebug
@echo "✅ Done!"

# Run IOS build
buildIOS: cleanBuild
@echo "IOS build"
chmod +x ./scripts/ios_script.sh
./scripts/ios_script.sh
@echo "✅ Done!"

# Run Android Ui test
testUiAndroid: cleanBuild
@echo "Android UI test"
$(GRADLEW) androidApp:app:connectedDebugAndroidTest
@echo "✅ Done!"

# Run Desktop build
buildDesktop: cleanBuild
@echo "Desktop build"
$(GRADLEW) :desktopapp:run
@echo "✅ Done!"

# Run web build
buildWeb: cleanBuild
@echo "Web build"
$(GRADLEW) webApp:js:wasmJsBrowserDevelopmentRun
# Run Desktop Ui test
testUiDesktop: cleanBuild
@echo "Desktop UI test"
$(GRADLEW) :desktopapp:jvmTest
@echo "✅ Done!"

# Run Desktop hot reload build
buildHotDesktop: clear
@echo "Desktop Hot reload build"
$(GRADLEW) :desktopapp:hotRunJvm --auto
@echo "✅ Done!"

# Run wasm build
buildWasmWeb: cleanBuild
@echo "Web Wasm build"
$(GRADLEW) webApp:wasm:wasmJsBrowserDevelopmentRun
@echo "✅ Done!"

# Run wasm UI test
testUiWasmWeb: cleanBuild
@echo "Web wasm UI test"
$(GRADLEW) webApp:wasm:wasmJsBrowserTest
@echo "✅ Done!"

# Run js build
buildJsWeb: cleanBuild
@echo "Web JS build"
$(GRADLEW) webApp:js:jsBrowserDevelopmentRun
@echo "✅ Done!"

# Run JS UI test
testUiJsWeb: cleanBuild
@echo "Web Js UI test"
$(GRADLEW) webApp:wasm:jsBrowserTest
@echo "✅ Done!"


# Run wasm UI test
testUiJsWeb: cleanBuild
@echo "Web Js UI test"
$(GRADLEW) iosApp:iosSimulatorArm64Test
@echo "✅ Done!"

# Run All test
Expand Down
20 changes: 1 addition & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,4 @@
This is a Kotlin Multiplatform project targeting Android, iOS, Web, Desktop.


* `/composeApp` is for code that will be shared across your Compose Multiplatform applications.
It contains several subfolders:
- `commonMain` is for code that’s common for all targets.
- Other folders are for Kotlin code that will be compiled for only the platform indicated in the folder name.
For example, if you want to use Apple’s CoreCrypto for the iOS part of your Kotlin app,
`iosMain` would be the right folder for such calls.

* `/iosApp` contains iOS applications. Even if you’re sharing your UI with Compose Multiplatform,
you need this entry point for your iOS app. This is also where you should add SwiftUI code for your project.


Learn more about [Kotlin Multiplatform](https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html),
[Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/#compose-multiplatform),
[Kotlin/Wasm](https://kotl.in/wasm/)…

We would appreciate your feedback on Compose/Web and Kotlin/Wasm in the public Slack channel [#compose-web](https://slack-chats.kotlinlang.org/c/compose-web).
If you face any issues, please report them on [YouTrack](https://youtrack.jetbrains.com/newIssue?project=CMP).

You can open the web application by running the `:composeApp:wasmJsBrowserDevelopmentRun` Gradle task.
Run the command from Makefile to run the respective Platform
3 changes: 3 additions & 0 deletions androidApp/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,7 @@ composeCompiler {
dependencies {
implementation(projects.sharedCode)
implementation(libs.androidx.activity.compose)

androidTestImplementation(libs.androidx.uitest.junit4)
debugImplementation(libs.androidx.uitest.testManifest)
}
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ plugins {
alias(libs.plugins.kotlin.multiplatform) apply false
alias(libs.plugins.compose.multiplatform) apply false
alias(libs.plugins.compose.compiler) apply false
alias(libs.plugins.compose.hotReload)
alias(libs.plugins.ksp) apply false
}
18 changes: 18 additions & 0 deletions desktopApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.compose.reload.gradle.ComposeHotRun

plugins {
alias(libs.plugins.compose.compiler)
alias(libs.plugins.compose.multiplatform)
alias(libs.plugins.kotlin.multiplatform)
alias(libs.plugins.compose.hotReload)
}

kotlin {
Expand All @@ -22,6 +24,10 @@ kotlin {
}
}

tasks.withType<ComposeHotRun>().configureEach {
mainClass = "dev.reprator.github.MainKt"
}

compose.desktop {
application {
mainClass = "dev.reprator.github.MainKt"
Expand All @@ -31,7 +37,19 @@ compose.desktop {
packageName = "dev.reprator.github"
packageVersion = "1.0.0"
includeAllModules = true

linux {
iconFile.set(project.file("resources/LinuxIcon.png"))
}
windows {
iconFile.set(project.file("resources/WindowsIcon.ico"))
}
macOS {
iconFile.set(project.file("resources/MacosIcon.icns"))
bundleID = "org.company.app.desktopApp"
}
}

buildTypes.release.proguard {
configurationFiles.from("compose-desktop.pro")
}
Expand Down
Binary file added desktopApp/src/jvmMain/resources/LinuxIcon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added desktopApp/src/jvmMain/resources/MacosIcon.icns
Binary file not shown.
Binary file added desktopApp/src/jvmMain/resources/WindowsIcon.ico
Binary file not shown.
21 changes: 14 additions & 7 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#Kotlin
kotlin.code.style=official
kotlin.daemon.jvmargs=-Xmx3072M

#Gradle
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8
org.gradle.configuration-cache=true
org.gradle.jvmargs=-Xmx4G
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.binary.gc=cms
kotlin.incremental.wasm=true

#Android
android.useAndroidX=true
android.nonTransitiveRClass=true
android.useAndroidX=true

#Compose
org.jetbrains.compose.experimental.jscanvas.enabled=true
9 changes: 7 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ androidx-constraintlayout = "2.2.1"
androidx-core = "1.16.0"
androidx-espresso = "3.6.1"
androidx-lifecycle = "2.9.1"
androidx-uiTest = "1.8.3"
androidx-navigation = "2.9.0-beta03"
androidx-testExt = "1.2.1"
composeHotReload = "1.0.0-beta04"
Expand All @@ -17,7 +18,7 @@ junit = "4.13.2"
kotlin = "2.2.0"
kotlinx-coroutines = "1.10.2"
ktor = "3.2.2"
coil = "3.2.0"
coil = "3.3.0"
kotlininject = "0.8.0"
turbine = "1.2.1"
ksp = "2.2.0-2.0.2"
Expand All @@ -29,6 +30,8 @@ spotless = "7.1.0"
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
kotlin-testJunit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
junit = { module = "junit:junit", version.ref = "junit" }
androidx-uitest-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" }
androidx-uitest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-testExt-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-testExt" }
androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-espresso" }
Expand Down Expand Up @@ -69,10 +72,12 @@ ktor-client-serialization-json = { group = "io.ktor", name = "ktor-serialization

logging-napier = { module = "io.github.aakira:napier", version = "2.7.1" }

test-turbine = { module="app.cash.turbine:turbine", version = "1.2.1" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" }
compose-hotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "composeHotReload" }
compose-multiplatform = { id = "org.jetbrains.compose", version.ref = "composeMultiplatform" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
Expand Down
30 changes: 30 additions & 0 deletions scripts/ios_script.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/bash

echo "🚀 Building Compose Multiplatform App..."

# Build the shared Compose framework
echo "📦 Building shared Compose Multiplatform framework..."
./gradlew :shared:compileKotlinIosSimulatorArm64
./gradlew :shared:linkDebugFrameworkIosSimulatorArm64

if [ $? -eq 0 ]; then
echo "✅ Compose Multiplatform framework built successfully!"

echo "📱 Starting iOS Simulator..."
xcrun simctl boot "iPhone 16 Pro" 2>/dev/null || echo "Simulator already running"
open -a Simulator

echo "🍎 Opening iOS project in Xcode..."
open iosApp/iosApp.xcodeproj

echo ""
echo "🎯 NEXT STEPS IN XCODE:"
echo "1. Wait for Xcode to open"
echo "2. Click on 'Any iOS Device' dropdown (top-left)"
echo "3. Select 'iPhone 16 Pro' from the list"
echo "4. Press Cmd+R or click ▶️ to run"
echo ""
else
echo "❌ Framework build failed!"
exit 1
fi
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ include(":sharedCode")
include(":androidApp:app")
include(":desktopApp")
include(":webapp:js")
include(":webapp:wasm")
23 changes: 21 additions & 2 deletions sharedCode/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
@file:OptIn(ExperimentalWasmDsl::class)
@file:OptIn(ExperimentalWasmDsl::class, ExperimentalComposeLibrary::class)
import org.gradle.kotlin.dsl.getByType
import org.gradle.kotlin.dsl.invoke
import org.jetbrains.compose.ExperimentalComposeLibrary
import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
Expand All @@ -13,6 +15,7 @@ plugins {
alias(libs.plugins.compose.compiler)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.ksp)
alias(libs.plugins.mokkery)
}

kotlin {
Expand Down Expand Up @@ -42,7 +45,12 @@ kotlin {
wasmJs {
browser()
}


js {
browser()
binaries.executable()
}

sourceSets {
val desktopMain by getting

Expand Down Expand Up @@ -89,6 +97,9 @@ kotlin {

commonTest.dependencies {
implementation(libs.kotlin.test)
implementation(libs.kotlinx.coroutines.test)
implementation(libs.test.turbine)
implementation(compose.uiTest)
}

desktopMain.dependencies {
Expand All @@ -100,6 +111,10 @@ kotlin {
wasmJsMain.dependencies {
implementation(libs.ktor.client.js)
}

jsMain.dependencies {
implementation(libs.ktor.client.js)
}
}
}

Expand All @@ -124,6 +139,10 @@ ksp {
arg("me.tatarka.inject.generateCompanionExtensions", "true")
}

mokkery {
ignoreFinalMembers.set(true)
}

fun Project.addKspDependencyForAllTargets(dependencyNotation: Any) = addKspDependencyForAllTargets("", dependencyNotation)
fun Project.addKspTestDependencyForAllTargets(dependencyNotation: Any) = addKspDependencyForAllTargets("Test", dependencyNotation)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ private fun UserDetailScreen(
onAction: OnAction,
modifier: Modifier = Modifier
) {
Column(modifier = modifier.fillMaxSize()) {
Column(modifier = modifier.padding(12.dp).fillMaxSize()) {
UserToolbar(
state.userInfo.userName,
state.userInfo.profilePic,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ import io.github.aakira.napier.Napier
import me.tatarka.inject.annotations.Inject

@Inject
class UserListScreenReducer :
Reducer<UserListState, UserListAction, UserListEffect> {
class UserListScreenReducer : Reducer<UserListState, UserListAction, UserListEffect> {

override fun reduce(
previousState: UserListState,
Expand Down Expand Up @@ -74,7 +73,9 @@ class UserListScreenReducer :

//Search query is empty, so fetch user list
return previousState.copy(
userLoading = true
userLoading = true,
errorMessage = "",
isError = false,
) to null
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package dev.reprator.github.features.userList.presentation

import androidx.lifecycle.Lifecycle
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.viewModelScope
import dev.reprator.github.util.AppCoroutineDispatchers
import dev.reprator.github.util.base.mvi.MVI
Expand All @@ -22,7 +20,7 @@ import kotlinx.coroutines.launch
import me.tatarka.inject.annotations.Assisted
import me.tatarka.inject.annotations.Inject

private const val SEARCH_QUERY_KEY = "searchQuery"
const val SEARCH_QUERY_KEY = "searchQuery"
private const val MINIMUM_SEARCH_DEBOUNCE = 100L
private const val MINIMUM_SEARCH_LENGTH = 3

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ private fun UserListScreen(
onSearchQueryChanged: (String) -> Unit = {},
lazyListState: LazyListState = rememberLazyListState()
) {
Column(modifier = modifier) {
Column(modifier = modifier.padding(12.dp)) {

SearchTextField(currentSearchQuery, onSearchQueryChanged, { query ->
onAction(UserListAction.SearchUsers(query, state.userList.isEmpty()))
Expand Down

This file was deleted.

Loading