Skip to content

Commit 1e6463f

Browse files
committed
refactor: introduce UI models and mappers for the apps feature
- Implement `Ui` model counterparts for `InstalledApp`, `DeviceApp`, `GithubAsset`, `GithubUser`, and `GithubRepoInfo` to decouple the presentation layer from the domain layer. - Add mapper functions (`toUi`, `toDomain`) for all new UI models. - Update `AppsState`, `AppsAction`, and `AppsViewModel` to use the new UI models instead of domain models. - Integrate `kotlinx.collections.immutable` (e.g., `ImmutableList`, `persistentListOf`) across the presentation layer to improve performance and ensure state stability. - Refactor `UpdateState` from a sealed interface to a sealed class. - Clean up `AppsRoot` by removing unused imports and streamlining event handling for app linking and importing. - Improve `AppsViewModel` logic for filtering and sorting apps, ensuring the results are returned as immutable lists.
1 parent 7126570 commit 1e6463f

19 files changed

Lines changed: 439 additions & 124 deletions

composeApp/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ kotlin {
7070
implementation(libs.jetbrains.compose.material.icons.extended)
7171

7272
implementation(libs.touchlab.kermit)
73+
implementation(libs.kotlinx.collections.immutable)
7374

7475
implementation(compose.runtime)
7576
implementation(compose.foundation)

feature/apps/presentation/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ kotlin {
1717

1818
implementation(libs.bundles.landscapist)
1919
implementation(libs.liquid)
20+
21+
implementation(libs.kotlinx.collections.immutable)
2022
}
2123
}
2224

feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsAction.kt

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package zed.rainxch.apps.presentation
22

3-
import zed.rainxch.core.domain.model.DeviceApp
4-
import zed.rainxch.core.domain.model.GithubAsset
5-
import zed.rainxch.core.domain.model.InstalledApp
3+
import zed.rainxch.apps.presentation.model.InstalledAppUi
4+
import zed.rainxch.apps.presentation.model.DeviceAppUi
5+
import zed.rainxch.apps.presentation.model.GithubAssetUi
6+
67

