Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ val searchModule: Module = module {
viewModel {
SearchViewModel(
searchRepository = get(),
installedAppsRepository = get()
installedAppsRepository = get(),
syncInstalledAppsUseCase = get()
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import githubstore.composeapp.generated.resources.results_found
import githubstore.composeapp.generated.resources.retry
import githubstore.composeapp.generated.resources.search_repositories_hint
import githubstore.composeapp.generated.resources.sort_by
import kotlinx.coroutines.delay
import org.jetbrains.compose.resources.stringResource
import org.jetbrains.compose.ui.tooling.preview.Preview
import org.koin.compose.viewmodel.koinViewModel
Expand Down Expand Up @@ -130,16 +131,25 @@ fun SearchScreen(
derivedStateOf {
val layoutInfo = listState.layoutInfo
val totalItems = layoutInfo.totalItemsCount
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
val visibleItems = layoutInfo.visibleItemsInfo

if (totalItems == 0 ||
state.isLoadingMore ||
state.isLoading ||
!state.hasMorePages) {
return@derivedStateOf false
}

val lastVisibleItem = visibleItems.lastOrNull() ?: return@derivedStateOf false
val viewportEndOffset = layoutInfo.viewportEndOffset

val hasEmptySpaceAtBottom = lastVisibleItem.index == totalItems - 1 &&
lastVisibleItem.offset.y + lastVisibleItem.size.height < viewportEndOffset

val threshold = (totalItems * 0.8f).toInt()
val isNearEnd = lastVisibleItem.index >= threshold

totalItems > 0 &&
lastVisibleItem != null &&
lastVisibleItem.index >= threshold &&
!state.isLoadingMore &&
!state.isLoading &&
state.hasMorePages
isNearEnd || hasEmptySpaceAtBottom
}
}

Expand All @@ -151,6 +161,27 @@ fun SearchScreen(
}
}

LaunchedEffect(listState.layoutInfo.totalItemsCount, listState.layoutInfo.viewportEndOffset) {
val layoutInfo = listState.layoutInfo
val visibleItems = layoutInfo.visibleItemsInfo
val lastVisible = visibleItems.lastOrNull()

if (lastVisible != null &&
layoutInfo.totalItemsCount > 0 &&
!state.isLoadingMore &&
!state.isLoading &&
state.hasMorePages) {

val hasEmptySpace = lastVisible.index == layoutInfo.totalItemsCount - 1 &&
lastVisible.offset.y + lastVisible.size.height < layoutInfo.viewportEndOffset

if (hasEmptySpace) {
delay(100)
currentOnAction(SearchAction.LoadMore)
}
}
}

LaunchedEffect(Unit) {
if (state.query.isEmpty()) {
focusRequester.requestFocus()
Expand Down Expand Up @@ -267,40 +298,6 @@ fun SearchScreen(
)
}

if (false) { // FOR NOW SORTING FEATURE IS NOT AVAILABLE
Row(
modifier = Modifier
.align(Alignment.End)
.clickable {

},
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End)
) {
Text(
text = stringResource(Res.string.sort_by) + ": ${state.selectedSortBy.displayText()}",
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.outline,
fontWeight = FontWeight.Bold
)

Icon(
imageVector = Icons.Outlined.KeyboardArrowDown,
contentDescription = null,
modifier = Modifier.size(24.dp),
tint = MaterialTheme.colorScheme.outline,
)
}
SortByBottomSheet(
sortByOptions = SortBy.entries.toList(),
selectedSortBy = state.selectedSortBy,
onSortBySelected = { chosen ->
onAction(SearchAction.OnSortBySelected(chosen))
},
onDismissRequest = { }
)
}

Box(Modifier.fillMaxSize()) {
if (state.isLoading && state.repositories.isEmpty()) {
Box(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.getString
import zed.rainxch.githubstore.core.domain.repository.InstalledAppsRepository
import zed.rainxch.githubstore.core.domain.use_cases.SyncInstalledAppsUseCase
import zed.rainxch.githubstore.feature.search.domain.repository.SearchRepository

class SearchViewModel(
private val searchRepository: SearchRepository,
private val installedAppsRepository: InstalledAppsRepository
private val installedAppsRepository: InstalledAppsRepository,
private val syncInstalledAppsUseCase: SyncInstalledAppsUseCase
) : ViewModel() {

private var currentSearchJob: Job? = null
Expand All @@ -35,9 +37,23 @@ class SearchViewModel(
val state = _state.asStateFlow()

init {
syncSystemState()
observeInstalledApps()
}

private fun syncSystemState() {
viewModelScope.launch {
try {
val result = syncInstalledAppsUseCase()
if (result.isFailure) {
Logger.w { "Initial sync had issues: ${result.exceptionOrNull()?.message}" }
}
} catch (e: Exception) {
Logger.e { "Initial sync failed: ${e.message}" }
}
}
}

private fun observeInstalledApps() {
viewModelScope.launch {
installedAppsRepository.getAllInstalledApps().collect { installedApps ->
Expand Down Expand Up @@ -99,21 +115,13 @@ class SearchViewModel(
.collect { paginatedRepos ->
currentPage = paginatedRepos.nextPageIndex

val newReposWithStatus = coroutineScope {
paginatedRepos.repos.map { repo ->
async(Dispatchers.IO) {
val app = installedMap[repo.id]
val isUpdateAvailable = if (app?.packageName != null) {
installedAppsRepository.checkForUpdates(app.packageName)
} else false

SearchRepo(
isInstalled = app != null,
isUpdateAvailable = isUpdateAvailable,
repo = repo
)
}
}.awaitAll()
val newReposWithStatus = paginatedRepos.repos.map { repo ->
val app = installedMap[repo.id]
SearchRepo(
isInstalled = app != null,
isUpdateAvailable = app?.isUpdateAvailable ?: false,
repo = repo
)
}

_state.update { currentState ->
Expand Down Expand Up @@ -183,9 +191,7 @@ class SearchViewModel(
is SearchAction.OnLanguageSelected -> {
if (_state.value.selectedLanguage != action.language) {
_state.update {
it.copy(
selectedLanguage = action.language
)
it.copy(selectedLanguage = action.language)
}
currentPage = 1
searchDebounceJob?.cancel()
Expand Down Expand Up @@ -225,9 +231,7 @@ class SearchViewModel(

SearchAction.OnToggleLanguageSheetVisibility -> {
_state.update {
it.copy(
isLanguageSheetVisible = !it.isLanguageSheetVisible
)
it.copy(isLanguageSheetVisible = !it.isLanguageSheetVisible)
}
}

Expand Down