From dabbe4033138c67c41477365ba4b8045c0bdaeae Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Sun, 8 Oct 2023 01:03:02 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=B3=E6=B3=A8=20/=20=E5=8F=96?= =?UTF-8?q?=E5=85=B3=E7=94=A8=E6=88=B7=20&=20=E7=BC=96=E8=BE=91=E8=B5=84?= =?UTF-8?q?=E6=96=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../post/ui/page/user/UserProfilePage.kt | 118 +++++++++++------ .../post/ui/page/user/UserProfileViewModel.kt | 125 ++++++++++++++++++ 2 files changed, 206 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfilePage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfilePage.kt index 4103e5b8..62593687 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfilePage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfilePage.kt @@ -32,6 +32,7 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.ContentCopy import androidx.compose.material.icons.rounded.Add +import androidx.compose.material.icons.rounded.Edit import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.LaunchedEffect @@ -67,8 +68,10 @@ import com.huanchengfly.tieba.post.arch.collectPartialAsState import com.huanchengfly.tieba.post.arch.emitGlobalEvent import com.huanchengfly.tieba.post.arch.getOrNull import com.huanchengfly.tieba.post.arch.pageViewModel +import com.huanchengfly.tieba.post.goToActivity import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme import com.huanchengfly.tieba.post.ui.page.ProvideNavigator +import com.huanchengfly.tieba.post.ui.page.editprofile.view.EditProfileActivity import com.huanchengfly.tieba.post.ui.page.user.likeforum.UserLikeForumPage import com.huanchengfly.tieba.post.ui.page.user.post.UserPostPage import com.huanchengfly.tieba.post.ui.widgets.Chip @@ -108,9 +111,14 @@ fun UserProfilePage( navigator: DestinationsNavigator, viewModel: UserProfileViewModel = pageViewModel(), ) { + val account = LocalAccount.current + val context = LocalContext.current val coroutineScope = rememberCoroutineScope() val density = LocalDensity.current - val account = LocalAccount.current + + val isSelf = remember(account, uid) { + account?.uid == uid.toString() + } LazyLoad(loaded = viewModel.initialized) { viewModel.send(UserProfileUiIntent.Refresh(uid)) @@ -129,6 +137,10 @@ fun UserProfilePage( prop1 = UserProfileUiState::user, initial = null ) + val disableButton by viewModel.uiState.collectPartialAsState( + prop1 = UserProfileUiState::disableButton, + initial = false + ) val isError by remember { derivedStateOf { error != null } @@ -260,21 +272,21 @@ fun UserProfilePage( ) } ), - UserProfilePageData( - id = "posts", - title = { - stringResource( - id = R.string.title_profile_posts_tab, - it.get { post_num }.getShortNumString() - ) - }, - content = { - UserPostPage( - uid = uid, - isThread = false - ) - } - ).takeIf { account?.uid == uid.toString() }, +// UserProfilePageData( +// id = "posts", +// title = { +// stringResource( +// id = R.string.title_profile_posts_tab, +// it.get { post_num }.getShortNumString() +// ) +// }, +// content = { +// UserPostPage( +// uid = uid, +// isThread = false +// ) +// } +// ).takeIf { account?.uid == uid.toString() }, UserProfilePageData( id = "concern_forums", title = { @@ -314,7 +326,31 @@ fun UserProfilePage( ) .onSizeChanged { headerHeight = it.height.toFloat() + }, + showBtn = account != null, + isSelf = isSelf, + onBtnClick = { + if (disableButton || account == null) { + return@UserProfileDetail } + if (isSelf) { + context.goToActivity() + } else if (holder.get { has_concerned } == 0) { + viewModel.send( + UserProfileUiIntent.Follow( + holder.get { portrait }, + account.tbs, + ) + ) + } else { + viewModel.send( + UserProfileUiIntent.Unfollow( + holder.get { portrait }, + account.tbs, + ) + ) + } + } ) } @@ -434,6 +470,9 @@ private fun ToolbarUserTitle( private fun UserProfileDetail( user: ImmutableHolder, modifier: Modifier = Modifier, + showBtn: Boolean = true, + isSelf: Boolean = false, + onBtnClick: () -> Unit = {}, ) { Column( modifier = modifier, @@ -451,28 +490,33 @@ private fun UserProfileDetail( modifier = Modifier.padding(bottom = 16.dp) ) Spacer(modifier = Modifier.weight(1f)) - Button( - onClick = { /*TODO*/ }, - colors = if (user.get { has_concerned } == 0) { - ButtonDefaults.buttonColors() - } else { - ButtonDefaults.outlinedButtonColors() - }, - border = if (user.get { has_concerned } == 0) { - null - } else { - ButtonDefaults.outlinedBorder - }, - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - if (user.get { has_concerned } == 0) { - Icon(imageVector = Icons.Rounded.Add, contentDescription = null) - Text(text = stringResource(id = R.string.button_follow)) + if (showBtn) { + Button( + onClick = onBtnClick, + colors = if (user.get { has_concerned } == 0 || isSelf) { + ButtonDefaults.buttonColors() } else { - Text(text = stringResource(id = R.string.button_unfollow)) + ButtonDefaults.outlinedButtonColors() + }, + border = if (user.get { has_concerned } == 0 || isSelf) { + null + } else { + ButtonDefaults.outlinedBorder + }, + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + if (isSelf) { + Icon(imageVector = Icons.Rounded.Edit, contentDescription = null) + Text(text = stringResource(id = R.string.menu_edit_info)) + } else if (user.get { has_concerned } == 0) { + Icon(imageVector = Icons.Rounded.Add, contentDescription = null) + Text(text = stringResource(id = R.string.button_follow)) + } else { + Text(text = stringResource(id = R.string.button_unfollow)) + } } } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfileViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfileViewModel.kt index 50d208ec..c285a916 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfileViewModel.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/UserProfileViewModel.kt @@ -1,15 +1,22 @@ package com.huanchengfly.tieba.post.ui.page.user +import com.huanchengfly.tieba.post.App +import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.api.TiebaApi +import com.huanchengfly.tieba.post.api.models.CommonResponse +import com.huanchengfly.tieba.post.api.models.FollowBean import com.huanchengfly.tieba.post.api.models.protos.User import com.huanchengfly.tieba.post.api.models.protos.profile.ProfileResponse +import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage import com.huanchengfly.tieba.post.arch.BaseViewModel +import com.huanchengfly.tieba.post.arch.CommonUiEvent import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.PartialChange import com.huanchengfly.tieba.post.arch.PartialChangeProducer import com.huanchengfly.tieba.post.arch.UiEvent import com.huanchengfly.tieba.post.arch.UiIntent import com.huanchengfly.tieba.post.arch.UiState +import com.huanchengfly.tieba.post.arch.getOrNull import com.huanchengfly.tieba.post.arch.wrapImmutable import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -30,6 +37,25 @@ class UserProfileViewModel @Inject constructor() : override fun createPartialChangeProducer(): PartialChangeProducer = UserProfilePartialChangeProducer + override fun dispatchEvent(partialChange: UserProfilePartialChange): UiEvent? = + when (partialChange) { + is UserProfilePartialChange.Follow.Failure -> CommonUiEvent.Toast( + App.INSTANCE.getString( + R.string.toast_like_failed, + partialChange.error.getErrorMessage() + ) + ) + + is UserProfilePartialChange.Unfollow.Failure -> CommonUiEvent.Toast( + App.INSTANCE.getString( + R.string.toast_unlike_failed, + partialChange.error.getErrorMessage() + ) + ) + + else -> null + } + private object UserProfilePartialChangeProducer : PartialChangeProducer { @OptIn(ExperimentalCoroutinesApi::class) @@ -37,6 +63,10 @@ class UserProfileViewModel @Inject constructor() : merge( intentFlow.filterIsInstance() .flatMapConcat { it.producePartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, ) private fun UserProfileUiIntent.Refresh.producePartialChange(): Flow = @@ -49,6 +79,24 @@ class UserProfileViewModel @Inject constructor() : } .onStart { emit(UserProfilePartialChange.Refresh.Start) } .catch { emit(UserProfilePartialChange.Refresh.Failure(it)) } + + private fun UserProfileUiIntent.Follow.producePartialChange(): Flow = + TiebaApi.getInstance() + .followFlow(portrait, tbs) + .map { + UserProfilePartialChange.Follow.Success + } + .onStart { emit(UserProfilePartialChange.Follow.Start) } + .catch { emit(UserProfilePartialChange.Follow.Failure(it)) } + + private fun UserProfileUiIntent.Unfollow.producePartialChange(): Flow = + TiebaApi.getInstance() + .unfollowFlow(portrait, tbs) + .map { + UserProfilePartialChange.Unfollow.Success + } + .onStart { emit(UserProfilePartialChange.Unfollow.Start) } + .catch { emit(UserProfilePartialChange.Unfollow.Failure(it)) } } } @@ -56,6 +104,16 @@ sealed interface UserProfileUiIntent : UiIntent { data class Refresh( val uid: Long, ) : UserProfileUiIntent + + data class Follow( + val portrait: String, + val tbs: String, + ) : UserProfileUiIntent + + data class Unfollow( + val portrait: String, + val tbs: String, + ) : UserProfileUiIntent } sealed interface UserProfilePartialChange : PartialChange { @@ -87,11 +145,78 @@ sealed interface UserProfilePartialChange : PartialChange { val error: Throwable, ) : Refresh() } + + sealed class Follow : UserProfilePartialChange { + override fun reduce(oldState: UserProfileUiState): UserProfileUiState = when (this) { + is Start -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 1 + )?.wrapImmutable(), + disableButton = true + ) + + is Success -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 1 + )?.wrapImmutable(), + disableButton = false + ) + + is Failure -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 0 + )?.wrapImmutable(), + disableButton = false + ) + } + + data object Start : Follow() + + data object Success : Follow() + + data class Failure( + val error: Throwable, + ) : Follow() + } + + sealed class Unfollow : UserProfilePartialChange { + override fun reduce(oldState: UserProfileUiState): UserProfileUiState = when (this) { + is Start -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 0 + )?.wrapImmutable(), + disableButton = true + ) + + is Success -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 0 + )?.wrapImmutable(), + disableButton = false + ) + + is Failure -> oldState.copy( + user = oldState.user.getOrNull()?.copy( + has_concerned = 1 + )?.wrapImmutable(), + disableButton = false + ) + } + + data object Start : Unfollow() + + data object Success : Unfollow() + + data class Failure( + val error: Throwable, + ) : Unfollow() + } } data class UserProfileUiState( val isRefreshing: Boolean = false, val error: ImmutableHolder? = null, + val disableButton: Boolean = false, val user: ImmutableHolder? = null, ) : UiState \ No newline at end of file