78
sealed interface AppsAction {
89
data object OnNavigateBackClick : AppsAction
@@ -12,11 +13,11 @@ sealed interface AppsAction {
1213
) : AppsAction
1314

1415
data class OnOpenApp(
15-
val app: InstalledApp,
16+
val app: InstalledAppUi,
1617
) : AppsAction
1718

1819
data class OnUpdateApp(
19-
val app: InstalledApp,
20+
val app: InstalledAppUi,
2021
) : AppsAction
2122

2223
data class OnCancelUpdate(
@@ -36,22 +37,22 @@ sealed interface AppsAction {
3637
) : AppsAction
3738

3839
data class OnUninstallApp(
39-
val app: InstalledApp,
40+
val app: InstalledAppUi,
4041
) : AppsAction
4142

4243
// Uninstall confirmation
43-
data class OnUninstallConfirmed(val app: InstalledApp) : AppsAction
44+
data class OnUninstallConfirmed(val app: InstalledAppUi) : AppsAction
4445
data object OnDismissUninstallDialog : AppsAction
4546

4647
// Link app to repo
4748
data object OnAddByLinkClick : AppsAction
4849
data object OnDismissLinkSheet : AppsAction
4950
data class OnDeviceAppSearchChange(val query: String) : AppsAction
50-
data class OnDeviceAppSelected(val app: DeviceApp) : AppsAction
51+
data class OnDeviceAppSelected(val app: DeviceAppUi) : AppsAction
5152
data class OnRepoUrlChanged(val url: String) : AppsAction
5253
data object OnValidateAndLinkRepo : AppsAction
5354
data object OnBackToAppPicker : AppsAction
54-
data class OnLinkAssetSelected(val asset: GithubAsset) : AppsAction
55+
data class OnLinkAssetSelected(val asset: GithubAssetUi) : AppsAction
5556
data object OnBackToEnterUrl : AppsAction
5657

5758
// Export/Import

feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsEvent.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ sealed interface AppsEvent {
1919
val appName: String,
2020
) : AppsEvent
2121

22-
data class ExportReady(
23-
val json: String,
24-
) : AppsEvent
25-
2622
data class ImportComplete(
2723
val result: ImportResult,
2824
) : AppsEvent

feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsRoot.kt

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
package zed.rainxch.apps.presentation
44

5-
import androidx.compose.foundation.clickable
65
import androidx.compose.foundation.layout.Arrangement
76
import androidx.compose.foundation.layout.Box
87
import androidx.compose.foundation.layout.Column
@@ -20,7 +19,6 @@ import androidx.compose.foundation.lazy.items
2019
import androidx.compose.foundation.shape.CircleShape
2120
import androidx.compose.foundation.shape.RoundedCornerShape
2221
import androidx.compose.material.icons.Icons
23-
import androidx.compose.material.icons.automirrored.filled.ArrowBack
2422
import androidx.compose.material.icons.automirrored.filled.OpenInNew
2523
import androidx.compose.material.icons.filled.Add
2624
import androidx.compose.material.icons.filled.Cancel
@@ -37,12 +35,10 @@ import androidx.compose.material3.AlertDialog
3735
import androidx.compose.material3.Button
3836
import androidx.compose.material3.ButtonDefaults
3937
import androidx.compose.material3.Card
40-
import androidx.compose.material3.CardDefaults
4138
import androidx.compose.material3.CircularProgressIndicator
4239
import androidx.compose.material3.CircularWavyProgressIndicator
4340
import androidx.compose.material3.DropdownMenu
4441
import androidx.compose.material3.DropdownMenuItem
45-
import androidx.compose.material3.ElevatedCard
4642
import androidx.compose.material3.ExperimentalMaterial3Api
4743
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
4844
import androidx.compose.material3.FloatingActionButton
@@ -73,7 +69,6 @@ import androidx.compose.ui.text.font.FontWeight
7369
import androidx.compose.ui.text.style.TextOverflow
7470
import androidx.compose.ui.tooling.preview.Preview
7571
import androidx.compose.ui.unit.dp
76-
import androidx.lifecycle.compose.collectAsStateWithLifecycle
7772
import com.skydoves.landscapist.coil3.CoilImage
7873
import io.github.fletchmckee.liquid.liquefiable
7974
import kotlinx.coroutines.launch
@@ -88,7 +83,34 @@ import zed.rainxch.core.presentation.locals.LocalBottomNavigationHeight
8883
import zed.rainxch.core.presentation.locals.LocalBottomNavigationLiquid
8984
import zed.rainxch.core.presentation.theme.GithubStoreTheme
9085
import zed.rainxch.core.presentation.utils.ObserveAsEvents
91-
import zed.rainxch.githubstore.core.presentation.res.*
86+
import zed.rainxch.githubstore.core.presentation.res.Res
87+
import zed.rainxch.githubstore.core.presentation.res.add_by_link
88+
import zed.rainxch.githubstore.core.presentation.res.cancel
89+
import zed.rainxch.githubstore.core.presentation.res.check_for_updates
90+
import zed.rainxch.githubstore.core.presentation.res.checking
91+
import zed.rainxch.githubstore.core.presentation.res.checking_for_updates
92+
import zed.rainxch.githubstore.core.presentation.res.confirm_uninstall_message
93+
import zed.rainxch.githubstore.core.presentation.res.confirm_uninstall_title
94+
import zed.rainxch.githubstore.core.presentation.res.currently_updating
95+
import zed.rainxch.githubstore.core.presentation.res.downloading
96+
import zed.rainxch.githubstore.core.presentation.res.error_with_message
97+
import zed.rainxch.githubstore.core.presentation.res.export_apps
98+
import zed.rainxch.githubstore.core.presentation.res.import_apps
99+
import zed.rainxch.githubstore.core.presentation.res.installed_apps
100+
import zed.rainxch.githubstore.core.presentation.res.installing
101+
import zed.rainxch.githubstore.core.presentation.res.last_checked
102+
import zed.rainxch.githubstore.core.presentation.res.last_checked_hours_ago
103+
import zed.rainxch.githubstore.core.presentation.res.last_checked_just_now
104+
import zed.rainxch.githubstore.core.presentation.res.last_checked_minutes_ago
105+
import zed.rainxch.githubstore.core.presentation.res.no_apps_found
106+
import zed.rainxch.githubstore.core.presentation.res.open
107+
import zed.rainxch.githubstore.core.presentation.res.pending_install
108+
import zed.rainxch.githubstore.core.presentation.res.search_your_apps
109+
import zed.rainxch.githubstore.core.presentation.res.uninstall
110+
import zed.rainxch.githubstore.core.presentation.res.update
111+
import zed.rainxch.githubstore.core.presentation.res.update_all
112+
import zed.rainxch.githubstore.core.presentation.res.updated_successfully
113+
import zed.rainxch.githubstore.core.presentation.res.updating_x_of_y
92114

93115
@Composable
94116
fun AppsRoot(
@@ -118,24 +140,9 @@ fun AppsRoot(
118140
}
119141
}
120142

121-
is AppsEvent.AppLinkedSuccessfully -> {
122-
coroutineScope.launch {
123-
snackbarHostState.showSnackbar("${event.appName} linked successfully")
124-
}
125-
}
126-
127-
is AppsEvent.ExportReady -> {
128-
// Share already handled by ShareManager in ViewModel
129-
}
143+
is AppsEvent.AppLinkedSuccessfully -> { /* handled by ShowSuccess */ }
130144

131-
is AppsEvent.ImportComplete -> {
132-
val r = event.result
133-
coroutineScope.launch {
134-
snackbarHostState.showSnackbar(
135-
"Imported ${r.imported}, skipped ${r.skipped}, failed ${r.failed}",
136-
)
137-
}
138-
}
145+
is AppsEvent.ImportComplete -> { /* handled by ShowSuccess */ }
139146
}
140147
}
141148

