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( MyLazyColumn(
state = state, state = state,
// horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
contentPadding = WindowInsets.navigationBars.asPaddingValues() 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.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -68,6 +69,7 @@ import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachIndexed
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.User 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.GlobalEvent
import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.arch.collectPartialAsState 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.models.database.Block
import com.huanchengfly.tieba.post.toastShort import com.huanchengfly.tieba.post.toastShort
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme 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.ProvideNavigator
import com.huanchengfly.tieba.post.ui.page.editprofile.view.EditProfileActivity 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.likeforum.UserLikeForumPage
@ -114,7 +117,6 @@ import kotlin.math.absoluteValue
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@OptIn(ExperimentalFoundationApi::class)
@Destination @Destination
@Composable @Composable
fun UserProfilePage( fun UserProfilePage(
@ -123,9 +125,6 @@ fun UserProfilePage(
viewModel: UserProfileViewModel = pageViewModel(), viewModel: UserProfileViewModel = pageViewModel(),
) { ) {
val account = LocalAccount.current val account = LocalAccount.current
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current
val isSelf = remember(account, uid) { val isSelf = remember(account, uid) {
account?.uid == uid.toString() account?.uid == uid.toString()
@ -160,6 +159,173 @@ fun UserProfilePage(
derivedStateOf { user == null } derivedStateOf { user == null }
} }
ProvideNavigator(navigator = navigator) {
StateScreen(
modifier = Modifier.fillMaxSize(),
isEmpty = isEmpty,
isError = isError,
isLoading = isRefreshing,
onReload = { viewModel.send(UserProfileUiIntent.Refresh(uid)) },
errorScreen = { ErrorScreen(error = error.getOrNull()) }
) {
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 = {
AnimatedVisibility(
visible = showTitle,
enter = fadeIn(),
exit = fadeOut()
) {
ToolbarUserTitle(user = user)
}
},
navigationIcon = {
BackNavigationIcon(onBackPressed = onBack)
},
actions = {
user.takeUnless { isSelf }?.let {
ClickMenu(
menuContent = {
DropdownMenuItem(
onClick = {
BlockManager.addBlockAsync(
Block(
category = Block.CATEGORY_BLACK_LIST,
type = Block.TYPE_USER,
username = it.get { name },
uid = it.get { id }.toString()
)
) {
if (it) context.toastShort(R.string.toast_add_success)
}
}
) {
Text(text = stringResource(id = R.string.menu_add_user_to_black_list))
}
DropdownMenuItem(
onClick = {
BlockManager.addBlockAsync(
Block(
category = Block.CATEGORY_WHITE_LIST,
type = Block.TYPE_USER,
username = it.get { name },
uid = it.get { id }.toString()
)
) {
if (it) context.toastShort(R.string.toast_add_success)
}
}
) {
Text(text = stringResource(id = R.string.menu_add_user_to_white_list))
}
},
triggerShape = CircleShape
) {
Box(
modifier = Modifier.size(48.dp),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Rounded.NoAccounts,
contentDescription = stringResource(id = R.string.btn_block)
)
}
}
}
},
)
}
@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 heightOffset by rememberSaveable { mutableFloatStateOf(0f) }
var headerHeight by rememberSaveable { var headerHeight by rememberSaveable {
mutableFloatStateOf( mutableFloatStateOf(
@ -175,268 +341,334 @@ fun UserProfilePage(
} }
} }
ProvideNavigator(navigator = navigator) { MyScaffold(
StateScreen( topBar = {
modifier = Modifier.fillMaxSize(), UserProfileToolbar(
isEmpty = isEmpty, user = user,
isError = isError, isSelf = isSelf,
isLoading = isRefreshing, showTitle = !isShowHeaderArea,
onReload = { viewModel.send(UserProfileUiIntent.Refresh(uid)) }, onBack = onBack
errorScreen = { ErrorScreen(error = error.getOrNull()) } )
}
) { paddingValues ->
var isFakeRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(isFakeRefreshing) {
if (isFakeRefreshing) {
delay(1000)
isFakeRefreshing = false
}
}
PullToRefreshLayout(
refreshing = isFakeRefreshing,
onRefresh = {
coroutineScope.emitGlobalEvent(GlobalEvent.Refresh(key = "user_profile"))
isFakeRefreshing = true
}
) { ) {
MyScaffold( val headerNestedScrollConnection = remember {
topBar = { object : NestedScrollConnection {
Toolbar( override fun onPreScroll(
title = { available: Offset,
user?.let { source: NestedScrollSource,
AnimatedVisibility( ): Offset {
visible = !isShowHeaderArea, if (available.y < 0) {
enter = fadeIn(), val prevHeightOffset = heightOffset
exit = fadeOut() heightOffset = max(heightOffset + available.y, -headerHeight)
) { if (prevHeightOffset != heightOffset) {
ToolbarUserTitle(user = it) return available.copy(x = 0f)
}
} }
}, }
navigationIcon = {
BackNavigationIcon {
navigator.navigateUp()
}
},
actions = {
user.takeUnless { isSelf }?.let {
ClickMenu(
menuContent = {
DropdownMenuItem(
onClick = {
BlockManager.addBlockAsync(
Block(
category = Block.CATEGORY_BLACK_LIST,
type = Block.TYPE_USER,
username = it.get { name },
uid = it.get { id }.toString()
)
) {
if (it) context.toastShort(R.string.toast_add_success)
}
}
) {
Text(text = stringResource(id = R.string.menu_add_user_to_black_list))
}
DropdownMenuItem(
onClick = {
BlockManager.addBlockAsync(
Block(
category = Block.CATEGORY_WHITE_LIST,
type = Block.TYPE_USER,
username = it.get { name },
uid = it.get { id }.toString()
)
) {
if (it) context.toastShort(R.string.toast_add_success)
}
}
) {
Text(text = stringResource(id = R.string.menu_add_user_to_white_list))
}
},
triggerShape = CircleShape
) {
Box(
modifier = Modifier.size(48.dp),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Rounded.NoAccounts,
contentDescription = stringResource(id = R.string.btn_block)
)
}
}
}
},
)
}
) { paddingValues ->
var isFakeRefreshing by remember { mutableStateOf(false) }
LaunchedEffect(isFakeRefreshing) { return Offset.Zero
if (isFakeRefreshing) { }
delay(1000)
isFakeRefreshing = false override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource,
): Offset {
if (available.y > 0f) {
// Adjust the height offset in case the consumed delta Y is less than what was
// recorded as available delta Y in the pre-scroll.
val prevHeightOffset = heightOffset
heightOffset = min(heightOffset + available.y, 0f)
if (prevHeightOffset != heightOffset) {
return available.copy(x = 0f)
}
}
return Offset.Zero
} }
} }
}
PullToRefreshLayout( ProvideContentColor(color = ExtendedTheme.colors.text) {
refreshing = isFakeRefreshing, Column(
onRefresh = { modifier = Modifier
coroutineScope.emitGlobalEvent(GlobalEvent.Refresh(key = "user_profile")) .padding(paddingValues)
isFakeRefreshing = true .nestedScroll(headerNestedScrollConnection)
}
) { ) {
val headerNestedScrollConnection = remember { val pages = remember {
object : NestedScrollConnection { listOfNotNull(
override fun onPreScroll( UserProfilePageData(
available: Offset, id = "threads",
source: NestedScrollSource, title = {
): Offset { stringResource(
if (available.y < 0) { id = R.string.title_profile_threads_tab,
val prevHeightOffset = heightOffset it.get { thread_num }.getShortNumString()
heightOffset = max(heightOffset + available.y, -headerHeight) )
if (prevHeightOffset != heightOffset) { },
return available.copy(x = 0f) content = { user, fluid ->
} UserPostPage(
uid = user.get { id },
isThread = true,
fluid = fluid,
)
} }
),
return Offset.Zero UserProfilePageData(
} id = "posts",
title = {
override fun onPostScroll( stringResource(
consumed: Offset, id = R.string.title_profile_posts_tab,
available: Offset, it.get { post_num }.getShortNumString()
source: NestedScrollSource, )
): Offset { },
if (available.y > 0f) { content = { user, fluid ->
// Adjust the height offset in case the consumed delta Y is less than what was UserPostPage(
// recorded as available delta Y in the pre-scroll. uid = user.get { id },
val prevHeightOffset = heightOffset isThread = false,
heightOffset = min(heightOffset + available.y, 0f) fluid = fluid,
if (prevHeightOffset != heightOffset) { )
return available.copy(x = 0f)
}
} }
).takeIf { isSelf },
UserProfilePageData(
id = "concern_forums",
title = {
stringResource(
id = R.string.title_profile_concern_forums_tab,
it.get { my_like_num }.toString()
)
},
content = { user, fluid ->
UserLikeForumPage(
uid = user.get { id },
fluid = fluid,
)
}
),
).toImmutableList()
}
val pagerState = rememberPagerState { pages.size }
return Offset.Zero val containerHeight by remember {
derivedStateOf {
with(density) {
(headerHeight + heightOffset).toDp()
} }
} }
} }
ProvideContentColor(color = ExtendedTheme.colors.text) { Box(
Column( modifier = Modifier
.height(containerHeight)
.clipToBounds()
) {
Box(
modifier = Modifier modifier = Modifier
.padding(paddingValues) .wrapContentHeight(
.nestedScroll(headerNestedScrollConnection) align = Alignment.Bottom,
unbounded = true
)
.onSizeChanged {
headerHeight = it.height.toFloat()
}
) { ) {
user?.let { holder -> UserProfileDetail(
val pages = remember { user = user,
listOfNotNull( modifier = Modifier
UserProfilePageData( .padding(16.dp)
id = "threads", .padding(top = 8.dp),
title = { showBtn = showActionBtn,
stringResource( isSelf = isSelf,
id = R.string.title_profile_threads_tab, onBtnClick = {
it.get { thread_num }.getShortNumString() if (disableButton || !showActionBtn) {
) return@UserProfileDetail
},
content = {
UserPostPage(
uid = it.get { id },
isThread = true
)
}
),
UserProfilePageData(
id = "posts",
title = {
stringResource(
id = R.string.title_profile_posts_tab,
it.get { post_num }.getShortNumString()
)
},
content = {
UserPostPage(
uid = uid,
isThread = false
)
}
).takeIf { isSelf },
UserProfilePageData(
id = "concern_forums",
title = {
stringResource(
id = R.string.title_profile_concern_forums_tab,
it.get { my_like_num }.toString()
)
},
content = {
UserLikeForumPage(uid = it.get { id })
}
),
).toImmutableList()
}
val pagerState = rememberPagerState { pages.size }
val containerHeight by remember {
derivedStateOf {
with(density) {
(headerHeight + heightOffset).toDp()
}
} }
} if (isSelf) {
context.goToActivity<EditProfileActivity>()
Box( } else if (user.get { has_concerned } == 0) {
modifier = Modifier onFollow()
.height(containerHeight) } else {
.clipToBounds() onUnfollow()
) { }
UserProfileDetail( },
user = holder, onCopyIdClick = {
modifier = Modifier TiebaUtil.copyText(
.padding(horizontal = 16.dp) context,
.wrapContentHeight( user.get { id }.toString()
align = Alignment.Bottom,
unbounded = true
)
.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,
)
)
}
},
onCopyIdClick = {
TiebaUtil.copyText(
context,
holder.get { id }.toString()
)
}
) )
} }
)
}
}
UserProfileTabRow( UserProfileTabRow(
user = holder, user = user,
pages = pages, pages = pages,
pagerState = pagerState, pagerState = pagerState,
// modifier = Modifier.padding(horizontal = 16.dp) // modifier = Modifier.padding(horizontal = 16.dp)
) )
LazyLoadHorizontalPager( LazyLoadHorizontalPager(
state = pagerState, state = pagerState,
key = { pages[it].id }, key = { pages[it].id },
modifier = Modifier.fillMaxSize() 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( data class UserProfilePageData(
val id: String, val id: String,
val title: @Composable (ImmutableHolder<User>) -> String, val title: @Composable (ImmutableHolder<User>) -> String,
val content: @Composable (ImmutableHolder<User>) -> Unit, val content: @Composable (ImmutableHolder<User>, Boolean) -> Unit,
) )
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@ -547,7 +779,6 @@ private fun UserProfileDetail(
modifier = modifier, modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
Spacer(modifier = Modifier.height(16.dp))
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp) 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.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf 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.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination 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.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.ErrorScreen
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
@ -46,6 +48,8 @@ import kotlinx.collections.immutable.persistentListOf
@Composable @Composable
fun UserLikeForumPage( fun UserLikeForumPage(
uid: Long, uid: Long,
fluid: Boolean = false,
enablePullRefresh: Boolean = false,
viewModel: UserLikeForumViewModel = pageViewModel(), viewModel: UserLikeForumViewModel = pageViewModel(),
) { ) {
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
@ -109,7 +113,10 @@ fun UserLikeForumPage(
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
Box { val pullRefreshModifier =
if (enablePullRefresh) Modifier.pullRefresh(pullRefreshState) else Modifier
Box(modifier = pullRefreshModifier) {
LoadMoreLayout( LoadMoreLayout(
isLoading = isLoadingMore, isLoading = isLoadingMore,
onLoadMore = { onLoadMore = {
@ -120,6 +127,7 @@ fun UserLikeForumPage(
) { ) {
UserLikeForumList( UserLikeForumList(
data = forums, data = forums,
fluid = fluid,
onClickForum = { forumBean -> onClickForum = { forumBean ->
forumBean.name?.let { forumBean.name?.let {
navigator.navigate(ForumPageDestination(it)) navigator.navigate(ForumPageDestination(it))
@ -144,6 +152,7 @@ fun UserLikeForumPage(
private fun UserLikeForumList( private fun UserLikeForumList(
data: ImmutableList<UserLikeForumBean.ForumBean>, data: ImmutableList<UserLikeForumBean.ForumBean>,
onClickForum: (UserLikeForumBean.ForumBean) -> Unit, onClickForum: (UserLikeForumBean.ForumBean) -> Unit,
fluid: Boolean = false,
lazyListState: LazyListState = rememberLazyListState(), lazyListState: LazyListState = rememberLazyListState(),
) { ) {
MyLazyColumn(state = lazyListState) { MyLazyColumn(state = lazyListState) {
@ -151,15 +160,17 @@ private fun UserLikeForumList(
items = data, items = data,
key = { it.id } key = { it.id }
) { ) {
UserLikeForumItem( Container(fluid = fluid) {
item = it, UserLikeForumItem(
onClick = { item = it,
onClickForum(it) onClick = {
}, onClickForum(it)
modifier = Modifier },
.fillMaxWidth() modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp) .fillMaxWidth()
) .padding(horizontal = 16.dp, vertical = 8.dp)
)
}
} }
} }
} }

View File

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

View File

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

View File

@ -25,14 +25,19 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.TipScreen
val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = { val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_loading_paperplane)) val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_loading_paperplane))
Box(modifier = Modifier.requiredWidthIn(max = 500.dp)) { Box(
LottieAnimation( modifier = Modifier.fillMaxWidth(),
composition = composition, contentAlignment = Alignment.Center
iterations = LottieConstants.IterateForever, ) {
modifier = Modifier Box(modifier = Modifier.requiredWidthIn(max = 500.dp)) {
.fillMaxWidth() LottieAnimation(
.aspectRatio(2f) composition = composition,
) iterations = LottieConstants.IterateForever,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(2f)
)
}
} }
// CircularProgressIndicator(modifier = Modifier.size(48.dp), color = MaterialTheme.colors.primary) // 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)) Text(text = stringResource(id = R.string.btn_refresh))
} }
} }
} },
modifier = Modifier.fillMaxWidth(),
) )
} }