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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ bin/
### Mac OS ###
.DS_Store

### Xcode (per-user, machine-specific — should never be committed) ###
**/xcuserdata/
*.xcuserstate
DerivedData/

### Kotlin ###
.kotlin/

Expand Down Expand Up @@ -81,6 +86,7 @@ coverage/
/oldProject/
/clients/tablet/composeApp/google-services.json
/local.properties
/localQuickStart/.local-fake-credentials.json
/deploy/dev/.env
/deploy/dev/*.jar
/deploy/prod/.env
Expand Down
40 changes: 23 additions & 17 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,30 @@ repositories {
}

dependencies {
implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:2.1.21")
implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:3.5.0")
implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:2.1.21")
implementation("org.jetbrains.kotlin.plugin.jpa:org.jetbrains.kotlin.plugin.jpa.gradle.plugin:2.1.21")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21")
implementation("org.jetbrains.kotlin:kotlin-allopen:2.1.21")
implementation("org.jetbrains.kotlin:kotlin-noarg:2.1.21")
implementation("org.springframework.boot:spring-boot-gradle-plugin:3.5.0")
implementation("io.spring.gradle:dependency-management-plugin:1.1.7")
val kotlinVersion = libs.versions.kotlin.get()
val springBootVersion = libs.versions.springBoot.get()
val springDepManagementVersion = libs.versions.springDependencyManagement.get()
val agpVersion = libs.versions.agp.get()
val composeGradlePluginVersion = libs.versions.compose.get()
val googleServicesVersion = libs.versions.googleServices.get()

implementation("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:2.1.21")
implementation("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.1.21")
implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:2.1.21")
implementation("com.android.tools.build:gradle:8.9.1") {
implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:$kotlinVersion")
implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:$springBootVersion")
implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin.plugin.jpa:org.jetbrains.kotlin.plugin.jpa.gradle.plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion")
implementation("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion")
implementation("io.spring.gradle:dependency-management-plugin:$springDepManagementVersion")

implementation("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:$kotlinVersion")
implementation("com.android.tools.build:gradle:$agpVersion") {
exclude(group = "org.apache.commons", module = "commons-compress")
}
implementation("org.jetbrains.compose:org.jetbrains.compose.gradle.plugin:1.8.1")
implementation("com.google.gms:google-services:4.4.3")
implementation("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:2.1.21")

implementation("org.jetbrains.compose:org.jetbrains.compose.gradle.plugin:$composeGradlePluginVersion")
implementation("com.google.gms:google-services:$googleServicesVersion")
implementation("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:$kotlinVersion")
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ java {
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_17.toString()
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ kotlin {
implementation(project(":clients:tablet:core:domain"))
implementation(project(":clients:tablet:core:data"))

implementation(libs.findLibrary("decompose").get())
implementation(libs.findLibrary("decompose.compose.jetbrains").get())
implementation(libs.findLibrary("essenty.lifecycle").get())
implementation(libs.findLibrary("essenty.state.keeper").get())
implementation(libs.findLibrary("jetbrains-lifecycle-viewmodel").get())

implementation(libs.findLibrary("kotlin.coroutines.core").get())

Expand All @@ -26,8 +23,5 @@ kotlin {
androidMain.dependencies {
implementation(libs.findLibrary("koin-android").get())
}
iosMain.dependencies {
implementation(libs.findLibrary("essenty.darwin.runtime").get())
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ kotlin {
implementation(compose.components.uiToolingPreview)
implementation(libs.findLibrary("coil").get())
implementation(libs.findLibrary("coil.network.ktor").get())
api("com.mohamedrejeb.calf:calf-ui:0.8.0")
}

androidMain.dependencies {
Expand Down
14 changes: 11 additions & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ subprojects {
}

tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = listOf("-Xjsr305=strict")
jvmTarget = JavaVersion.VERSION_17.toString()
compilerOptions {
freeCompilerArgs.add("-Xjsr305=strict")
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

// kotlinx-datetime 0.7.x is built on the still-experimental kotlin.time.Clock/Instant,
// so opt in project-wide for every Kotlin compilation (JVM, Android and Native).
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>().configureEach {
compilerOptions {
optIn.add("kotlin.time.ExperimentalTime")
}
}

Expand Down
4 changes: 2 additions & 2 deletions clients/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ The TV application is a Compose Multiplatform app designed for large-screen devi
- **Framework**: Kotlin Multiplatform with Jetpack Compose
- **Platforms**: Android and iOS
- **UI**: Jetpack Compose
- **Navigation**: Decompose
- **Navigation**: Compose Navigation (NavHost/NavController with type-safe routes)
- **Dependency Injection**: Koin
- **State Management**: Decompose with MVI pattern
- **State Management**: Koin ViewModels + MVI pattern (StateFlow)
- **API Communication**: Ktor Client
- **Date/Time Handling**: Kotlinx.datetime
- **Logging**: Napier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package band.effective.office.shared.core.utils

import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlinx.datetime.Clock
import kotlin.time.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,10 @@ import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.toNSDateComponents
import platform.Foundation.NSCalendar
import platform.Foundation.NSDateFormatter
import platform.Foundation.NSLocale

actual fun LocalDateTime.toLocalisedString(pattern: String): String {
val dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = pattern
dateFormatter.locale = NSLocale.currentLocale

val calendar = NSCalendar.currentCalendar
val dateComponents = toNSDateComponents()
Expand Down
9 changes: 6 additions & 3 deletions clients/smsrouter/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,18 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
compose = true
buildConfig = true
}
}

kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
}

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
Expand Down
4 changes: 2 additions & 2 deletions clients/tablet/composeApp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ composeApp/
```

## Key Components
- **App**: Main application composable that sets up the navigation and theme
- **Navigation**: Handles routing between different screens and features
- **AppRoot**: Main application composable that provides the root `ViewModelStoreOwner`, theme, and `AppNavHost`
- **AppNavHost**: Compose Navigation graph (`NavHost`/`NavController`) with type-safe routes; full-screen `composable<>` destinations (Settings/Main) and modal `dialog<>` destinations (FreeRoom/BookingEditor/FastBooking)
- **DI**: Dependency injection setup for the application
- **Theme**: Application-wide styling and theming

Expand Down
31 changes: 10 additions & 21 deletions clients/tablet/composeApp/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties

plugins {
id("org.jetbrains.kotlin.multiplatform")
id("org.jetbrains.kotlin.plugin.compose") version "2.1.21"
id("org.jetbrains.compose")
id("com.android.application")
id("org.jetbrains.kotlin.plugin.serialization") version "2.1.21"
alias(libs.plugins.multiplatform)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.compose)
alias(libs.plugins.android.application)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.buildkonfig)
id("com.google.gms.google-services") version "4.4.3"
alias(libs.plugins.google.services)
id("sign-config")
}

Expand All @@ -22,17 +22,7 @@ kotlin {
it.binaries.framework {
baseName = "ComposeApp"
isStatic = true

export(libs.decompose)
export(libs.decompose.compose.jetbrains)
export(libs.essenty.lifecycle)

export("com.mohamedrejeb.calf:calf-ui:0.8.0")
}
it.compilations.all {
kotlinOptions.freeCompilerArgs += "-Xdisable-decompose-parcelize"
}

}

sourceSets {
Expand All @@ -43,19 +33,18 @@ kotlin {
implementation(compose.components.resources)
implementation(compose.components.uiToolingPreview)

api(libs.decompose)
api(libs.decompose.compose.jetbrains)
api("com.mohamedrejeb.calf:calf-ui:0.8.0")
api(libs.kotlinx.datetime)

implementation(libs.jetbrains.navigation.compose)
implementation(libs.jetbrains.lifecycle.viewmodel)
implementation(libs.kotlinx.serialization.json)

implementation(libs.kotlin.coroutines.core)

implementation(libs.bundles.koin)

implementation(libs.napier)

implementation(libs.settings)

implementation(project(":clients:tablet:core:ui"))
implementation(project(":clients:tablet:feature:main"))
implementation(project(":clients:tablet:feature:settings"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@ import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.lifecycle.lifecycleScope
import band.effective.office.tablet.root.RootComponent
import band.effective.office.tablet.time.TimeReceiver
import band.effective.office.tablet.utils.KioskCommandBus
import band.effective.office.tablet.utils.KioskLifecycleObserver
import band.effective.office.tablet.utils.KioskManager
import com.arkivanov.decompose.defaultComponentContext

val LocalKioskManager = staticCompositionLocalOf<KioskManager?> { null }

Expand All @@ -30,11 +28,9 @@ class AppActivity : ComponentActivity() {

lifecycle.addObserver(KioskLifecycleObserver(this, kioskManager, kioskCommandBus, lifecycleScope))

val root = RootComponent(componentContext = defaultComponentContext())

setContent {
CompositionLocalProvider(LocalKioskManager provides kioskManager) {
App(root)
AppRoot()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package band.effective.office.tablet

import android.app.Application
import band.effective.office.tablet.core.domain.manager.DateResetManager
import band.effective.office.tablet.core.domain.model.SettingsManager
import band.effective.office.tablet.core.ui.inactivity.InactivityLifecycleCallbacks
import band.effective.office.tablet.di.KoinInitializer
import band.effective.office.tablet.di.firebaseTopicsModule
import com.google.firebase.messaging.FirebaseMessaging
import com.russhwolf.settings.SharedPreferencesSettings
import org.koin.android.ext.android.get
import org.koin.android.ext.koin.androidContext
import org.koin.core.qualifier.named
import kotlin.time.Duration.Companion.minutes

Expand All @@ -16,15 +16,10 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
LoggerInitializer().init()
KoinInitializer().init()
SettingsManager.init(
SharedPreferencesSettings(
this.getSharedPreferences(
"settings",
MODE_PRIVATE
)
)
)
KoinInitializer().init {
androidContext(applicationContext)
modules(firebaseTopicsModule)
}
subscribeOnFirebaseTopics()
initializeInactivitySystem()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import android.content.IntentFilter
import android.util.Log
import band.effective.office.tablet.feature.main.domain.CurrentTimeHolder
import kotlinx.coroutines.flow.StateFlow
import kotlinx.datetime.Clock
import kotlin.time.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
package band.effective.office.tablet

import androidx.compose.runtime.Composable
import band.effective.office.tablet.core.ui.theme.AppTheme
import band.effective.office.tablet.root.Root
import band.effective.office.tablet.root.RootComponent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import band.effective.office.tablet.components.VersionOverlay
import band.effective.office.tablet.core.domain.useCase.CheckSettingsUseCase
import band.effective.office.tablet.core.domain.useCase.ResourceDisposerUseCase
import band.effective.office.tablet.core.ui.theme.AppTheme
import band.effective.office.tablet.navigation.AppNavHost
import org.koin.compose.koinInject

@Composable
fun App(rootComponent: RootComponent) {
AppTheme {
Box(modifier = Modifier.fillMaxSize()) {
Root(rootComponent)
VersionOverlay()
fun AppRoot() {
val rootViewModelStoreOwner = rememberRootViewModelStoreOwner()

CompositionLocalProvider(LocalViewModelStoreOwner provides rootViewModelStoreOwner) {
AppTheme {
Box(modifier = Modifier.fillMaxSize()) {
val resourceDisposerUseCase = koinInject<ResourceDisposerUseCase>()
val checkSettingsUseCase = koinInject<CheckSettingsUseCase>()

LaunchedEffect(Unit) { resourceDisposerUseCase() }

val startRoomConfigured = remember { checkSettingsUseCase().isNotEmpty() }
Box(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
.systemBarsPadding()
) {
AppNavHost(startRoomConfigured = startRoomConfigured)
}
VersionOverlay()
}
}
}
}

@Composable
private fun rememberRootViewModelStoreOwner(): ViewModelStoreOwner = remember {
object : ViewModelStoreOwner {
override val viewModelStore: ViewModelStore = ViewModelStore()
}
}
Loading