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 { interface FriendsRepository {
suspend fun getAllFriends( suspend fun getAllFriends(
order: String,
count: Int?, count: Int?,
offset: Int? offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain> ): ApiResult<FriendsInfo, RestApiErrorDomain>
suspend fun getFriends( suspend fun getFriends(
order: String,
count: Int?, count: Int?,
offset: Int? offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain> ): ApiResult<List<VkUser>, RestApiErrorDomain>
@@ -25,10 +25,11 @@ class FriendsRepositoryImpl(
) : FriendsRepository { ) : FriendsRepository {
override suspend fun getAllFriends( override suspend fun getAllFriends(
order: String,
count: Int?, count: Int?,
offset: Int? offset: Int?
): ApiResult<FriendsInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) { ): ApiResult<FriendsInfo, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val friends = async { getFriends(count, offset) }.await() val friends = async { getFriends(order, count, offset) }.await()
.successOrElse { failure -> .successOrElse { failure ->
return@withContext failure return@withContext failure
} }
@@ -42,11 +43,12 @@ class FriendsRepositoryImpl(
} }
override suspend fun getFriends( override suspend fun getFriends(
order: String,
count: Int?, count: Int?,
offset: Int? offset: Int?
): ApiResult<List<VkUser>, RestApiErrorDomain> = withContext(Dispatchers.IO) { ): ApiResult<List<VkUser>, RestApiErrorDomain> = withContext(Dispatchers.IO) {
val requestModel = GetFriendsRequest( val requestModel = GetFriendsRequest(
order = "hints", order = order,
count = count, count = count,
offset = offset, offset = offset,
fields = VkConstants.USER_FIELDS fields = VkConstants.USER_FIELDS
@@ -8,11 +8,13 @@ import kotlinx.coroutines.flow.Flow
interface FriendsUseCase { interface FriendsUseCase {
fun getAllFriends( fun getAllFriends(
order: String = "hints",
count: Int?, count: Int?,
offset: Int? offset: Int?
): Flow<State<FriendsInfo>> ): Flow<State<FriendsInfo>>
fun getFriends( fun getFriends(
order: String = "hints",
count: Int?, count: Int?,
offset: Int? offset: Int?
): Flow<State<List<VkUser>>> ): Flow<State<List<VkUser>>>
@@ -11,19 +11,26 @@ import kotlinx.coroutines.flow.flow
class FriendsUseCaseImpl(private val repository: FriendsRepository) : class FriendsUseCaseImpl(private val repository: FriendsRepository) :
FriendsUseCase { 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) emit(State.Loading)
val newState = repository.getAllFriends(count, offset).mapToState() val newState = repository.getAllFriends(order, count, offset).mapToState()
emit(newState) emit(newState)
} }
override fun getFriends( override fun getFriends(
count: Int?, offset: Int? order: String,
count: Int?,
offset: Int?
): Flow<State<List<VkUser>>> = flow { ): Flow<State<List<VkUser>>> = flow {
emit(State.Loading) emit(State.Loading)
val newState = repository.getFriends(count, offset).mapToState() val newState = repository.getFriends(
order = order,
count = count,
offset = offset
).mapToState()
emit(newState) 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 setScrollIndex(index: Int)
fun setScrollOffset(offset: Int) fun setScrollOffset(offset: Int)
fun onOrderTypeChanged(newOrderType: String)
} }
abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel { abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel {
@@ -69,6 +71,12 @@ abstract class BaseFriendsViewModelImpl : ViewModel(), FriendsViewModel {
screenState.setValue { old -> old.copy(scrollOffset = offset) } 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) abstract fun loadFriends(offset: Int = currentOffset.value)
protected fun handleError(error: State.Error) { protected fun handleError(error: State.Error) {
@@ -138,8 +146,11 @@ class FriendsViewModelImpl(
} }
override fun loadFriends(offset: Int) { override fun loadFriends(offset: Int) {
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset) friendsUseCase.getFriends(
.listenValue(viewModelScope) { state -> order = screenState.value.orderType,
count = LOAD_COUNT,
offset = offset
).listenValue(viewModelScope) { state ->
state.processState( state.processState(
error = ::handleError, error = ::handleError,
success = { response -> success = { response ->
@@ -11,6 +11,7 @@ data class FriendsScreenState(
val isPaginationExhausted: Boolean, val isPaginationExhausted: Boolean,
val scrollIndex: Int, val scrollIndex: Int,
val scrollOffset: Int, val scrollOffset: Int,
val orderType: String,
) { ) {
companion object { companion object {
@@ -20,7 +21,8 @@ data class FriendsScreenState(
isPaginating = false, isPaginating = false,
isPaginationExhausted = false, isPaginationExhausted = false,
scrollIndex = 0, scrollIndex = 0,
scrollOffset = 0 scrollOffset = 0,
orderType = "hints"
) )
} }
} }
@@ -45,6 +45,7 @@ import org.koin.androidx.compose.koinViewModel
@Composable @Composable
fun FriendsScreen( fun FriendsScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
orderType: String,
padding: PaddingValues, padding: PaddingValues,
tabIndex: Int, tabIndex: Int,
onSessionExpiredLogOutButtonClicked: () -> Unit = {}, onSessionExpiredLogOutButtonClicked: () -> Unit = {},
@@ -60,6 +61,10 @@ fun FriendsScreen(
koinViewModel<OnlineFriendsViewModelImpl>() koinViewModel<OnlineFriendsViewModelImpl>()
} }
LaunchedEffect(orderType) {
viewModel.onOrderTypeChanged(orderType)
}
val screenState by viewModel.screenState.collectAsStateWithLifecycle() val screenState by viewModel.screenState.collectAsStateWithLifecycle()
val baseError by viewModel.baseError.collectAsStateWithLifecycle() val baseError by viewModel.baseError.collectAsStateWithLifecycle()
val canPaginate by viewModel.canPaginate.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.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PrimaryTabRow import androidx.compose.material3.PrimaryTabRow
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
@@ -32,6 +34,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -40,9 +43,15 @@ import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.model.BaseError import dev.meloda.fast.model.BaseError
import dev.meloda.fast.ui.R 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.model.TabItem
import dev.meloda.fast.ui.theme.LocalHazeState import dev.meloda.fast.ui.theme.LocalHazeState
import dev.meloda.fast.ui.theme.LocalThemeConfig 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) @OptIn(ExperimentalMaterial3Api::class, ExperimentalHazeMaterialsApi::class)
@Composable @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( Scaffold(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
contentWindowInsets = WindowInsets.statusBars, contentWindowInsets = WindowInsets.statusBars,
@@ -129,7 +176,19 @@ fun FriendsRoute(
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent 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( PrimaryTabRow(
selectedTabIndex = selectedTabIndex, selectedTabIndex = selectedTabIndex,
@@ -175,6 +234,7 @@ fun FriendsRoute(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) { index -> ) { index ->
FriendsScreen( FriendsScreen(
orderType = orderType,
padding = padding, padding = padding,
tabIndex = index, tabIndex = index,
onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) }, onSessionExpiredLogOutButtonClicked = { onError(BaseError.SessionExpired) },