Skip to content

Commit

Permalink
Merge pull request #142 from mbakgun/feature/desktop-app
Browse files Browse the repository at this point in the history
  • Loading branch information
mbakgun authored Dec 4, 2023
2 parents 9804f93 + fa94e77 commit f9a20bf
Show file tree
Hide file tree
Showing 15 changed files with 246 additions and 24 deletions.
17 changes: 17 additions & 0 deletions README-de.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ Nach dem Build des Projekts kann die Android Automotive Anwendung auf jedem Emul

<img src="image-assets/automotive.gif" alt="android-compose"/>

## Desktop-Anwendung

Dieses Projekt kann für Windows, Debian und MacOS erstellt werden.

```bash
./gradlew desktopApp:run
```

### Erstellen einer nativen Desktop-Distribution

```
./gradlew :desktop:packageDistributionForCurrentOS
# Ausgaben werden in desktopApp/build/compose/binaries geschrieben
```

<img src="image-assets/desktop.gif" alt="desktop-compose"/>

## Tests

Die Anwendung verfügt über Compose UI-Tests, Maestro UI-Tests und Unit-Tests. Die Unit-Tests sind unter dem Common-Paket mit Fake-Daten geschrieben. Die UI-Tests sind unter dem androidTest-Paket geschrieben. Die Maestro-Tests sind unter dem [Maestro-Paket](https://github.com/mbakgun/midjourney-images-compose-multiplatform/tree/master/.maestro) geschrieben.
Expand Down
17 changes: 17 additions & 0 deletions README-tr.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,23 @@ Proje build edildikten sonra Android Automotive uygulaması herhangi bir emulato

<img src="image-assets/automotive.gif" alt="android-compose"/>

## Desktop Application

Bu proje Windows, Debian ve MacOS için oluşturulabilir.

```bash
./gradlew desktopApp:run
```

### Dağıtım oluşturma

```
./gradlew :desktop:packageDistributionForCurrentOS
# çıktılar desktopApp/build/compose/binaries dizinine yazılır
```

<img src="image-assets/desktop.gif" alt="desktop-compose"/>

## Test

Uygulama compose ui test,maestro ui test ve unit testlere sahiptir. Unit testler common paket altında, fake data ile yazılmıştır. UI testler ise
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,23 @@ After the project is built, the Android Automotive application can be run on any

<img src="image-assets/automotive.gif" alt="android-compose"/>

## Desktop Application

This project can be built for Windows, Debian, and MacOS.

```bash
./gradlew desktopApp:run
```

### Building native desktop distribution

```
./gradlew :desktop:packageDistributionForCurrentOS
# outputs are written to desktopApp/build/compose/binaries
```

<img src="image-assets/desktop.gif" alt="desktop-compose"/>

## Testing

The application has Compose UI tests, Maestro UI tests, and unit tests. The unit tests are written under the common package with fake data. The UI tests are written under the androidTest package. The Maestro tests are written under the [maestro package](https://github.com/mbakgun/midjourney-images-compose-multiplatform/tree/master/.maestro).
Expand Down
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
plugins {
kotlin("jvm").apply(false)
kotlin("multiplatform").apply(false)
id("com.android.library").apply(false)
id("org.jetbrains.compose").apply(false)
Expand Down
39 changes: 39 additions & 0 deletions desktopApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
kotlin("multiplatform")
id("org.jetbrains.compose")
}

kotlin {
jvm {
jvmToolchain(11)
withJava()
}
sourceSets {
val jvmMain by getting {
dependencies {
implementation(project(":shared"))
implementation(compose.desktop.currentOs)
}
}
}
}

compose.desktop {
application {
mainClass = "com.mbakgun.mj.MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageVersion = "1.0.0"
copyright = "Mbakgun"
description = "MjImagesDesktopApp"
packageName = "MjImagesDesktopApp"

macOS {
dockName = "MjImagesDesktopApp"
appCategory = "public.app-category.utilities"
}
}
}
}
12 changes: 12 additions & 0 deletions desktopApp/rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Ktor
-keep class io.ktor.** { *; }
-keepclassmembers class io.ktor.** { volatile <fields>; }
-keep class io.ktor.client.engine.cio.** { *; }
-keep class kotlinx.coroutines.** { *; }
-dontwarn kotlinx.atomicfu.**
-dontwarn io.netty.**
-dontwarn com.typesafe.**
-dontwarn org.slf4j.**

# Obfuscation breaks coroutines/ktor for some reason
-dontobfuscate
39 changes: 39 additions & 0 deletions desktopApp/src/jvmMain/kotlin/com/mbakgun/mj/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.mbakgun.mj

import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.key
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import di.initKoin
import domain.usecase.MjImagesFetchUseCase
import domain.usecase.MjImagesUseCase
import org.koin.java.KoinJavaComponent.inject
import ui.MjImagesApp
import ui.MjImagesViewModel

fun main() {
System.setProperty("apple.awt.application.appearance", "system")
initKoin { }
val fetchUseCase by inject<MjImagesFetchUseCase>(MjImagesFetchUseCase::class.java)
val useCase by inject<MjImagesUseCase>(MjImagesUseCase::class.java)
val viewModel = MjImagesViewModel(fetchUseCase, useCase)

application {
Window(
onCloseRequest = ::exitApplication,
title = "MjImages",
onKeyEvent = {
when (it.key) {
Key.R -> {
viewModel.refreshImages()
true
}

else -> false
}
}
) {
MjImagesApp(viewModel)
}
}
}
3 changes: 3 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,6 @@ android.useAndroidX=true
android.compileSdk=34
android.targetSdk=34
android.minSdk=24

#Desktop
compose.desktop.packaging.checkJdkVendor=false
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ktorClient = { module = "io.ktor:ktor-client-android", version.ref="ktorClient"
ktorClientContentNegotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref="ktorClient" }
ktorClientCore = { module = "io.ktor:ktor-client-core", version.ref="ktorClient" }
ktorClientIos = { module = "io.ktor:ktor-client-ios", version.ref="ktorClient" }
ktorClientJvm = { module = "io.ktor:ktor-client-jvm", version.ref="ktorClient" }
ktorClientJson = { module = "io.ktor:ktor-client-json", version.ref="ktorClient" }
ktorClientLogging = { module = "io.ktor:ktor-client-logging", version.ref="ktorClient" }
ktorSerializationKotlinxJson = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref="ktorClient" }
Expand Down
Binary file added image-assets/desktop.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
rootProject.name = "MidJourneyImagesComposeMultiplatform"

