forked from melod1n/fast-messenger
feat: Add ordering functionality for friends list
This commit is contained in:
@@ -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,52 +146,55 @@ 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,
|
||||||
state.processState(
|
count = LOAD_COUNT,
|
||||||
error = ::handleError,
|
offset = offset
|
||||||
success = { response ->
|
).listenValue(viewModelScope) { state ->
|
||||||
val itemsCountSufficient = response.size == LOAD_COUNT
|
state.processState(
|
||||||
canPaginate.setValue { itemsCountSufficient }
|
error = ::handleError,
|
||||||
|
success = { response ->
|
||||||
|
val itemsCountSufficient = response.size == LOAD_COUNT
|
||||||
|
canPaginate.setValue { itemsCountSufficient }
|
||||||
|
|
||||||
val paginationExhausted = !itemsCountSufficient &&
|
val paginationExhausted = !itemsCountSufficient &&
|
||||||
screenState.value.friends.size >= LOAD_COUNT
|
screenState.value.friends.size >= LOAD_COUNT
|
||||||
|
|
||||||
imagesToPreload.setValue {
|
imagesToPreload.setValue {
|
||||||
response.mapNotNull(VkUser::photo100)
|
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)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
friendsUseCase.storeUsers(response)
|
friends.emit(friends.value.plus(response))
|
||||||
|
screenState.setValue {
|
||||||
val loadedFriends = response.map {
|
newState.copy(friends = newState.friends.plus(loadedFriends))
|
||||||
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))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
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 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()
|
||||||
|
|||||||
+61
-1
@@ -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) },
|
||||||
|
|||||||
Reference in New Issue
Block a user