feat: Add ordering functionality for friends list

This commit is contained in:
2025-03-26 01:28:50 +03:00
parent 3dbf2bd8a4
commit 0ae05709db
9 changed files with 149 additions and 47 deletions
@@ -8,11 +8,13 @@ import com.slack.eithernet.ApiResult
interface FriendsRepository {
suspend fun getAllFriends(
order: String,
count: Int?,
offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain>
suspend fun getFriends(
order: String,
count: Int?,
offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain>
@@ -25,10 +25,11 @@ class FriendsRepositoryImpl(
) : FriendsRepository {
override suspend fun getAllFriends(
order: String,
count: Int?,
offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val friends = async { getFriends(count, offset) }.await()
val friends = async { getFriends(order, count, offset) }.await()
.successOrElse { failure ->
return@withContext failure
}
@@ -42,11 +43,12 @@ class FriendsRepositoryImpl(
}
override suspend fun getFriends(
order: String,
count: Int?,
offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = GetFriendsRequest(
order = "hints",
order = order,
count = count,
offset = offset,
fields = VkConstants.USER_FIELDS
@@ -8,11 +8,13 @@ import kotlinx.coroutines.flow.Flow
interface FriendsUseCase {
fun getAllFriends(
order: String = "hints",
count: Int?,
offset: Int?
): Flow<State<FriendsInfo>>
fun getFriends(
order: String = "hints",
count: Int?,
offset: Int?
): Flow<State<List<VkUser>>>
@@ -11,19 +11,26 @@ import kotlinx.coroutines.flow.flow
class FriendsUseCaseImpl(private val repository: FriendsRepository) :
FriendsUseCase {
override fun getAllFriends(count: Int?, offset: Int?): Flow<State<FriendsInfo>> = flow {
override fun getAllFriends(order: String, count: Int?, offset: Int?): Flow<State<FriendsInfo>> = flow {
emit(State.Loading)
val newState = repository.getAllFriends(count, offset).mapToState()
val newState = repository.getAllFriends(order, count, offset).mapToState()
emit(newState)
}
override fun getFriends(
count: Int?, offset: Int?
order: String,
count: Int?,
offset: Int?
): Flow<State<List<VkUser>>> = flow {
emit(State.Loading)
val newState = repository.getFriends(count, offset).mapToState()
val newState = repository.getFriends(
order = order,
count = count,
offset = offset
).mapToState()
emit(newState)
}
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M11,18h2c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1h-2c-0.55,0 -1,0.45 -1,1s0.45,1 1,1zM3,7c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L4,6c-0.55,0 -1,0.45 -1,1zM7,13h10c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1L7,11c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z" />
</vector>
@@ -34,6 +34,8 @@ interface FriendsViewModel {
fun setScrollIndex(index: Int)
fun setScrollOffset(offset: Int)
fun onOrderTypeChanged(newOrderType: String)
}
abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel {
@@ -69,6 +71,12 @@ abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel {
screenState.setValue { old -> old.copy(scrollOffset = offset) }
}
override fun onOrderTypeChanged(newOrderType: String) {
if (screenState.value.orderType == newOrderType) return
screenState.setValue { old -> old.copy(orderType = newOrderType) }
loadFriends(offset = 0)
}
abstract fun loadFriends(offset: Int = currentOffset.value)
protected fun handleError(error: State.Error) {
@@ -138,52 +146,55 @@ class FriendsViewModelImpl(
}
override fun loadFriends(offset: Int) {
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
.listenValue(viewModelScope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val itemsCountSufficient = response.size == LOAD_COUNT
canPaginate.setValue { itemsCountSufficient }
friendsUseCase.getFriends(
order = screenState.value.orderType,
count = LOAD_COUNT,
offset = offset
).listenValue(viewModelScope) { state ->
state.processState(
error = ::handleError,
success = { response ->
val itemsCountSufficient = response.size == LOAD_COUNT
canPaginate.setValue { itemsCountSufficient }
val paginationExhausted = !itemsCountSufficient &&
screenState.value.friends.size >= LOAD_COUNT
val paginationExhausted = !itemsCountSufficient &&
screenState.value.friends.size >= LOAD_COUNT
imagesToPreload.setValue {
response.mapNotNull(VkUser::photo100)
imagesToPreload.setValue {
response.mapNotNull(VkUser::photo100)
}
friendsUseCase.storeUsers(response)
val loadedFriends = response.map {
it.asPresentation(userSettings.useContactNames.value)
}
val newState = screenState.value.copy(
isPaginationExhausted = paginationExhausted
)
if (offset == 0) {
friends.emit(response)
screenState.setValue {
newState.copy(friends = loadedFriends)
}
friendsUseCase.storeUsers(response)
val loadedFriends = response.map {
it.asPresentation(userSettings.useContactNames.value)
}
val newState = screenState.value.copy(
isPaginationExhausted = paginationExhausted
)
if (offset == 0) {
friends.emit(response)
screenState.setValue {
newState.copy(friends = loadedFriends)
}
} else {
friends.emit(friends.value.plus(response))
screenState.setValue {
newState.copy(friends = newState.friends.plus(loadedFriends))
}
} else {
friends.emit(friends.value.plus(response))
screenState.setValue {
newState.copy(friends = newState.friends.plus(loadedFriends))
}
}
)
screenState.setValue { old ->
old.copy(
isLoading = offset == 0 && state.isLoading(),
isPaginating = offset > 0 && state.isLoading()
)
}
)
screenState.setValue { old ->
old.copy(
isLoading = offset == 0 && state.isLoading(),
isPaginating = offset > 0 && state.isLoading()
)
}
}
}
}
@@ -11,6 +11,7 @@ data class FriendsScreenState(
val isPaginationExhausted: Boolean,
val scrollIndex: Int,
val scrollOffset: Int,
val orderType: String,
) {
companion object {
@@ -20,7 +21,8 @@ data class FriendsScreenState(
isPaginating = false,
isPaginationExhausted = false,
scrollIndex = 0,
scrollOffset = 0
scrollOffset = 0,
orderType = "hints"
)
}
}
@@ -45,6 +45,7 @@ import org.koin.androidx.compose.koinViewModel
@Composable
fun FriendsScreen(
modifier: Modifier = Modifier,
orderType: String,
padding: PaddingValues,
tabIndex: Int,
onSessionExpiredLogOutButtonClicked: () -> Unit = {},
@@ -60,6 +61,10 @@ fun FriendsScreen(
koinViewModel<OnlineFriendsViewModelImpl>()
}
LaunchedEffect(orderType) {
viewModel.onOrderTypeChanged(orderType)
}
val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.collectAsStateWithLifecycle()
@@ -13,6 +13,8 @@ import androidx.compose.foundation.layout.statusBars
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold
@@ -32,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
@@ -40,9 +43,15 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.R
import dev.meloda.fast.ui.components.ActionInvokeDismiss
import dev.meloda.fast.ui.components.MaterialDialog
import dev.meloda.fast.ui.components.SelectionType
import dev.meloda.fast.ui.model.TabItem
import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig
import dev.meloda.fast.ui.util.ImmutableList
import dev.meloda.fast.ui.R as UiR
@OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@Composable
@@ -98,6 +107,44 @@ fun FriendsRoute(
)
}
var orderType: String by remember { mutableStateOf("hints") }
var showOrderDialog by remember { mutableStateOf(false) }
val orderItems = remember {
mapOf(
"hints" to "Priority",
"name" to "Name",
"random" to "Random",
"mobile" to "Mobile",
"smart" to "Smart"
)
}
var selectedIndex by remember {
mutableIntStateOf(0)
}
if (showOrderDialog) {
MaterialDialog(
onDismissRequest = { showOrderDialog = false },
confirmText = stringResource(R.string.ok),
confirmAction = {
orderType =
orderItems.keys.toCollection(mutableListOf())[selectedIndex]
},
cancelText = stringResource(R.string.cancel),
selectionType = SelectionType.Single,
items = ImmutableList.copyOf(orderItems.values),
preSelectedItems = ImmutableList.of(selectedIndex),
onItemClick = {
selectedIndex = it
},
title = "Order type",
actionInvokeDismiss = ActionInvokeDismiss.Always
)
}
Scaffold(
modifier = Modifier.fillMaxSize(),
contentWindowInsets = WindowInsets.statusBars,
@@ -129,7 +176,19 @@ fun FriendsRoute(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent
),
modifier = Modifier.fillMaxWidth()
modifier = Modifier.fillMaxWidth(),
actions = {
IconButton(
onClick = {
showOrderDialog = true
}
) {
Icon(
painter = painterResource(UiR.drawable.round_filter_list_24),
contentDescription = null
)
}
}
)
PrimaryTabRow(
selectedTabIndex = selectedTabIndex,
@@ -175,6 +234,7 @@ fun FriendsRoute(
modifier = Modifier.fillMaxSize(),
) { index ->
FriendsScreen(
orderType = orderType,
padding = padding,
tabIndex = index,
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },