Skip to content

Commit 658b3c4

Browse files
authored
Merge pull request #288 from bilalahmadsheikh/copilot/create-first-issue
2 parents 8a9b476 + ffbbd3b commit 658b3c4

11 files changed

Lines changed: 164 additions & 10 deletions

File tree

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@
8383
<string name="sort_most_forks">Most Forks</string>
8484
<string name="sort_best_match">Best Match</string>
8585

86+
<string name="sort_order_descending">Descending</string>
87+
<string name="sort_order_ascending">Ascending</string>
88+
<string name="sort_label">Sort</string>
89+
8690
<!-- Programming languages -->
8791
<string name="language_all">All Languages</string>
8892
<string name="language_kotlin">Kotlin</string>

feature/search/data/src/commonMain/kotlin/zed/rainxch/search/data/repository/SearchRepositoryImpl.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import zed.rainxch.core.domain.model.RateLimitException
3030
import zed.rainxch.domain.model.ProgrammingLanguage
3131
import zed.rainxch.domain.model.SearchPlatform
3232
import zed.rainxch.domain.model.SortBy
33+
import zed.rainxch.domain.model.SortOrder
3334
import zed.rainxch.domain.repository.SearchRepository
3435
import zed.rainxch.search.data.dto.GithubReleaseNetworkModel
3536
import zed.rainxch.search.data.utils.LruCache
@@ -53,20 +54,22 @@ class SearchRepositoryImpl(
5354
platform: SearchPlatform,
5455
language: ProgrammingLanguage,
5556
sortBy: SortBy,
57+
sortOrder: SortOrder,
5658
page: Int
5759
): String {
5860
val queryHash = query.trim().lowercase().hashCode().toUInt().toString(16)
59-
return "search:$queryHash:${platform.name}:${language.name}:${sortBy.name}:page$page"
61+
return "search:$queryHash:${platform.name}:${language.name}:${sortBy.name}:${sortOrder.name}:page$page"
6062
}
6163

6264
override fun searchRepositories(
6365
query: String,
6466
searchPlatform: SearchPlatform,
6567
language: ProgrammingLanguage,
6668
sortBy: SortBy,
69+
sortOrder: SortOrder,
6770
page: Int
6871
): Flow<PaginatedDiscoveryRepositories> = channelFlow {
69-
val cacheKey = searchCacheKey(query, searchPlatform, language, sortBy, page)
72+
val cacheKey = searchCacheKey(query, searchPlatform, language, sortBy, sortOrder, page)
7073

7174
val cached = cacheManager.get<PaginatedDiscoveryRepositories>(cacheKey)
7275
if (cached != null) {
@@ -75,7 +78,8 @@ class SearchRepositoryImpl(
7578
}
7679

7780
val searchQuery = buildSearchQuery(query, language)
78-
val (sort, order) = sortBy.toGithubParams()
81+
val sort = sortBy.toGithubSortParam()
82+
val order = sortOrder.toGithubParam()
7983

8084
try {
8185
var currentPage = page

feature/search/domain/src/commonMain/kotlin/zed/rainxch/domain/model/SortBy.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ enum class SortBy {
55
MostForks,
66
BestMatch;
77

8-
fun toGithubParams(): Pair<String?, String> = when (this) {
9-
MostStars -> "stars" to "desc"
10-
MostForks -> "forks" to "desc"
11-
BestMatch -> null to "desc"
8+
fun toGithubSortParam(): String? = when (this) {
9+
MostStars -> "stars"
10+
MostForks -> "forks"
11+
BestMatch -> null
1212
}
1313
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package zed.rainxch.domain.model
2+
3+
enum class SortOrder {
4+
Descending,
5+
Ascending;
6+
7+
fun toGithubParam(): String = when (this) {
8+
Descending -> "desc"
9+
Ascending -> "asc"
10+
}
11+
}

feature/search/domain/src/commonMain/kotlin/zed/rainxch/domain/repository/SearchRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@ import zed.rainxch.core.domain.model.PaginatedDiscoveryRepositories
55
import zed.rainxch.domain.model.ProgrammingLanguage
66
import zed.rainxch.domain.model.SearchPlatform
77
import zed.rainxch.domain.model.SortBy
8+
import zed.rainxch.domain.model.SortOrder
89

910
interface SearchRepository {
1011
fun searchRepositories(
1112
query: String,
1213
searchPlatform: SearchPlatform,
1314
language: ProgrammingLanguage,
1415
sortBy: SortBy,
16+
sortOrder: SortOrder,
1517
page: Int
1618
): Flow<PaginatedDiscoveryRepositories>
1719
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import zed.rainxch.core.domain.model.GithubRepoSummary
44
import zed.rainxch.domain.model.ProgrammingLanguage
55
import zed.rainxch.domain.model.SearchPlatform
66
import zed.rainxch.domain.model.SortBy
7+
import zed.rainxch.domain.model.SortOrder
78

89
sealed interface SearchAction {
910
data class OnSearchChange(val query: String) : SearchAction
1011
data class OnPlatformTypeSelected(val searchPlatform: SearchPlatform) : SearchAction
1112
data class OnLanguageSelected(val language: ProgrammingLanguage) : SearchAction
1213
data class OnSortBySelected(val sortBy: SortBy) : SearchAction
14+
data class OnSortOrderSelected(val sortOrder: SortOrder) : SearchAction
1315
data class OnRepositoryClick(val repository: GithubRepoSummary) : SearchAction
1416
data class OnRepositoryDeveloperClick(val username: String) : SearchAction
1517
data class OnShareClick(val repo: GithubRepoSummary) : SearchAction
@@ -20,6 +22,7 @@ sealed interface SearchAction {
2022
data object OnClearClick : SearchAction
2123
data object Retry : SearchAction
2224
data object OnToggleLanguageSheetVisibility : SearchAction
25+
data object OnToggleSortByDialogVisibility : SearchAction
2326
data object OnFabClick : SearchAction
2427
data object DismissClipboardBanner : SearchAction
2528
}

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import androidx.compose.foundation.text.KeyboardActions
3333
import androidx.compose.foundation.text.KeyboardOptions
3434
import androidx.compose.material.icons.Icons
3535
import androidx.compose.material.icons.automirrored.filled.OpenInNew
36+
import androidx.compose.material.icons.automirrored.filled.Sort
3637
import androidx.compose.material.icons.filled.Clear
3738
import androidx.compose.material.icons.filled.Close
3839
import androidx.compose.material.icons.filled.Link
@@ -100,7 +101,10 @@ import zed.rainxch.githubstore.core.presentation.res.open_in_app
100101
import zed.rainxch.githubstore.core.presentation.res.results_found
101102
import zed.rainxch.githubstore.core.presentation.res.retry
102103
import zed.rainxch.githubstore.core.presentation.res.search_repositories_hint
104+
import zed.rainxch.githubstore.core.presentation.res.sort_label
105+
import zed.rainxch.domain.model.SortBy
103106
import zed.rainxch.search.presentation.components.LanguageFilterBottomSheet
107+
import zed.rainxch.search.presentation.components.SortByBottomSheet
104108
import zed.rainxch.search.presentation.utils.ParsedGithubLink
105109
import zed.rainxch.search.presentation.utils.label
106110

@@ -166,6 +170,23 @@ fun SearchRoot(
166170
}
167171
)
168172
}
173+
174+
if (state.isSortByDialogVisible) {
175+
SortByBottomSheet(
176+
sortByOptions = SortBy.entries,
177+
selectedSortBy = state.selectedSortBy,
178+
selectedSortOrder = state.selectedSortOrder,
179+
onSortBySelected = { sortBy ->
180+
viewModel.onAction(SearchAction.OnSortBySelected(sortBy))
181+
},
182+
onSortOrderSelected = { sortOrder ->
183+
viewModel.onAction(SearchAction.OnSortOrderSelected(sortOrder))
184+
},
185+
onDismissRequest = {
186+
viewModel.onAction(SearchAction.OnToggleSortByDialogVisibility)
187+
}
188+
)
189+
}
169190
}
170191

171192
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -399,6 +420,48 @@ fun SearchScreen(
399420
}
400421
}
401422

423+
Row(
424+
modifier = Modifier.fillMaxWidth(),
425+
horizontalArrangement = Arrangement.spacedBy(6.dp),
426+
verticalAlignment = Alignment.CenterVertically
427+
) {
428+
Text(
429+
text = stringResource(Res.string.sort_label),
430+
style = MaterialTheme.typography.bodyMedium,
431+
color = MaterialTheme.colorScheme.onSurfaceVariant,
432+
fontWeight = FontWeight.Medium
433+
)
434+
435+
FilterChip(
436+
selected = state.selectedSortBy != SortBy.BestMatch,
437+
onClick = {
438+
onAction(SearchAction.OnToggleSortByDialogVisibility)
439+
},
440+
label = {
441+
Row(
442+
verticalAlignment = Alignment.CenterVertically,
443+
horizontalArrangement = Arrangement.spacedBy(4.dp)
444+
) {
445+
Icon(
446+
imageVector = Icons.AutoMirrored.Filled.Sort,
447+
contentDescription = null,
448+
modifier = Modifier.size(18.dp)
449+
)
450+
Text(
451+
text = stringResource(state.selectedSortBy.label()),
452+
style = MaterialTheme.typography.bodyMedium,
453+
fontWeight = FontWeight.Medium
454+
)
455+
Icon(
456+
imageVector = Icons.Outlined.KeyboardArrowDown,
457+
contentDescription = null,
458+
modifier = Modifier.size(18.dp)
459+
)
460+
}
461+
}
462+
)
463+
}
464+
402465
Spacer(Modifier.height(6.dp))
403466

404467
if (state.totalCount != null) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@ import zed.rainxch.core.presentation.model.DiscoveryRepository
44
import zed.rainxch.domain.model.ProgrammingLanguage
55
import zed.rainxch.domain.model.SearchPlatform
66
import zed.rainxch.domain.model.SortBy
7+
import zed.rainxch.domain.model.SortOrder
78
import zed.rainxch.search.presentation.utils.ParsedGithubLink
89

910
data class SearchState(
1011
val query: String = "",
1112
val repositories: List<DiscoveryRepository> = emptyList(),
1213
val selectedSearchPlatform: SearchPlatform = SearchPlatform.All,
1314
val selectedSortBy: SortBy = SortBy.BestMatch,
15+
val selectedSortOrder: SortOrder = SortOrder.Descending,
1416
val selectedLanguage: ProgrammingLanguage = ProgrammingLanguage.All,
1517
val isLoading: Boolean = false,
1618
val isLoadingMore: Boolean = false,
1719
val errorMessage: String? = null,
1820
val hasMorePages: Boolean = true,
1921
val totalCount: Int? = null,
2022
val isLanguageSheetVisible: Boolean = false,
23+
val isSortByDialogVisible: Boolean = false,
2124
val detectedLinks: List<ParsedGithubLink> = emptyList(),
2225
val clipboardLinks: List<ParsedGithubLink> = emptyList(),
2326
val isClipboardBannerVisible: Boolean = false,

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,7 @@ class SearchViewModel(
235235
searchPlatform = _state.value.selectedSearchPlatform,
236236
language = _state.value.selectedLanguage,
237237
sortBy = _state.value.selectedSortBy,
238+
sortOrder = _state.value.selectedSortOrder,
238239
page = currentPage
239240
)
240241
.collect { paginatedRepos ->
@@ -447,6 +448,23 @@ class SearchViewModel(
447448
}
448449
}
449450

451+
is SearchAction.OnSortOrderSelected -> {
452+
if (_state.value.selectedSortOrder != action.sortOrder) {
453+
_state.update {
454+
it.copy(selectedSortOrder = action.sortOrder)
455+
}
456+
currentPage = 1
457+
searchDebounceJob?.cancel()
458+
performSearch(isInitial = true)
459+
}
460+
}
461+
462+
SearchAction.OnToggleSortByDialogVisibility -> {
463+
_state.update {
464+
it.copy(isSortByDialogVisible = !it.isSortByDialogVisible)
465+
}
466+
}
467+
450468
SearchAction.LoadMore -> {
451469
if (!_state.value.isLoadingMore && !_state.value.isLoading && _state.value.hasMorePages) {
452470
performSearch(isInitial = false)

feature/search/presentation/src/commonMain/kotlin/zed/rainxch/search/presentation/components/SortByBottomSheet.kt

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,33 @@ import androidx.compose.runtime.Composable
44
import androidx.compose.ui.Modifier
55
import androidx.compose.foundation.layout.Arrangement
66
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.Spacer
79
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.height
811
import androidx.compose.material3.AlertDialog
12+
import androidx.compose.material3.FilterChip
13+
import androidx.compose.material3.HorizontalDivider
914
import androidx.compose.material3.MaterialTheme
1015
import androidx.compose.material3.Text
1116
import androidx.compose.material3.TextButton
17+
import androidx.compose.ui.Alignment
1218
import androidx.compose.ui.unit.dp
1319
import org.jetbrains.compose.resources.stringResource
1420
import zed.rainxch.domain.model.SortBy
21+
import zed.rainxch.domain.model.SortOrder
22+
import zed.rainxch.githubstore.core.presentation.res.Res
23+
import zed.rainxch.githubstore.core.presentation.res.close
24+
import zed.rainxch.githubstore.core.presentation.res.sort_by
1525
import zed.rainxch.search.presentation.utils.label
1626

1727
@Composable
1828
fun SortByBottomSheet(
1929
sortByOptions: List<SortBy>,
2030
selectedSortBy: SortBy,
31+
selectedSortOrder: SortOrder,
2132
onSortBySelected: (SortBy) -> Unit,
33+
onSortOrderSelected: (SortOrder) -> Unit,
2234
onDismissRequest: () -> Unit,
2335
modifier: Modifier = Modifier
2436
) {
@@ -27,12 +39,12 @@ fun SortByBottomSheet(
2739
confirmButton = {},
2840
dismissButton = {
2941
TextButton(onClick = onDismissRequest) {
30-
Text(text = "Close")
42+
Text(text = stringResource(Res.string.close))
3143
}
3244
},
3345
title = {
3446
Text(
35-
text = "Sort by",
47+
text = stringResource(Res.string.sort_by),
3648
style = MaterialTheme.typography.titleMedium
3749
)
3850
},
@@ -46,7 +58,6 @@ fun SortByBottomSheet(
4658
TextButton(
4759
onClick = {
4860
onSortBySelected(option)
49-
onDismissRequest()
5061
},
5162
modifier = Modifier.fillMaxWidth()
5263
) {
@@ -57,6 +68,29 @@ fun SortByBottomSheet(
5768
)
5869
}
5970
}
71+
72+
HorizontalDivider()
73+
74+
Spacer(Modifier.height(4.dp))
75+
76+
Row(
77+
modifier = Modifier.fillMaxWidth(),
78+
horizontalArrangement = Arrangement.spacedBy(8.dp),
79+
verticalAlignment = Alignment.CenterVertically
80+
) {
81+
SortOrder.entries.forEach { order ->
82+
FilterChip(
83+
selected = order == selectedSortOrder,
84+
onClick = { onSortOrderSelected(order) },
85+
label = {
86+
Text(
87+
text = stringResource(order.label()),
88+
style = MaterialTheme.typography.bodyMedium
89+
)
90+
}
91+
)
92+
}
93+
}
6094
}
6195
}
6296
)

0 commit comments

Comments
 (0)