@@ -581,7 +588,7 @@ fun AppItemCard(
581588
Spacer(Modifier.height(8.dp))
582589

583590
Text(
584-
text = app.repoDescription!!,
591+
text = app.repoDescription,
585592
style = MaterialTheme.typography.bodyMediumEmphasized,
586593
maxLines = 2,
587594
overflow = TextOverflow.Ellipsis,

feature/apps/presentation/src/commonMain/kotlin/zed/rainxch/apps/presentation/AppsState.kt

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package zed.rainxch.apps.presentation
22

3+
import kotlinx.collections.immutable.ImmutableList
4+
import kotlinx.collections.immutable.persistentListOf
5+
import kotlinx.collections.immutable.toImmutableList
36
import zed.rainxch.apps.domain.model.GithubRepoInfo
47
import zed.rainxch.apps.presentation.model.AppItem
8+
import zed.rainxch.apps.presentation.model.DeviceAppUi
9+
import zed.rainxch.apps.presentation.model.GithubAssetUi
10+
import zed.rainxch.apps.presentation.model.GithubRepoInfoUi
11+
import zed.rainxch.apps.presentation.model.InstalledAppUi
512
import zed.rainxch.apps.presentation.model.UpdateAllProgress
613
import zed.rainxch.core.domain.model.DeviceApp
714
import zed.rainxch.core.domain.model.GithubAsset
8-
import zed.rainxch.core.domain.model.InstalledApp
915

1016
data class AppsState(
11-
val apps: List<AppItem> = emptyList(),
12-
val filteredApps: List<AppItem> = emptyList(),
17+
val apps: ImmutableList<AppItem> = persistentListOf(),
18+
val filteredApps: ImmutableList<AppItem> = persistentListOf(),
1319
val searchQuery: String = "",
1420
val isLoading: Boolean = false,
1521
val isUpdatingAll: Boolean = false,
@@ -21,32 +27,33 @@ data class AppsState(
2127
// Link app to repo
2228
val showLinkSheet: Boolean = false,
2329
val linkStep: LinkStep = LinkStep.PickApp,
24-
val deviceApps: List<DeviceApp> = emptyList(),
30+
val deviceApps: ImmutableList<DeviceAppUi> = persistentListOf(),
2531
val deviceAppSearchQuery: String = "",
26-
val selectedDeviceApp: DeviceApp? = null,
32+
val selectedDeviceApp: DeviceAppUi? = null,
2733
val repoUrl: String = "",
2834
val isValidatingRepo: Boolean = false,
2935
val repoValidationError: String? = null,
3036
val linkValidationStatus: String? = null,
31-
val linkInstallableAssets: List<GithubAsset> = emptyList(),
32-
val linkSelectedAsset: GithubAsset? = null,
37+
val linkInstallableAssets: ImmutableList<GithubAssetUi> = persistentListOf(),
38+
val linkSelectedAsset: GithubAssetUi? = null,
3339
val linkDownloadProgress: Int? = null,
34-
val fetchedRepoInfo: GithubRepoInfo? = null,
40+
val fetchedRepoInfo: GithubRepoInfoUi? = null,
3541
// Export/Import
3642
val isExporting: Boolean = false,
3743
val isImporting: Boolean = false,
3844
// Uninstall confirmation
39-
val appPendingUninstall: InstalledApp? = null,
45+
val appPendingUninstall: InstalledAppUi? = null,
4046
) {
41-
val filteredDeviceApps: List<DeviceApp>
47+
val filteredDeviceApps: ImmutableList<DeviceAppUi>
4248
get() =
4349
if (deviceAppSearchQuery.isBlank()) {
44-
deviceApps
50+
deviceApps.toImmutableList()
4551
} else {
46-
deviceApps.filter {
47-
it.appName.contains(deviceAppSearchQuery, ignoreCase = true) ||
48-
it.packageName.contains(deviceAppSearchQuery, ignoreCase = true)
49-
}
52+
deviceApps
53+
.filter {
54+
it.appName.contains(deviceAppSearchQuery, ignoreCase = true) ||
55+
it.packageName.contains(deviceAppSearchQuery, ignoreCase = true)
56+
}.toImmutableList()
5057
}
5158
}
5259

0 commit comments

Comments
 (0)