Skip to content

Commit 03a544e

Browse files
committed
feat: implement "seen" status indicator for repositories
- Add `isSeen` property to `DiscoveryRepositoryUi` to track if a repository has been viewed. - Update `HomeViewModel` and `SearchViewModel` to observe seen repository IDs and reactively update the UI state. - Initialize the `isSeen` status when mapping repository data in both home and search flows. - Implement `SeenBadge` component in `RepositoryCard` to display a "Viewed" label with a visibility icon. - Add "Viewed" localized string resource. - Refactor `RepositoryCard` to display multiple badges (Installation status and Seen status) using a `Row` layout.
1 parent 7df4ea2 commit 03a544e

5 files changed

Lines changed: 73 additions & 6 deletions

File tree

core/presentation/src/commonMain/composeResources/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,4 +592,5 @@
592592
<string name="clear_seen_history">Clear Seen History</string>
593593
<string name="clear_seen_history_description">Reset all seen repositories so they appear again in feeds</string>
594594
<string name="seen_history_cleared">Seen history cleared</string>
595+
<string name="seen_badge">Viewed</string>
595596
</resources>

core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/components/RepositoryCard.kt

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import androidx.compose.material.icons.filled.OpenInBrowser
2323
import androidx.compose.material.icons.filled.Share
2424
import androidx.compose.material.icons.filled.Star
2525
import androidx.compose.material.icons.filled.Update
26+
import androidx.compose.material.icons.outlined.Visibility
2627
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
2728
import androidx.compose.material3.Icon
2829
import androidx.compose.material3.IconButton
@@ -54,6 +55,7 @@ import zed.rainxch.githubstore.core.presentation.res.forked_repository
5455
import zed.rainxch.githubstore.core.presentation.res.home_view_details
5556
import zed.rainxch.githubstore.core.presentation.res.installed
5657
import zed.rainxch.githubstore.core.presentation.res.open_in_browser
58+
import zed.rainxch.githubstore.core.presentation.res.seen_badge
5759
import zed.rainxch.githubstore.core.presentation.res.share_repository
5860
import zed.rainxch.githubstore.core.presentation.res.update_available
5961

@@ -221,12 +223,23 @@ fun RepositoryCard(
221223
}
222224
}
223225

224-
if (discoveryRepositoryUi.isInstalled) {
226+
if (discoveryRepositoryUi.isInstalled || discoveryRepositoryUi.isSeen) {
225227
Spacer(Modifier.height(12.dp))
226228

227-
InstallStatusBadge(
228-
isUpdateAvailable = discoveryRepositoryUi.isUpdateAvailable,
229-
)
229+
Row(
230+
horizontalArrangement = Arrangement.spacedBy(8.dp),
231+
verticalAlignment = Alignment.CenterVertically,
232+
) {
233+
if (discoveryRepositoryUi.isInstalled) {
234+
InstallStatusBadge(
235+
isUpdateAvailable = discoveryRepositoryUi.isUpdateAvailable,
236+
)
237+
}
238+
239+
if (discoveryRepositoryUi.isSeen) {
240+
SeenBadge()
241+
}
242+
}
230243
}
231244

232245
if (discoveryRepositoryUi.repository.availablePlatforms.isNotEmpty()) {
@@ -434,6 +447,34 @@ fun InstallStatusBadge(
434447
}
435448
}
436449

450+
@Composable
451+
fun SeenBadge(modifier: Modifier = Modifier) {
452+
Surface(
453+
modifier = modifier,
454+
shape = RoundedCornerShape(12.dp),
455+
color = MaterialTheme.colorScheme.surfaceContainerHighest,
456+
) {
457+
Row(
458+
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
459+
verticalAlignment = Alignment.CenterVertically,
460+
horizontalArrangement = Arrangement.spacedBy(4.dp),
461+
) {
462+
Icon(
463+
imageVector = Icons.Outlined.Visibility,
464+
contentDescription = null,
465+
modifier = Modifier.size(14.dp),
466+
tint = MaterialTheme.colorScheme.onSurfaceVariant,
467+
)
468+
Text(
469+
text = stringResource(Res.string.seen_badge),
470+
style = MaterialTheme.typography.labelSmall,
471+
color = MaterialTheme.colorScheme.onSurfaceVariant,
472+
fontWeight = FontWeight.SemiBold,
473+
)
474+
}
475+
}
476+
}
477+
437478
@Preview
438479
@Composable
439480
fun RepositoryCardPreview() {

core/presentation/src/commonMain/kotlin/zed/rainxch/core/presentation/model/DiscoveryRepositoryUi.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ data class DiscoveryRepositoryUi(
55
val isUpdateAvailable: Boolean,
66
val isFavourite: Boolean,
77
val isStarred: Boolean,
8+
val isSeen: Boolean = false,
89
val repository: GithubRepoSummaryUi,
910
)

feature/home/presentation/src/commonMain/kotlin/zed/rainxch/home/presentation/HomeViewModel.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ class HomeViewModel(
202202
.first()
203203
.associateBy { it.repoId }
204204

205+
val seenIds = _state.value.seenRepoIds
206+
205207
val newReposWithStatus =
206208
paginatedRepos.repos.map { repo ->
207209
val app = installedAppsMap[repo.id]
@@ -212,6 +214,7 @@ class HomeViewModel(
212214
isInstalled = app != null,
213215
isFavourite = favourite != null,
214216
isStarred = starred != null,
217+
isSeen = repo.id in seenIds,
215218
isUpdateAvailable = app?.isUpdateAvailable ?: false,
216219
repository = repo.toUi(),
217220
)
@@ -372,7 +375,16 @@ class HomeViewModel(
372375
private fun observeSeenRepos() {
373376
viewModelScope.launch {
374377
seenReposRepository.getAllSeenRepoIds().collect { ids ->
375-
_state.update { it.copy(seenRepoIds = ids) }
378+
_state.update { current ->
379+
current.copy(
380+
seenRepoIds = ids,
381+
repos =
382+
current.repos
383+
.map { repo ->
384+
repo.copy(isSeen = repo.repository.id in ids)
385+
}.toImmutableList(),
386+
)
387+
}
376388
}
377389
}
378390
}

feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/SearchViewModel.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,16 @@ class SearchViewModel(
103103
private fun observeSeenRepos() {
104104
viewModelScope.launch {
105105
seenReposRepository.getAllSeenRepoIds().collect { ids ->
106-
_state.update { it.copy(seenRepoIds = ids) }
106+
_state.update { current ->
107+
current.copy(
108+
seenRepoIds = ids,
109+
repositories =
110+
current.repositories
111+
.map { repo ->
112+
repo.copy(isSeen = repo.repository.id in ids)
113+
}.toImmutableList(),
114+
)
115+
}
107116
}
108117
}
109118
}
@@ -297,6 +306,8 @@ class SearchViewModel(
297306
).collect { paginatedRepos ->
298307
currentPage = paginatedRepos.nextPageIndex
299308

309+
val seenIds = _state.value.seenRepoIds
310+
300311
val newReposWithStatus =
301312
paginatedRepos.repos.map { repo ->
302313
val app = installedMap[repo.id]
@@ -307,6 +318,7 @@ class SearchViewModel(
307318
isInstalled = app != null,
308319
isFavourite = favourite != null,
309320
isStarred = starred != null,
321+
isSeen = repo.id in seenIds,
310322
isUpdateAvailable = app?.isUpdateAvailable ?: false,
311323
repository = repo.toUi(),
312324
)

0 commit comments

Comments
 (0)