Skip to content

Commit

Permalink
Merge pull request #27 from opatry/tasks-app-desktop
Browse files Browse the repository at this point in the history
Add :tasks-app-desktop module
  • Loading branch information
opatry authored Sep 30, 2024
2 parents 81e749a + e6d62e7 commit 7706938
Show file tree
Hide file tree
Showing 6 changed files with 239 additions and 2 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,14 @@ jobs:
with:
add-job-summary-as-pr-comment: on-failure

- name: 🔨 Build
run: ./gradlew --no-daemon assemble
- name: 🔨 Build Desktop App 🖥️
run: ./gradlew --no-daemon :tasks-app-desktop:assemble

- name: ✅ Test
run: ./gradlew --no-daemon test

- name: 🗒️ Publish Test Reports
uses: mikepenz/action-junit-report@v4
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

plugins {
alias(libs.plugins.jetbrains.kotlin.multiplatform) apply false
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
alias(libs.plugins.jetbrains.kotlin.serialization) apply false
alias(libs.plugins.jetbrains.kotlin.compose.compiler) apply false
alias(libs.plugins.jetbrains.compose) apply false
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ coil = "3.0.0-alpha10"

[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }

kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version = "0.6.1" }

Expand Down Expand Up @@ -58,6 +59,7 @@ coil = ["coil-compose", "coil-network"]

[plugins]
jetbrains-kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
jetbrains-kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
jetbrains-kotlin-compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" }
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ include(":google:tasks")
include(":lucide-icons")
include(":tasks-core")
include(":tasks-app-shared")
include(":tasks-app-desktop")
81 changes: 81 additions & 0 deletions tasks-app-desktop/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import org.jetbrains.compose.desktop.application.dsl.TargetFormat

plugins {
alias(libs.plugins.jetbrains.kotlin.jvm)
alias(libs.plugins.jetbrains.kotlin.compose.compiler)
alias(libs.plugins.jetbrains.compose)
}

kotlin {
jvmToolchain(17)

dependencies {
implementation(libs.kotlinx.coroutines.swing) {
because("requires Dispatchers.Main & co at runtime for Jvm")
// java.lang.IllegalStateException: Module with the Main dispatcher is missing. Add dependency providing the Main dispatcher, e.g. 'kotlinx-coroutines-android' and ensure it has the same version as 'kotlinx-coroutines-core'
// see also https://github.com/JetBrains/compose-jb/releases/tag/v1.1.1
}

implementation(libs.kotlinx.datetime)

implementation(project.dependencies.platform(libs.koin.bom))
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation(libs.koin.compose.viewmodel)

implementation(compose.material3)
implementation(compose.desktop.currentOs)

implementation(project(":google:oauth"))
implementation(project(":google:tasks"))
implementation(project(":tasks-app-shared"))
}
}

compose.desktop {
application {
mainClass = "MainAppKt"

nativeDistributions {
packageVersion = "1.0.0"
packageName = "Tasks app"
version = "1.0.0"
targetFormats(
TargetFormat.Dmg,
)

modules(
// for org.apache.logging.log4j.core.LoggerContext
"java.management",
// for DriverManager (required by SQLite JDBC driver)
"java.sql",
)

macOS {
bundleID = "net.opatry.tasks.app"
}
}
}
}
143 changes: 143 additions & 0 deletions tasks-app-desktop/src/main/kotlin/mainApp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2024 Olivier Patry
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Surface
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.WindowPlacement
import androidx.compose.ui.window.WindowPosition
import androidx.compose.ui.window.WindowState
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import net.opatry.tasks.app.di.authModule
import net.opatry.tasks.app.di.dataModule
import net.opatry.tasks.app.di.networkModule
import net.opatry.tasks.app.di.platformModule
import net.opatry.tasks.app.di.tasksAppModule
import net.opatry.tasks.app.ui.TaskListsViewModel
import net.opatry.tasks.app.ui.TasksApp
import net.opatry.tasks.app.ui.UserState
import net.opatry.tasks.app.ui.UserViewModel
import net.opatry.tasks.app.ui.screen.AuthorizationScreen
import net.opatry.tasks.app.ui.theme.TasksAppTheme
import org.koin.compose.KoinApplication
import org.koin.compose.viewmodel.koinViewModel
import java.awt.Dimension
import java.awt.Toolkit
import javax.swing.UIManager

private const val GCP_CLIENT_ID = "191682949161-esokhlfh7uugqptqnu3su9vgqmvltv95.apps.googleusercontent.com"

fun main() {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())

application {
val screenSize by remember {
mutableStateOf(Toolkit.getDefaultToolkit().screenSize)
}

val defaultSize = DpSize(1024.dp, 800.dp)
val minSize = Dimension(600, 400)

var windowState = rememberWindowState(
position = WindowPosition(Alignment.Center), width = defaultSize.width, height = defaultSize.height
)

Window(
onCloseRequest = ::exitApplication,
state = windowState,
title = "Tasks App",
) {
if (window.minimumSize != minSize) window.minimumSize = minSize

if (windowState.placement == WindowPlacement.Floating) {
val insets = window.insets
val maxWidth = screenSize.width - insets.left - insets.right
val finalWidth = if (window.size.width > maxWidth) maxWidth else window.size.width
val maxHeight = screenSize.height - insets.bottom - insets.top
val finalHeight = if (window.size.height > maxHeight) maxHeight else window.size.height

if (finalWidth != window.size.width || finalHeight != window.size.height) {
windowState = WindowState(
placement = windowState.placement,
position = windowState.position,
isMinimized = windowState.isMinimized,
width = finalWidth.dp,
height = finalHeight.dp
)
}
}

KoinApplication(application = {
modules(
platformModule(),
dataModule,
authModule(GCP_CLIENT_ID),
networkModule,
tasksAppModule,
)
}) {
val userViewModel = koinViewModel<UserViewModel>()
val userState by userViewModel.state.collectAsState(null)

if (userState == null) {
LaunchedEffect(userState) {
userViewModel.refreshUserState()
}
}

TasksAppTheme {
Surface {
when (userState) {
null -> Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
CircularProgressIndicator(Modifier.size(24.dp), strokeWidth = 1.dp)
}

is UserState.Unsigned,
is UserState.SignedIn -> {
val tasksViewModel = koinViewModel<TaskListsViewModel>()
TasksApp(userViewModel, tasksViewModel)
}

is UserState.Newcomer -> AuthorizationScreen(
onSkip = userViewModel::skipSignIn,
onSuccess = userViewModel::signIn,
)
}
}
}
}
}
}
}

0 comments on commit 7706938

Please sign in to comment.