Skip to content

Commit 152d25f

Browse files
Merge pull request #16 from matejsemancik/feature/update-banner
Application Update banner
2 parents 0d8c422 + 2fd94c8 commit 152d25f

29 files changed

Lines changed: 707 additions & 113 deletions

File tree

CLAUDE.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,63 @@
99
- **Naming**: Classes = PascalCase, Functions/Variables = camelCase
1010
- **Architecture**: MVVM pattern with UI, State (Model), and Data layers
1111
- **State Management**: Kotlin Flows (StateFlow for UI state)
12+
- **Visibility Modifiers**:
13+
- Use the most restrictive visibility modifier possible
14+
- Prefer `internal` over `public` for implementation classes
15+
- Use `private` for all properties and functions that don't need wider visibility
16+
- Use `public` only for APIs that need to be accessed outside the module
1217
- **DI**: Koin for dependency injection
18+
- Use constructor injection whenever possible instead of property injection
19+
- Constructor parameters make dependencies explicit and improve testability
1320
- **Imports**: Grouped by purpose, explicit imports preferred over wildcards
1421
- **Error Handling**: Repository pattern for data operations, proper error propagation through Flows
22+
- **Expressive Kotlin Features**:
23+
- Use `runCatching` instead of try-catch blocks
24+
- Use `.use` extension for closeables
25+
- Prefer `kotlin.io` extensions over Java IO streams
26+
- Use scope functions (`let`, `apply`, `run`, `with`, `also`) appropriately
27+
- Leverage extension functions for better readability
28+
- Use property delegates (`by lazy`, `by Delegates`) where appropriate
29+
- Use Elvis operator (`?:`) for fallback values
30+
31+
## Architecture Guidelines
32+
- Network models (from APIs) should not be propagated to the presentation layer
33+
- Always map network models to domain models in the repository layer
34+
- Service layer handles API communication, Repository layer handles domain translation
35+
- One-shot data fetching should use suspend functions, not Flows
36+
- Only use Flows when there's data to continuously observe
37+
- Classes should receive their dependencies through constructor parameters:
38+
```kotlin
39+
// Preferred: Constructor injection
40+
class MyRepository(
41+
private val apiManager: ApiManager,
42+
private val database: AppDatabase
43+
)
44+
45+
// Avoid: Property injection
46+
class MyRepository : KoinComponent {
47+
private val apiManager: ApiManager by inject()
48+
private val database: AppDatabase by inject()
49+
}
50+
```
51+
- Limit KoinComponent usage to top-level components (ViewModels, Application class)
52+
- Apply appropriate visibility modifiers to control access:
53+
```kotlin
54+
// Preferred: Implementation classes are internal
55+
internal class MyRepositoryImpl(
56+
private val apiManager: ApiManager
57+
) : MyRepository {
58+
// Private implementation details
59+
private fun processData(data: Data): ProcessedData {
60+
// ...
61+
}
62+
}
63+
64+
// Public interfaces define the contract
65+
interface MyRepository {
66+
suspend fun getData(): Data
67+
}
68+
```
1569

1670
## Project Structure
1771
- `feature/` - App features organized by domain (tracker, search, settings)

desktopApp/build.gradle.kts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
11
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
2+
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
23

34
plugins {
45
alias(libs.plugins.kotlinMultiplatform)
56
alias(libs.plugins.composeMultiplatform)
67
alias(libs.plugins.composeCompiler)
8+
alias(libs.plugins.composeHotReload)
9+
}
10+
11+
// https://github.com/JetBrains/compose-hot-reload?tab=readme-ov-file#enable-optimizenonskippinggroups
12+
composeCompiler {
13+
featureFlags.add(ComposeFeatureFlag.OptimizeNonSkippingGroups)
714
}
815

916
kotlin {

desktopApp/src/desktopMain/kotlin/dev/matsem/bpm/main.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import androidx.compose.ui.window.application
2525
import androidx.compose.ui.window.rememberWindowState
2626
import bpm_tracker.desktopapp.generated.resources.Res
2727
import bpm_tracker.desktopapp.generated.resources.launcher_icon
28-
import dev.matsem.bpm.feature.app.ui.AppUi
28+
import dev.matsem.bpm.feature.app.ui.AppWindowUi
2929
import dev.matsem.bpm.injection.AppInjection
30+
import org.jetbrains.compose.reload.DevelopmentEntryPoint
3031
import org.jetbrains.compose.resources.painterResource
3132
import org.koin.compose.KoinContext
3233
import java.awt.Desktop
@@ -93,6 +94,8 @@ fun ApplicationScope.MainApplication() {
9394
}
9495
}
9596
) {
96-
AppUi()
97+
DevelopmentEntryPoint {
98+
AppWindowUi()
99+
}
97100
}
98101
}

gradle/libs.versions.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
[versions]
22
androidx-lifecycle = "2.8.4"
33
compose-multiplatform = "1.7.3"
4+
compose-hotReload = "1.0.0-dev-65"
45
junit = "4.13.2"
5-
kotlin = "2.1.10"
6+
kotlin = "2.1.20-RC"
7+
ksp = "2.1.20-RC-1.0.31"
68
kotlinx-coroutines = "1.10.1"
79
kotlinx-collections = "0.3.8"
810
kotlinx-datetime = "0.6.1"
911
kotlinx-serialization = "2.1.0"
10-
ksp = "2.1.10-1.0.29"
11-
ktorfit = "2.2.0"
12+
ktorfit = "2.4.0"
1213
koin = "4.1.0-Beta5"
1314
ktor = "3.0.1"
1415
coil = "3.0.4"
1516
dataStore = "1.1.2"
1617
androidx-room = "2.7.0-alpha13"
1718
androidx-sqlite = "2.5.0-alpha13"
1819
appDirs = "1.3.0"
20+
markdownRenderer = "0.31.0"
1921

