Refactor: Enhance conversations and friends features

-   In `ConversationsScreen`, removed `isNeedToScrollToTop` and `onScrolledToTop`, and refactored toolbar container color logic. Added `NoItemsView` for empty conversation lists.
-   In `MainGraph`, added `onMessageClicked` for navigation to message history.
-   In `ApiEvent`, introduced `parseOrNull` for handling unknown event types.
-   In `ConversationsViewModel`, removed `scrollToTop` logic and refactored error handling.
-   In `FriendsViewModel`, refactored error handling and introduced `onErrorConsumed` and `handleError`.
-   In `FriendItem`, added an icon button to initiate sending a message to a friend.
-   In `strings.xml`, added or updated strings for session expiration, log out, refreshing, and empty friend lists.
-   In `RootScreen`, added `onMessageClicked` for navigating to messages.
-   In `FriendsList`, added `onMessageClicked` for handling message clicks.
-   In `MainScreen`, removed unused `MutableSharedFlow`.
-   In `FriendsScreen`, added support for showing errors, added `onMessageClicked`, and replaced `hazeChild` with `hazeEffect` and `hazeSource`.
-   In `FriendsNavigation`, added `onMessageClicked` for handling message clicks.
-   In `ConversationsNavigation`, removed the unused `scrollToTopFlow` parameter.
-   In `ErrorView`, added text alignment.
-   In `NoItemsView`, added support for a button and custom text.
-   In `LongPollUpdatesParser`, replaced try-catch with `parseOrNull`.
This commit is contained in:
2025-03-21 12:43:22 +03:00
parent 1a78a51017
commit 36a119ffa9
17 changed files with 173 additions and 141 deletions
@@ -68,6 +68,7 @@ class FriendsViewModelImpl(
}
override fun onRefresh() {
onErrorConsumed()
loadFriends(offset = 0)
}
@@ -99,32 +100,12 @@ class FriendsViewModelImpl(
friendsUseCase.getOnlineFriends(null, null)
.listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
if (error is State.Error.ApiError) {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> Unit
}
}
},
error = ::handleError,
success = { userIds ->
loadUsersByIdsUseCase(userIds = userIds)
.listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
if (error is State.Error.ApiError) {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> Unit
}
}
},
error = ::handleError,
success = { onlineFriends ->
screenState.setValue { old ->
old.copy(
@@ -142,17 +123,7 @@ class FriendsViewModelImpl(
friendsUseCase.getFriends(count = LOAD_COUNT, offset = offset)
.listenValue(viewModelScope) { state ->
state.processState(
error = { error ->
if (error is State.Error.ApiError) {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> Unit
}
}
},
error = ::handleError,
success = { response ->
val itemsCountSufficient = response.size == LOAD_COUNT
canPaginate.setValue { itemsCountSufficient }
@@ -197,6 +168,40 @@ class FriendsViewModelImpl(
}
}
private fun handleError(error: State.Error) {
when (error) {
is State.Error.ApiError -> {
when (error.errorCode) {
VkErrorCode.USER_AUTHORIZATION_FAILED -> {
baseError.setValue { BaseError.SessionExpired }
}
else -> {
baseError.setValue {
BaseError.SimpleError(message = error.errorMessage)
}
}
}
}
State.Error.ConnectionError -> {
baseError.setValue {
BaseError.SimpleError(message = "Connection error")
}
}
State.Error.InternalError -> {
baseError.setValue {
BaseError.SimpleError(message = "Internal error")
}
}
State.Error.UnknownError -> {
baseError.setValue {
BaseError.SimpleError(message = "Unknown error")
}
}
else -> Unit
}
}
private fun updateFriendsNames(useContactNames: Boolean) {
val friends = friends.value
if (friends.isEmpty()) return
@@ -16,7 +16,8 @@ object Friends
fun NavGraphBuilder.friendsScreen(
onError: (BaseError) -> Unit,
navController: NavController,
onPhotoClicked: (url: String) -> Unit
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit
) {
composable<Friends> {
val viewModel: FriendsViewModel =
@@ -25,7 +26,8 @@ fun NavGraphBuilder.friendsScreen(
FriendsRoute(
onError = onError,
viewModel = viewModel,
onPhotoClicked = onPhotoClicked
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked
)
}
}
@@ -12,6 +12,10 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.MailOutline
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -31,7 +35,8 @@ fun FriendItem(
modifier: Modifier = Modifier,
friend: UiFriend,
maxLines: Int,
onPhotoClicked: (url: String) -> Unit
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit
) {
Row(
modifier = modifier.fillMaxWidth(),
@@ -92,9 +97,24 @@ fun FriendItem(
text = friend.title,
minLines = 1,
maxLines = maxLines,
style = MaterialTheme.typography.headlineSmall.copy(fontSize = 20.sp)
style = MaterialTheme.typography.headlineSmall.copy(fontSize = 20.sp),
modifier = Modifier.weight(1f)
)
Spacer(modifier = Modifier.width(16.dp))
IconButton(
onClick = {
onMessageClicked(friend.userId)
}
) {
Icon(
imageVector = Icons.Rounded.MailOutline,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary
)
}
Spacer(modifier = Modifier.width(16.dp))
}
}
@@ -37,6 +37,7 @@ fun FriendsList(
maxLines: Int,
padding: PaddingValues,
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit,
setCanScrollBackward: (Boolean) -> Unit
) {
LaunchedEffect(listState) {
@@ -66,7 +67,8 @@ fun FriendsList(
FriendItem(
friend = friend,
maxLines = maxLines,
onPhotoClicked = onPhotoClicked
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked
)
Spacer(modifier = Modifier.height(16.dp))
@@ -48,8 +48,8 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.imageLoader
import coil.request.ImageRequest
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.hazeEffect
import dev.chrisbanes.haze.hazeSource
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import dev.meloda.fast.friends.FriendsViewModel
@@ -72,6 +72,7 @@ import dev.meloda.fast.ui.R as UiR
fun FriendsRoute(
onError: (BaseError) -> Unit,
onPhotoClicked: (url: String) -> Unit,
onMessageClicked: (userId: Int) -> Unit,
viewModel: FriendsViewModel = koinViewModel<FriendsViewModelImpl>()
) {
val context = LocalContext.current
@@ -99,11 +100,12 @@ fun FriendsRoute(
onPaginationConditionsMet = viewModel::onPaginationConditionsMet,
onRefresh = viewModel::onRefresh,
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked,
setSelectedTabIndex = viewModel::onTabSelected,
setScrollIndex = viewModel::setScrollIndex,
setScrollOffset = viewModel::setScrollOffset,
setScrollIndexOnline = viewModel::setScrollIndexOnline,
setScrollOffsetOnline = viewModel::setScrollOffsetOnline,
setScrollOffsetOnline = viewModel::setScrollOffsetOnline
)
}
@@ -120,11 +122,12 @@ fun FriendsScreen(
onPaginationConditionsMet: () -> Unit = {},
onRefresh: () -> Unit = {},
onPhotoClicked: (url: String) -> Unit = {},
onMessageClicked: (userId: Int) -> Unit = {},
setSelectedTabIndex: (Int) -> Unit = {},
setScrollIndex: (Int) -> Unit = {},
setScrollOffset: (Int) -> Unit = {},
setScrollIndexOnline: (Int) -> Unit = {},
setScrollOffsetOnline: (Int) -> Unit = {},
setScrollOffsetOnline: (Int) -> Unit = {}
) {
val currentTheme = LocalThemeConfig.current
@@ -231,7 +234,7 @@ fun FriendsScreen(
modifier = Modifier
.then(
if (currentTheme.enableBlur) {
Modifier.hazeChild(
Modifier.hazeEffect(
state = hazeState,
style = HazeMaterials.thick()
)
@@ -281,12 +284,24 @@ fun FriendsScreen(
}
) { padding ->
when {
baseError is BaseError.SessionExpired -> {
ErrorView(
text = "Session expired",
buttonText = "Log out",
onButtonClick = onSessionExpiredLogOutButtonClicked
)
baseError != null -> {
when (baseError) {
is BaseError.SessionExpired -> {
ErrorView(
text = stringResource(UiR.string.session_expired),
buttonText = stringResource(UiR.string.action_log_out),
onButtonClick = onSessionExpiredLogOutButtonClicked
)
}
is BaseError.SimpleError -> {
ErrorView(
text = baseError.message,
buttonText = stringResource(UiR.string.try_again),
onButtonClick = onRefresh
)
}
}
}
screenState.isLoading && screenState.friends.isEmpty() -> FullScreenLoader()
@@ -333,15 +348,17 @@ fun FriendsScreen(
)
}
) {
val friendsToDisplay = if (index == 0) {
screenState.friends
} else {
screenState.onlineFriends
val friendsToDisplay = remember(index) {
if (index == 0) {
screenState.friends
} else {
screenState.onlineFriends
}
}
FriendsList(
modifier = if (currentTheme.enableBlur) {
Modifier.haze(state = hazeState)
Modifier.hazeSource(state = hazeState)
} else {
Modifier
}.fillMaxSize(),
@@ -351,6 +368,7 @@ fun FriendsScreen(
maxLines = maxLines,
padding = padding,
onPhotoClicked = onPhotoClicked,
onMessageClicked = onMessageClicked,
setCanScrollBackward = { can ->
canScrollBackward = can
}
@@ -358,10 +376,9 @@ fun FriendsScreen(
if (friendsToDisplay.isEmpty()) {
NoItemsView(
modifier = Modifier
.padding(padding.calculateTopPadding())
.padding(top = 16.dp),
customText = "No${if (index == 1) " online" else ""} friends :("
customText = if (index == 1) stringResource(UiR.string.no_online_friends) else null,
buttonText = stringResource(UiR.string.action_refresh),
onButtonClick = onRefresh
)
}
}