include(":androidApp")
include(":desktopApp")
include(":shared")
include(":wearApp")
include(":televisionApp")
Expand All @@ -18,6 +19,7 @@ pluginManagement {
val agpVersion = "8.2.0"
val composeVersion = "1.5.11"

kotlin("jvm").version(kotlinVersion)
kotlin("multiplatform").version(kotlinVersion)
id("com.android.library").version(agpVersion)
id("org.jetbrains.compose").version(composeVersion)
Expand Down
47 changes: 26 additions & 21 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ plugins {

kotlin {
androidTarget()
jvm()

iosX64()
iosArm64()
Expand All @@ -28,33 +29,33 @@ kotlin {

sourceSets {
commonMain.dependencies {
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.materialIconsExtended)
implementation(compose.runtime)
implementation(compose.foundation)
implementation(compose.material)
implementation(compose.materialIconsExtended)

//sharedVm
api(libs.kmmViewmodelCore)
//sharedVm
api(libs.kmmViewmodelCore)

//di
api(libs.koinCore)
//di
api(libs.koinCore)

//network
implementation(libs.ktorClientCore)
implementation(libs.ktorClientJson)
implementation(libs.ktorClientLogging)
implementation(libs.ktorClientContentNegotiation)
implementation(libs.ktorSerializationKotlinxJson)
implementation(libs.kotlinxSerializationCore)
//network
implementation(libs.ktorClientCore)
implementation(libs.ktorClientJson)
implementation(libs.ktorClientLogging)
implementation(libs.ktorClientContentNegotiation)
implementation(libs.ktorSerializationKotlinxJson)
implementation(libs.kotlinxSerializationCore)

//imageloading
implementation(libs.imageLoader)
//imageloading
implementation(libs.imageLoader)

//coroutines
implementation(libs.kotlinxCoroutinesCore)
//coroutines
implementation(libs.kotlinxCoroutinesCore)

// local
implementation(libs.multiplatformSettings)
// local
implementation(libs.multiplatformSettings)
}

androidMain.dependencies {
Expand All @@ -66,6 +67,10 @@ kotlin {
implementation(libs.ktorClientIos)
}

jvmMain.dependencies {
implementation(libs.ktorClientJvm)
}

commonTest.dependencies {
implementation(kotlin("test-common"))
implementation(kotlin("test-annotations-common"))
Expand Down
5 changes: 2 additions & 3 deletions shared/detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@
<ID>MagicNumber:MjImagesApp.kt$230f</ID>
<ID>MagicNumber:MjImagesApp.kt$24f</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val androidInstrumentedTest by getting { dependencies { implementation(libs.androidxUiTestJunit4) implementation(libs.androidxUiTestManifest) } }</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val androidMain by getting { dependencies { api(libs.koin) implementation(libs.ktorClient) } }</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val androidMain by getting { dependencies { implementation(project(":shared")) api(libs.androidxActivityCompose) api(libs.androidxAppcompat) api(libs.androidxCoreKtx) } }</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val commonTest by getting { dependencies { implementation(kotlin("test-common")) implementation(kotlin("test-annotations-common")) implementation(libs.kotlinxCoroutinesTest) implementation(libs.koinTest) } }</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) iosArm64Main.dependsOn(this) iosSimulatorArm64Main.dependsOn(this) dependencies { implementation(libs.ktorClientIos) } }</ID>
<ID>UnusedPrivateProperty:build.gradle.kts$val jvmMain by getting { dependencies { implementation(project(":shared")) implementation(compose.desktop.currentOs) } }</ID>
<ID>UseCheckOrError:ImageLoader.kt$throw IllegalStateException("Unsupported operating system")</ID>
</CurrentIssues>
</SmellBaseline>
12 changes: 12 additions & 0 deletions shared/src/jvmMain/kotlin/util/DispatcherProvider.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package util

import kotlinx.coroutines.Dispatchers

internal actual fun getDispatcherProvider(): DispatcherProvider = JvmDispatcherProvider()

private class JvmDispatcherProvider : DispatcherProvider {
override val main = Dispatchers.Main
override val io = Dispatchers.IO
override val default = Dispatchers.Default
override val unconfined = Dispatchers.Unconfined
}
58 changes: 58 additions & 0 deletions shared/src/jvmMain/kotlin/util/ImageLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package util

import com.seiko.imageloader.ImageLoader
import com.seiko.imageloader.component.setupDefaultComponents
import com.seiko.imageloader.defaultImageResultMemoryCache
import okio.Path.Companion.toOkioPath
import java.io.File

actual fun generateImageLoader(): ImageLoader {
return ImageLoader {
components {
setupDefaultComponents()
}
interceptor {
defaultImageResultMemoryCache()
memoryCacheConfig {
maxSizeBytes(32 * 1024 * 1024) // 32MB
}
diskCacheConfig {
directory(getCacheDir().toOkioPath().resolve("image_cache"))
maxSizeBytes(512L * 1024 * 1024) // 512MB
}
}
}
}

enum class OperatingSystem {
Windows, Linux, MacOS, Unknown
}

private val currentOperatingSystem: OperatingSystem
get() {
val operSys = System.getProperty("os.name").lowercase()
return if (operSys.contains("win")) {
OperatingSystem.Windows
} else if (operSys.contains("nix") || operSys.contains("nux") ||
operSys.contains("aix")
) {
OperatingSystem.Linux
} else if (operSys.contains("mac")) {
OperatingSystem.MacOS
} else {
OperatingSystem.Unknown
}
}

private fun getCacheDir(): File {
val appName = "MidJourneyImagesComposeMultiplatform"
return when (currentOperatingSystem) {
OperatingSystem.Windows -> File(System.getenv("AppData"), "$appName/cache")
OperatingSystem.Linux -> File(System.getProperty("user.home"), ".cache/$appName")
OperatingSystem.MacOS -> File(
System.getProperty("user.home"),
"Library/Caches/$appName"
)
else -> throw IllegalStateException("Unsupported operating system")
}
}

0 comments on commit f9a20bf

Please sign in to comment.