feat: 关注 / 取关用户 & 编辑资料
This commit is contained in:
parent
2d52b45ec1
commit
dabbe40331
|
|
@ -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,6 +326,30 @@ fun UserProfilePage(
|
|||
)
|
||||
.onSizeChanged {
|
||||
headerHeight = it.height.toFloat()
|
||||
},
|
||||
showBtn = account != null,
|
||||
isSelf = isSelf,
|
||||
onBtnClick = {
|
||||
if (disableButton || account == null) {
|
||||
return@UserProfileDetail
|
||||
}
|
||||
if (isSelf) {
|
||||
context.goToActivity<EditProfileActivity>()
|
||||
} 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<User>,
|
||||
modifier: Modifier = Modifier,
|
||||
showBtn: Boolean = true,
|
||||
isSelf: Boolean = false,
|
||||
onBtnClick: () -> Unit = {},
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
|
|
@ -451,14 +490,15 @@ private fun UserProfileDetail(
|
|||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (showBtn) {
|
||||
Button(
|
||||
onClick = { /*TODO*/ },
|
||||
colors = if (user.get { has_concerned } == 0) {
|
||||
onClick = onBtnClick,
|
||||
colors = if (user.get { has_concerned } == 0 || isSelf) {
|
||||
ButtonDefaults.buttonColors()
|
||||
} else {
|
||||
ButtonDefaults.outlinedButtonColors()
|
||||
},
|
||||
border = if (user.get { has_concerned } == 0) {
|
||||
border = if (user.get { has_concerned } == 0 || isSelf) {
|
||||
null
|
||||
} else {
|
||||
ButtonDefaults.outlinedBorder
|
||||
|
|
@ -468,7 +508,10 @@ private fun UserProfileDetail(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
if (user.get { has_concerned } == 0) {
|
||||
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 {
|
||||
|
|
@ -477,6 +520,7 @@ private fun UserProfileDetail(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = StringUtil.getUsernameAnnotatedString(
|
||||
LocalContext.current,
|
||||
|
|
|
|||
|
|
@ -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<UserProfileUiIntent, UserProfilePartialChange, UserProfileUiState> =
|
||||
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<UserProfileUiIntent, UserProfilePartialChange, UserProfileUiState> {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
|
@ -37,6 +63,10 @@ class UserProfileViewModel @Inject constructor() :
|
|||
merge(
|
||||
intentFlow.filterIsInstance<UserProfileUiIntent.Refresh>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<UserProfileUiIntent.Follow>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<UserProfileUiIntent.Unfollow>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
)
|
||||
|
||||
private fun UserProfileUiIntent.Refresh.producePartialChange(): Flow<UserProfilePartialChange.Refresh> =
|
||||
|
|
@ -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<UserProfilePartialChange.Follow> =
|
||||
TiebaApi.getInstance()
|
||||
.followFlow(portrait, tbs)
|
||||
.map<FollowBean, UserProfilePartialChange.Follow> {
|
||||
UserProfilePartialChange.Follow.Success
|
||||
}
|
||||
.onStart { emit(UserProfilePartialChange.Follow.Start) }
|
||||
.catch { emit(UserProfilePartialChange.Follow.Failure(it)) }
|
||||
|
||||
private fun UserProfileUiIntent.Unfollow.producePartialChange(): Flow<UserProfilePartialChange.Unfollow> =
|
||||
TiebaApi.getInstance()
|
||||
.unfollowFlow(portrait, tbs)
|
||||
.map<CommonResponse, UserProfilePartialChange.Unfollow> {
|
||||
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<UserProfileUiState> {
|
||||
|
|
@ -87,11 +145,78 @@ sealed interface UserProfilePartialChange : PartialChange<UserProfileUiState> {
|
|||
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<Throwable>? = null,
|
||||
|
||||
val disableButton: Boolean = false,
|
||||
val user: ImmutableHolder<User>? = null,
|
||||
) : UiState
|
||||
Loading…
Reference in New Issue