2022
[libraries]
2123
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@@ -48,10 +50,12 @@ coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.re
4850
coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" }
4951

5052
appDirs = { module = "net.harawata:appdirs", version.ref = "appDirs" }
53+
markdownRenderer = { module = "com.mikepenz:multiplatform-markdown-renderer-m3", version.ref = "markdownRenderer" }
5154

5255
[plugins]
5356
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
5457
composeCompiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
58+
composeHotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "compose-hotReload"}
5559
kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
5660
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
5761
ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }

settings.gradle.kts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ pluginManagement {
1212
}
1313
mavenCentral()
1414
gradlePluginPortal()
15+
16+
// https://github.com/JetBrains/compose-hot-reload?tab=readme-ov-file#add-repository
17+
maven("https://packages.jetbrains.team/maven/p/firework/dev")
1518
}
1619
}
1720

@@ -25,8 +28,15 @@ dependencyResolutionManagement {
2528
}
2629
}
2730
mavenCentral()
31+
32+
// https://github.com/JetBrains/compose-hot-reload?tab=readme-ov-file#add-repository
33+
maven("https://packages.jetbrains.team/maven/p/firework/dev")
2834
}
2935
}
3036

37+
plugins {
38+
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
39+
}
40+
3141
include(":desktopApp")
3242
include(":shared")

shared/build.gradle.kts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ kotlin {
1313

1414
sourceSets {
1515
val desktopMain by getting
16+
val commonTest by getting
1617

1718
commonMain.dependencies {
1819
// compose
@@ -22,6 +23,7 @@ kotlin {
2223
implementation(compose.components.resources)
2324
implementation(compose.components.uiToolingPreview)
2425
implementation(libs.androidx.lifecycle.runtime.compose)
26+
implementation(libs.markdownRenderer)
2527

2628
// kx
2729
implementation(libs.kotlinx.collections)
@@ -52,6 +54,10 @@ kotlin {
5254
implementation(libs.androidx.sqlite.bundled)
5355
}
5456

57+
commonTest.dependencies {
58+
implementation(libs.kotlin.test)
59+
}
60+
5561
desktopMain.dependencies {
5662
implementation(libs.ktor.engine.okhttp)
5763
implementation(libs.appDirs)

shared/src/commonMain/composeResources/values/strings.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<string name="new_timer">New timer</string>
1313
<string name="pick_issue">Pick an issue</string>
1414
<string name="timer">Timer</string>
15+
<string name="new_version_banner_md">✨ New version available! [Download on GitHub](%1$s)</string>
1516

1617
<!-- Commit/Timer -->
1718
<string name="duration_label">⏳ Duration</string>
@@ -23,7 +24,7 @@
2324
<!-- Settings -->
2425
<string name="credentials_title">🔐 Credentials</string>
2526
<string name="credentials_description">Sign In by providing necessary credentials.</string>
26-
<string name="credentials_instructions">Jira API token is used to sync your profile and search issues. Tempo API token is used to synchronize worklogs with Tempo.\nInstructions on how to generate TBD.</string>
27+
<string name="credentials_instructions_md">Jira API token is used to sync your profile and search issues. Tempo API token is used to synchronize worklogs with Tempo. [Instructions here](https://github.com/matejsemancik/tempo-timer?tab=readme-ov-file#getting-started).</string>
2728
<string name="jira_url">Jira URL</string>
2829
<string name="jira_email">Atlassian account e-mail</string>
2930
<string name="jira_email_placeholder">you@corporate.org</string>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package dev.matsem.bpm.data.mapping
2+
3+
import dev.matsem.bpm.data.repo.model.AppVersion
4+
import dev.matsem.bpm.data.service.github.model.Release as ApiRelease
5+
6+
fun ApiRelease.toAppVersion(): AppVersion = AppVersion(
7+
version = tagName.removePrefix("v"),
8+
name = name,
9+
releaseUrl = htmlUrl
10+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package dev.matsem.bpm.data.repo
2+
3+
import dev.matsem.bpm.data.mapping.toAppVersion
4+
import dev.matsem.bpm.data.repo.model.AppVersion
5+
import dev.matsem.bpm.data.service.github.GitHubApiManager
6+
7+
interface GitHubRepo {
8+
suspend fun getLatestAppVersion(): AppVersion
9+
}
10+
11+
class GitHubRepoImpl(
12+
private val gitHubApiManager: GitHubApiManager
13+
) : GitHubRepo {
14+
15+
override suspend fun getLatestAppVersion(): AppVersion {
16+
return gitHubApiManager.getLatestRelease().toAppVersion()
17+
}
18+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dev.matsem.bpm.data.repo.model
2+
3+
data class AppVersion(
4+
val version: String,
5+
val name: String?,
6+
val releaseUrl: String,
7+
)

0 commit comments

Comments
 (0)