diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt index 31c21141..d50d27f1 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/threadlist/ForumThreadListPage.kt @@ -201,7 +201,7 @@ private fun ThreadList( } MyLazyColumn( state = state, -// horizontalAlignment = Alignment.CenterHorizontally, + horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth(), contentPadding = WindowInsets.navigationBars.asPaddingValues() ) { 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 d61b5e75..b9a65ba3 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 @@ -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,6 +159,173 @@ fun UserProfilePage( 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, + 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, + 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, + 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( @@ -175,268 +341,334 @@ fun UserProfilePage( } } - ProvideNavigator(navigator = navigator) { - StateScreen( - modifier = Modifier.fillMaxSize(), - isEmpty = isEmpty, - isError = isError, - isLoading = isRefreshing, - onReload = { viewModel.send(UserProfileUiIntent.Refresh(uid)) }, - errorScreen = { ErrorScreen(error = error.getOrNull()) } + MyScaffold( + topBar = { + UserProfileToolbar( + user = user, + isSelf = isSelf, + showTitle = !isShowHeaderArea, + onBack = onBack + ) + } + ) { 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( - topBar = { - Toolbar( - title = { - user?.let { - AnimatedVisibility( - visible = !isShowHeaderArea, - enter = fadeIn(), - exit = fadeOut() - ) { - ToolbarUserTitle(user = it) - } + val headerNestedScrollConnection = remember { + object : NestedScrollConnection { + override fun onPreScroll( + available: Offset, + source: NestedScrollSource, + ): Offset { + if (available.y < 0) { + val prevHeightOffset = heightOffset + heightOffset = max(heightOffset + available.y, -headerHeight) + if (prevHeightOffset != heightOffset) { + 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) { - if (isFakeRefreshing) { - delay(1000) - isFakeRefreshing = false + return Offset.Zero + } + + 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( - refreshing = isFakeRefreshing, - onRefresh = { - coroutineScope.emitGlobalEvent(GlobalEvent.Refresh(key = "user_profile")) - isFakeRefreshing = true - } + ProvideContentColor(color = ExtendedTheme.colors.text) { + Column( + modifier = Modifier + .padding(paddingValues) + .nestedScroll(headerNestedScrollConnection) ) { - val headerNestedScrollConnection = remember { - object : NestedScrollConnection { - override fun onPreScroll( - available: Offset, - source: NestedScrollSource, - ): Offset { - if (available.y < 0) { - val prevHeightOffset = heightOffset - heightOffset = max(heightOffset + available.y, -headerHeight) - if (prevHeightOffset != heightOffset) { - return available.copy(x = 0f) - } + val pages = remember { + listOfNotNull( + UserProfilePageData( + id = "threads", + title = { + stringResource( + id = R.string.title_profile_threads_tab, + it.get { thread_num }.getShortNumString() + ) + }, + content = { user, fluid -> + UserPostPage( + uid = user.get { id }, + isThread = true, + fluid = fluid, + ) } - - return Offset.Zero - } - - 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) - } + ), + UserProfilePageData( + id = "posts", + title = { + stringResource( + id = R.string.title_profile_posts_tab, + it.get { post_num }.getShortNumString() + ) + }, + content = { user, fluid -> + UserPostPage( + uid = user.get { id }, + isThread = false, + fluid = fluid, + ) } + ).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) { - Column( + Box( + modifier = Modifier + .height(containerHeight) + .clipToBounds() + ) { + Box( modifier = Modifier - .padding(paddingValues) - .nestedScroll(headerNestedScrollConnection) + .wrapContentHeight( + align = Alignment.Bottom, + unbounded = true + ) + .onSizeChanged { + headerHeight = it.height.toFloat() + } ) { - user?.let { holder -> - val pages = remember { - listOfNotNull( - UserProfilePageData( - id = "threads", - title = { - stringResource( - id = R.string.title_profile_threads_tab, - it.get { thread_num }.getShortNumString() - ) - }, - 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() - } + UserProfileDetail( + user = user, + modifier = Modifier + .padding(16.dp) + .padding(top = 8.dp), + showBtn = showActionBtn, + isSelf = isSelf, + onBtnClick = { + if (disableButton || !showActionBtn) { + return@UserProfileDetail } - } - - Box( - modifier = Modifier - .height(containerHeight) - .clipToBounds() - ) { - UserProfileDetail( - user = holder, - modifier = Modifier - .padding(horizontal = 16.dp) - .wrapContentHeight( - 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() - } 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() - ) - } + if (isSelf) { + context.goToActivity() + } else if (user.get { has_concerned } == 0) { + onFollow() + } else { + onUnfollow() + } + }, + onCopyIdClick = { + TiebaUtil.copyText( + context, + user.get { id }.toString() ) } + ) + } + } - UserProfileTabRow( - user = holder, - pages = pages, - pagerState = pagerState, + UserProfileTabRow( + user = user, + pages = pages, + pagerState = pagerState, // modifier = Modifier.padding(horizontal = 16.dp) - ) + ) - LazyLoadHorizontalPager( - state = pagerState, - key = { pages[it].id }, - modifier = Modifier.fillMaxSize() - ) { - pages[it].content(holder) + LazyLoadHorizontalPager( + state = pagerState, + key = { pages[it].id }, + modifier = Modifier.fillMaxSize() + ) { + pages[it].content(user, false) + } + } + } + } + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun UserProfileContentExpanded( + user: ImmutableHolder, + 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() + } 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) -> String, - val content: @Composable (ImmutableHolder) -> Unit, + val content: @Composable (ImmutableHolder, 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)) } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/likeforum/UserLikeForumPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/likeforum/UserLikeForumPage.kt index 7217f329..15de9c78 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/likeforum/UserLikeForumPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/likeforum/UserLikeForumPage.kt @@ -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, onClickForum: (UserLikeForumBean.ForumBean) -> Unit, + fluid: Boolean = false, lazyListState: LazyListState = rememberLazyListState(), ) { MyLazyColumn(state = lazyListState) { @@ -151,15 +160,17 @@ private fun UserLikeForumList( items = data, key = { it.id } ) { - UserLikeForumItem( - item = it, - onClick = { - onClickForum(it) - }, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp) - ) + Container(fluid = fluid) { + UserLikeForumItem( + item = it, + onClick = { + onClickForum(it) + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp) + ) + } } } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/post/UserPostPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/post/UserPostPage.kt index aa892e57..7b306f5d 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/post/UserPostPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/user/post/UserPostPage.kt @@ -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, + fluid: Boolean = false, lazyListState: LazyListState = rememberLazyListState(), onClickItem: (threadId: Long, postId: Long?, isSubPost: Boolean) -> Unit = { _, _, _ -> }, onAgreeItem: (PostInfoList) -> Unit = {}, @@ -301,15 +310,17 @@ private fun UserPostList( "${it.data.get { thread_id }}_${it.data.get { post_id }}" } ) { itemData -> - UserPostItem( - post = itemData, - onClick = onClickItem, - onAgree = onAgreeItem, - onClickReply = onClickReply, - onClickUser = onClickUser, - onClickForum = onClickForum, - onClickOriginThread = onClickOriginThread, - ) + Container(fluid = fluid) { + UserPostItem( + post = itemData, + onClick = onClickItem, + onAgree = onAgreeItem, + onClickReply = onClickReply, + onClickUser = onClickUser, + onClickForum = onClickForum, + onClickOriginThread = onClickOriginThread, + ) + } } } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Containers.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Containers.kt index 1722d770..cd2376eb 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Containers.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/Containers.kt @@ -12,14 +12,19 @@ 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) { - when (windowWidthSizeClass) { - WindowWidthSizeClass.Medium -> 0.87f - WindowWidthSizeClass.Expanded -> 0.75f - else -> 1f + if (fluid) { + 1f + } else { + when (windowWidthSizeClass) { + WindowWidthSizeClass.Medium -> 0.87f + WindowWidthSizeClass.Expanded -> 0.75f + else -> 1f + } } } diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/states/States.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/states/States.kt index a2e92915..1a17fea3 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/states/States.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/widgets/compose/states/States.kt @@ -25,14 +25,19 @@ 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.requiredWidthIn(max = 500.dp)) { - LottieAnimation( - composition = composition, - iterations = LottieConstants.IterateForever, - modifier = Modifier - .fillMaxWidth() - .aspectRatio(2f) - ) + Box( + modifier = Modifier.fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + Box(modifier = Modifier.requiredWidthIn(max = 500.dp)) { + LottieAnimation( + composition = composition, + iterations = LottieConstants.IterateForever, + modifier = Modifier + .fillMaxWidth() + .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(), ) }