feat: 用户资料界面 Pad 适配

This commit is contained in:
HuanCheng65 2023-10-08 19:01:44 +08:00
parent bb80c7055d
commit cfdfe9597b
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
6 changed files with 542 additions and 279 deletions

View File

@ -201,7 +201,7 @@ private fun ThreadList(
}
MyLazyColumn(
state = state,
// horizontalAlignment = Alignment.CenterHorizontally,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth(),
contentPadding = WindowInsets.navigationBars.asPaddingValues()
) {

View File

@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -68,6 +69,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEachIndexed
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.User
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
import com.huanchengfly.tieba.post.arch.GlobalEvent
import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.arch.collectPartialAsState
@ -78,6 +80,7 @@ import com.huanchengfly.tieba.post.goToActivity
import com.huanchengfly.tieba.post.models.database.Block
import com.huanchengfly.tieba.post.toastShort
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClass
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
@ -114,7 +117,6 @@ import kotlin.math.absoluteValue
import kotlin.math.max
import kotlin.math.min
@OptIn(ExperimentalFoundationApi::class)
@Destination
@Composable
fun UserProfilePage(
@ -123,9 +125,6 @@ fun UserProfilePage(
viewModel: UserProfileViewModel = pageViewModel(),
) {
val account = LocalAccount.current
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
val isSelf = remember(account, uid) {
account?.uid == uid.toString()
@ -160,21 +159,6 @@ fun UserProfilePage(
derivedStateOf { user == null }
}
var heightOffset by rememberSaveable { mutableFloatStateOf(0f) }
var headerHeight by rememberSaveable {
mutableFloatStateOf(
with(density) {
(96.dp + 16.dp).toPx()
}
)
}
val isShowHeaderArea by remember {
derivedStateOf {
heightOffset.absoluteValue < headerHeight
}
}
ProvideNavigator(navigator = navigator) {
StateScreen(
modifier = Modifier.fillMaxSize(),
@ -184,24 +168,93 @@ fun UserProfilePage(
onReload = { viewModel.send(UserProfileUiIntent.Refresh(uid)) },
errorScreen = { ErrorScreen(error = error.getOrNull()) }
) {
MyScaffold(
topBar = {
user?.let {
UserProfileContent(
user = it,
showActionBtn = account != null,
disableButton = disableButton,
isSelf = isSelf,
onBack = { navigator.navigateUp() },
onFollow = {
viewModel.send(
UserProfileUiIntent.Follow(
it.get { portrait },
account!!.tbs,
)
)
},
onUnfollow = {
viewModel.send(
UserProfileUiIntent.Unfollow(
it.get { portrait },
account!!.tbs,
)
)
}
)
}
}
}
}
@Composable
private fun UserProfileContent(
user: ImmutableHolder<User>,
showActionBtn: Boolean,
disableButton: Boolean,
isSelf: Boolean,
onBack: () -> Unit,
onFollow: () -> Unit,
onUnfollow: () -> Unit,
) {
when (LocalWindowSizeClass.current.widthSizeClass) {
WindowWidthSizeClass.Expanded -> {
UserProfileContentExpanded(
user = user,
showActionBtn = showActionBtn,
disableButton = disableButton,
isSelf = isSelf,
onBack = onBack,
onFollow = onFollow,
onUnfollow = onUnfollow
)
}
else -> {
UserProfileContentNormal(
user = user,
showActionBtn = showActionBtn,
disableButton = disableButton,
isSelf = isSelf,
onBack = onBack,
onFollow = onFollow,
onUnfollow = onUnfollow
)
}
}
}
@Composable
private fun UserProfileToolbar(
user: ImmutableHolder<User>,
isSelf: Boolean,
showTitle: Boolean,
onBack: () -> Unit,
) {
val context = LocalContext.current
Toolbar(
title = {
user?.let {
AnimatedVisibility(
visible = !isShowHeaderArea,
visible = showTitle,
enter = fadeIn(),
exit = fadeOut()
) {
ToolbarUserTitle(user = it)
}
ToolbarUserTitle(user = user)
}
},
navigationIcon = {
BackNavigationIcon {
navigator.navigateUp()
}
BackNavigationIcon(onBackPressed = onBack)
},
actions = {
user.takeUnless { isSelf }?.let {
@ -255,6 +308,47 @@ fun UserProfilePage(
}
},
)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun UserProfileContentNormal(
user: ImmutableHolder<User>,
showActionBtn: Boolean,
disableButton: Boolean,
isSelf: Boolean,
onBack: () -> Unit,
onFollow: () -> Unit,
onUnfollow: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
var heightOffset by rememberSaveable { mutableFloatStateOf(0f) }
var headerHeight by rememberSaveable {
mutableFloatStateOf(
with(density) {
(96.dp + 16.dp).toPx()
}
)
}
val isShowHeaderArea by remember {
derivedStateOf {
heightOffset.absoluteValue < headerHeight
}
}
MyScaffold(
topBar = {
UserProfileToolbar(
user = user,
isSelf = isSelf,
showTitle = !isShowHeaderArea,
onBack = onBack
)
}
) { paddingValues ->
var isFakeRefreshing by remember { mutableStateOf(false) }
@ -316,7 +410,6 @@ fun UserProfilePage(
.padding(paddingValues)
.nestedScroll(headerNestedScrollConnection)
) {
user?.let { holder ->
val pages = remember {
listOfNotNull(
UserProfilePageData(
@ -327,10 +420,11 @@ fun UserProfilePage(
it.get { thread_num }.getShortNumString()
)
},
content = {
content = { user, fluid ->
UserPostPage(
uid = it.get { id },
isThread = true
uid = user.get { id },
isThread = true,
fluid = fluid,
)
}
),
@ -342,10 +436,11 @@ fun UserProfilePage(
it.get { post_num }.getShortNumString()
)
},
content = {
content = { user, fluid ->
UserPostPage(
uid = uid,
isThread = false
uid = user.get { id },
isThread = false,
fluid = fluid,
)
}
).takeIf { isSelf },
@ -357,8 +452,11 @@ fun UserProfilePage(
it.get { my_like_num }.toString()
)
},
content = {
UserLikeForumPage(uid = it.get { id })
content = { user, fluid ->
UserLikeForumPage(
uid = user.get { id },
fluid = fluid,
)
}
),
).toImmutableList()
@ -378,52 +476,47 @@ fun UserProfilePage(
.height(containerHeight)
.clipToBounds()
) {
UserProfileDetail(
user = holder,
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.wrapContentHeight(
align = Alignment.Bottom,
unbounded = true
)
.onSizeChanged {
headerHeight = it.height.toFloat()
},
showBtn = account != null,
}
) {
UserProfileDetail(
user = user,
modifier = Modifier
.padding(16.dp)
.padding(top = 8.dp),
showBtn = showActionBtn,
isSelf = isSelf,
onBtnClick = {
if (disableButton || account == null) {
if (disableButton || !showActionBtn) {
return@UserProfileDetail
}
if (isSelf) {
context.goToActivity<EditProfileActivity>()
} else if (holder.get { has_concerned } == 0) {
viewModel.send(
UserProfileUiIntent.Follow(
holder.get { portrait },
account.tbs,
)
)
} else if (user.get { has_concerned } == 0) {
onFollow()
} else {
viewModel.send(
UserProfileUiIntent.Unfollow(
holder.get { portrait },
account.tbs,
)
)
onUnfollow()
}
},
onCopyIdClick = {
TiebaUtil.copyText(
context,
holder.get { id }.toString()
user.get { id }.toString()
)
}
)
}
}
UserProfileTabRow(
user = holder,
user = user,
pages = pages,
pagerState = pagerState,
// modifier = Modifier.padding(horizontal = 16.dp)
@ -434,12 +527,151 @@ fun UserProfilePage(
key = { pages[it].id },
modifier = Modifier.fillMaxSize()
) {
pages[it].content(holder)
pages[it].content(user, false)
}
}
}
}
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun UserProfileContentExpanded(
user: ImmutableHolder<User>,
showActionBtn: Boolean,
disableButton: Boolean,
isSelf: Boolean,
onBack: () -> Unit,
onFollow: () -> Unit,
onUnfollow: () -> Unit,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current
MyScaffold(
topBar = {
UserProfileToolbar(
user = user,
isSelf = isSelf,
showTitle = false,
onBack = onBack
)
}
) { paddingValues ->
ProvideContentColor(color = ExtendedTheme.colors.text) {
Box(
modifier = Modifier.fillMaxSize()
) {
Row(
modifier = Modifier
.padding(paddingValues)
.fillMaxHeight()
.fillMaxWidth(0.75f)
.align(Alignment.TopCenter),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
val pages = remember {
listOfNotNull(
UserProfilePageData(
id = "threads",
title = {
stringResource(
id = R.string.title_profile_threads_tab,
it.get { thread_num }.getShortNumString()
)
},
content = { user, expanded ->
UserPostPage(
uid = user.get { id },
isThread = true,
fluid = expanded,
enablePullRefresh = expanded,
)
}
),
UserProfilePageData(
id = "posts",
title = {
stringResource(
id = R.string.title_profile_posts_tab,
it.get { post_num }.getShortNumString()
)
},
content = { user, expanded ->
UserPostPage(
uid = user.get { id },
isThread = false,
fluid = expanded,
enablePullRefresh = expanded,
)
}
).takeIf { isSelf },
UserProfilePageData(
id = "concern_forums",
title = {
stringResource(
id = R.string.title_profile_concern_forums_tab,
it.get { my_like_num }.toString()
)
},
content = { user, expanded ->
UserLikeForumPage(
uid = user.get { id },
fluid = expanded,
enablePullRefresh = expanded,
)
}
),
).toImmutableList()
}
val pagerState = rememberPagerState { pages.size }
UserProfileDetail(
user = user,
modifier = Modifier
.weight(1f)
.align(Alignment.Top),
showBtn = showActionBtn,
isSelf = isSelf,
onBtnClick = {
if (disableButton || !showActionBtn) {
return@UserProfileDetail
}
if (isSelf) {
context.goToActivity<EditProfileActivity>()
} else if (user.get { has_concerned } == 0) {
onFollow()
} else {
onUnfollow()
}
},
onCopyIdClick = {
TiebaUtil.copyText(
context,
user.get { id }.toString()
)
}
)
Column(
modifier = Modifier.weight(2f)
) {
UserProfileTabRow(
user = user,
pages = pages,
pagerState = pagerState,
)
LazyLoadHorizontalPager(
state = pagerState,
key = { pages[it].id },
modifier = Modifier.weight(1f)
) {
pages[it].content(user, true)
}
}
}
}
}
}
@ -449,7 +681,7 @@ fun UserProfilePage(
data class UserProfilePageData(
val id: String,
val title: @Composable (ImmutableHolder<User>) -> String,
val content: @Composable (ImmutableHolder<User>) -> Unit,
val content: @Composable (ImmutableHolder<User>, Boolean) -> Unit,
)
@OptIn(ExperimentalFoundationApi::class)
@ -547,7 +779,6 @@ private fun UserProfileDetail(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
Spacer(modifier = Modifier.height(16.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
@ -736,7 +967,6 @@ private fun UserProfileDetail(
)
)
}
Spacer(modifier = Modifier.height(8.dp))
}
}

View File

@ -14,6 +14,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@ -33,6 +34,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
import com.huanchengfly.tieba.post.ui.widgets.compose.Container
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
@ -46,6 +48,8 @@ import kotlinx.collections.immutable.persistentListOf
@Composable
fun UserLikeForumPage(
uid: Long,
fluid: Boolean = false,
enablePullRefresh: Boolean = false,
viewModel: UserLikeForumViewModel = pageViewModel(),
) {
val navigator = LocalNavigator.current
@ -109,7 +113,10 @@ fun UserLikeForumPage(
val lazyListState = rememberLazyListState()
Box {
val pullRefreshModifier =
if (enablePullRefresh) Modifier.pullRefresh(pullRefreshState) else Modifier
Box(modifier = pullRefreshModifier) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = {
@ -120,6 +127,7 @@ fun UserLikeForumPage(
) {
UserLikeForumList(
data = forums,
fluid = fluid,
onClickForum = { forumBean ->
forumBean.name?.let {
navigator.navigate(ForumPageDestination(it))
@ -144,6 +152,7 @@ fun UserLikeForumPage(
private fun UserLikeForumList(
data: ImmutableList<UserLikeForumBean.ForumBean>,
onClickForum: (UserLikeForumBean.ForumBean) -> Unit,
fluid: Boolean = false,
lazyListState: LazyListState = rememberLazyListState(),
) {
MyLazyColumn(state = lazyListState) {
@ -151,6 +160,7 @@ private fun UserLikeForumList(
items = data,
key = { it.id }
) {
Container(fluid = fluid) {
UserLikeForumItem(
item = it,
onClick = {
@ -162,6 +172,7 @@ private fun UserLikeForumList(
)
}
}
}
}
@Composable

View File

@ -20,6 +20,7 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
@ -52,6 +53,7 @@ import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
import com.huanchengfly.tieba.post.ui.page.destinations.UserProfilePageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.Button
import com.huanchengfly.tieba.post.ui.widgets.compose.Card
import com.huanchengfly.tieba.post.ui.widgets.compose.Container
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCardPlaceholder
@ -70,6 +72,8 @@ import kotlinx.collections.immutable.persistentListOf
fun UserPostPage(
uid: Long,
isThread: Boolean = true,
fluid: Boolean = false,
enablePullRefresh: Boolean = false,
viewModel: UserPostViewModel = pageViewModel(key = if (isThread) "user_thread_$uid" else "user_post_$uid"),
) {
val navigator = LocalNavigator.current
@ -207,7 +211,10 @@ fun UserPostPage(
val lazyListState = rememberLazyListState()
Box {
val pullRefreshModifier =
if (enablePullRefresh) Modifier.pullRefresh(pullRefreshState) else Modifier
Box(modifier = pullRefreshModifier) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = {
@ -218,6 +225,7 @@ fun UserPostPage(
) {
UserPostList(
data = posts,
fluid = fluid,
lazyListState = lazyListState,
onClickItem = { threadId, postId, isSubPost ->
if (postId == null) {
@ -286,6 +294,7 @@ fun UserPostPage(
@Composable
private fun UserPostList(
data: ImmutableList<PostListItemData>,
fluid: Boolean = false,
lazyListState: LazyListState = rememberLazyListState(),
onClickItem: (threadId: Long, postId: Long?, isSubPost: Boolean) -> Unit = { _, _, _ -> },
onAgreeItem: (PostInfoList) -> Unit = {},
@ -301,6 +310,7 @@ private fun UserPostList(
"${it.data.get { thread_id }}_${it.data.get { post_id }}"
}
) { itemData ->
Container(fluid = fluid) {
UserPostItem(
post = itemData,
onClick = onClickItem,
@ -312,6 +322,7 @@ private fun UserPostList(
)
}
}
}
}
@Composable

View File

@ -12,16 +12,21 @@ import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClas
@Composable
fun Container(
modifier: Modifier = Modifier,
fluid: Boolean = false,
content: @Composable () -> Unit,
) {
val windowWidthSizeClass = LocalWindowSizeClass.current.widthSizeClass
val widthFraction = remember(windowWidthSizeClass) {
if (fluid) {
1f
} else {
when (windowWidthSizeClass) {
WindowWidthSizeClass.Medium -> 0.87f
WindowWidthSizeClass.Expanded -> 0.75f
else -> 1f
}
}
}
Box(
modifier = Modifier.fillMaxWidth(),

View File

@ -25,6 +25,10 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.TipScreen
val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_loading_paperplane))
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Box(modifier = Modifier.requiredWidthIn(max = 500.dp)) {
LottieAnimation(
composition = composition,
@ -34,6 +38,7 @@ val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = {
.aspectRatio(2f)
)
}
}
// CircularProgressIndicator(modifier = Modifier.size(48.dp), color = MaterialTheme.colors.primary)
}
@ -56,7 +61,8 @@ val DefaultEmptyScreen: @Composable StateScreenScope.() -> Unit = {
Text(text = stringResource(id = R.string.btn_refresh))
}
}
}
},
modifier = Modifier.fillMaxWidth(),
)
}