feat: 新 UI 双击底栏刷新

This commit is contained in:
HuanCheng65 2023-03-23 19:15:36 +08:00
parent 18cbbe1b76
commit 37f378f59f
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
8 changed files with 132 additions and 29 deletions

View File

@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -49,6 +50,7 @@ import com.huanchengfly.tieba.post.utils.appPreferences
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
@ -56,6 +58,7 @@ import kotlinx.coroutines.launch
private fun NavigationWrapper(
currentPosition: Int,
onChangePosition: (position: Int) -> Unit,
onReselected: (position: Int) -> Unit,
navigationItems: List<NavigationItem>,
navigationType: MainNavigationType,
navigationContentPosition: MainNavigationContentPosition,
@ -66,6 +69,7 @@ private fun NavigationWrapper(
NavigationDrawerContent(
currentPosition = currentPosition,
onChangePosition = onChangePosition,
onReselected = onReselected,
navigationItems = navigationItems,
navigationContentPosition = navigationContentPosition
)
@ -74,6 +78,7 @@ private fun NavigationWrapper(
NavigationRail(
currentPosition = currentPosition,
onChangePosition = onChangePosition,
onReselected = onReselected,
navigationItems = navigationItems,
navigationContentPosition = navigationContentPosition
)
@ -100,6 +105,15 @@ fun MainPage(
initial = 0
)
val eventFlows = remember {
listOf(
MutableSharedFlow<MainUiEvent>(),
MutableSharedFlow<MainUiEvent>(),
MutableSharedFlow<MainUiEvent>(),
MutableSharedFlow<MainUiEvent>(),
)
}
val notificationCountFlow = LocalNotificationCountFlow.current
LaunchedEffect(null) {
notificationCountFlow.collect {
@ -118,13 +132,13 @@ fun MainPage(
title = stringResource(id = R.string.title_main),
content = {
HomePage(
canOpenExplore = !LocalContext.current.appPreferences.hideExplore,
onOpenExplore = {
eventFlow = eventFlows[0],
canOpenExplore = !LocalContext.current.appPreferences.hideExplore
) {
coroutineScope.launch {
pagerState.scrollToPage(1)
}
}
)
}
),
if (LocalContext.current.appPreferences.hideExplore) null
@ -136,7 +150,7 @@ fun MainPage(
},
title = stringResource(id = R.string.title_explore),
content = {
ExplorePage()
ExplorePage(eventFlows[1])
}
),
NavigationItem(
@ -187,17 +201,25 @@ fun MainPage(
WindowHeightSizeClass.Compact -> {
MainNavigationContentPosition.TOP
}
WindowHeightSizeClass.Medium,
WindowHeightSizeClass.Expanded -> {
MainNavigationContentPosition.CENTER
}
else -> {
MainNavigationContentPosition.TOP
}
}
val onReselected: (Int) -> Unit = {
coroutineScope.launch {
eventFlows[it].emit(MainUiEvent.Refresh)
}
}
NavigationWrapper(
currentPosition = pagerState.currentPage,
onChangePosition = { coroutineScope.launch { pagerState.scrollToPage(it) } },
onReselected = onReselected,
navigationItems = navigationItems,
navigationType = navigationType,
navigationContentPosition = navigationContentPosition
@ -209,7 +231,10 @@ fun MainPage(
AnimatedVisibility(visible = navigationType == MainNavigationType.BOTTOM_NAVIGATION) {
BottomNavigation(
currentPosition = pagerState.currentPage,
onChangePosition = { coroutineScope.launch { pagerState.scrollToPage(it) } },
onChangePosition = {
coroutineScope.launch { pagerState.scrollToPage(it) }
},
onReselected = onReselected,
navigationItems = navigationItems,
themeColors = themeColors,
)

View File

@ -1,6 +1,11 @@
package com.huanchengfly.tieba.post.ui.page.main
import com.huanchengfly.tieba.post.arch.*
import com.huanchengfly.tieba.post.arch.BaseViewModel
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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.filterIsInstance
@ -50,4 +55,6 @@ sealed interface MainPartialChange : PartialChange<MainUiState> {
data class MainUiState(val messageCount: Int = 0) : UiState
sealed interface MainUiEvent : UiEvent
sealed interface MainUiEvent : UiEvent {
object Refresh : MainUiEvent
}

View File

@ -129,6 +129,7 @@ fun NavigationDrawerItem(
fun NavigationDrawerContent(
currentPosition: Int,
onChangePosition: (position: Int) -> Unit,
onReselected: (position: Int) -> Unit,
navigationItems: List<NavigationItem>,
navigationContentPosition: MainNavigationContentPosition
) {
@ -191,7 +192,13 @@ fun NavigationDrawerContent(
navigationItems.forEachIndexed { index, navigationItem ->
NavigationDrawerItem(
selected = index == currentPosition,
onClick = { onChangePosition(index) },
onClick = {
if (index == currentPosition) {
onReselected(index)
} else {
onChangePosition(index)
}
},
label = { Text(text = navigationItem.title) },
icon = {
Box {
@ -275,6 +282,7 @@ private fun PositionLayout(
fun NavigationRail(
currentPosition: Int,
onChangePosition: (position: Int) -> Unit,
onReselected: (position: Int) -> Unit,
navigationItems: List<NavigationItem>,
navigationContentPosition: MainNavigationContentPosition
) {
@ -296,7 +304,13 @@ fun NavigationRail(
navigationItems.forEachIndexed { index, navigationItem ->
NavigationRailItem(
selected = index == currentPosition,
onClick = { onChangePosition(index) },
onClick = {
if (index == currentPosition) {
onReselected(index)
} else {
onChangePosition(index)
}
},
icon = {
Box {
Icon(
@ -345,6 +359,7 @@ fun BottomNavigationDivider(
fun BottomNavigation(
currentPosition: Int,
onChangePosition: (position: Int) -> Unit,
onReselected: (position: Int) -> Unit,
navigationItems: List<NavigationItem>,
themeColors: ExtendedColors = ExtendedTheme.colors
) {
@ -358,7 +373,11 @@ fun BottomNavigation(
BottomNavigationItem(
selected = index == currentPosition,
onClick = {
if (index == currentPosition) {
onReselected(index)
} else {
onChangePosition(index)
}
navigationItem.onClick?.invoke()
},
icon = {

View File

@ -9,6 +9,7 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Search
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -21,8 +22,10 @@ import com.google.accompanist.pager.HorizontalPager
import com.google.accompanist.pager.rememberPagerState
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.NewSearchActivity
import com.huanchengfly.tieba.post.arch.onEvent
import com.huanchengfly.tieba.post.goToActivity
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
import com.huanchengfly.tieba.post.ui.page.main.explore.concern.ConcernPage
import com.huanchengfly.tieba.post.ui.page.main.explore.hot.HotPage
import com.huanchengfly.tieba.post.ui.page.main.explore.personalized.PersonalizedPage
@ -31,27 +34,47 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
import com.huanchengfly.tieba.post.ui.widgets.compose.accountNavIconIfCompact
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.launch
@OptIn(ExperimentalPagerApi::class)
@Composable
fun ExplorePage() {
fun ExplorePage(
eventFlow: Flow<MainUiEvent>,
) {
val account = LocalAccount.current
val context = LocalContext.current
val eventFlows = remember {
listOf(
MutableSharedFlow<MainUiEvent>(),
MutableSharedFlow<MainUiEvent>(),
MutableSharedFlow<MainUiEvent>()
)
}
val firstIndex = if (account != null) 0 else -1
val pages = listOfNotNull<Pair<String, (@Composable () -> Unit)>>(
if (account != null) stringResource(id = R.string.title_concern) to @Composable {
ConcernPage()
ConcernPage(eventFlows[firstIndex + 0])
} else null,
stringResource(id = R.string.title_personalized) to @Composable {
PersonalizedPage()
PersonalizedPage(eventFlows[firstIndex + 1])
},
stringResource(id = R.string.title_hot) to @Composable {
HotPage()
HotPage(eventFlows[firstIndex + 2])
},
)
val pagerState = rememberPagerState(initialPage = if (account != null) 1 else 0)
val coroutineScope = rememberCoroutineScope()
eventFlow.onEvent<MainUiEvent.Refresh> {
eventFlows[pagerState.currentPage].emit(it)
}
Scaffold(
backgroundColor = Color.Transparent,
topBar = {
@ -88,8 +111,12 @@ fun ExplorePage() {
selected = pagerState.currentPage == index,
onClick = {
coroutineScope.launch {
if (pagerState.currentPage == index) {
eventFlows[pagerState.currentPage].emit(MainUiEvent.Refresh)
} else {
pagerState.animateScrollToPage(index)
}
}
},
)
}

View File

@ -19,16 +19,20 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.huanchengfly.tieba.post.activities.ThreadActivity
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.onEvent
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.arch.wrapImmutable
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
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.VerticalDivider
import kotlinx.coroutines.flow.Flow
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
fun ConcernPage(
eventFlow: Flow<MainUiEvent>,
viewModel: ConcernViewModel = pageViewModel()
) {
LazyLoad(loaded = viewModel.initialized) {
@ -55,6 +59,11 @@ fun ConcernPage(
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = { viewModel.send(ConcernUiIntent.Refresh) })
eventFlow.onEvent<MainUiEvent.Refresh> {
viewModel.send(ConcernUiIntent.Refresh)
}
Box(
modifier = Modifier.pullRefresh(pullRefreshState)
) {

View File

@ -47,6 +47,7 @@ import com.google.accompanist.placeholder.material.placeholder
import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.onEvent
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.common.theme.compose.OrangeA700
@ -55,6 +56,7 @@ import com.huanchengfly.tieba.post.ui.common.theme.compose.White
import com.huanchengfly.tieba.post.ui.common.theme.compose.Yellow
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.HotTopicListPageDestination
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.NetworkImage
import com.huanchengfly.tieba.post.ui.widgets.compose.ProvideContentColor
@ -64,17 +66,22 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.items
import com.huanchengfly.tieba.post.ui.widgets.compose.itemsIndexed
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
import com.ramcosta.composedestinations.annotation.Destination
import kotlinx.coroutines.flow.Flow
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Destination
@Composable
fun HotPage(
eventFlow: Flow<MainUiEvent>,
viewModel: HotViewModel = pageViewModel()
) {
LazyLoad(loaded = viewModel.initialized) {
viewModel.send(HotUiIntent.Load)
viewModel.initialized = true
}
eventFlow.onEvent<MainUiEvent.Refresh> {
viewModel.send(HotUiIntent.Load)
}
val navigator = LocalNavigator.current
val isLoading by viewModel.uiState.collectPartialAsState(
prop1 = HotUiState::isRefreshing,

View File

@ -53,20 +53,22 @@ import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.personalized.DislikeReason
import com.huanchengfly.tieba.post.api.models.protos.personalized.ThreadPersonalized
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.onEvent
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.arch.wrapImmutable
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
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.VerticalDivider
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.Flow
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
fun PersonalizedPage(
eventFlow: Flow<MainUiEvent>,
viewModel: PersonalizedViewModel = pageViewModel()
) {
LazyLoad(loaded = viewModel.initialized) {
@ -113,16 +115,15 @@ fun PersonalizedPage(
var showRefreshTip by remember {
mutableStateOf(false)
}
LaunchedEffect(Unit) {
launch {
viewModel.uiEventFlow
.filterIsInstance<PersonalizedUiEvent.RefreshSuccess>()
.collect {
eventFlow.onEvent<MainUiEvent.Refresh> {
viewModel.send(PersonalizedUiIntent.Refresh)
}
viewModel.onEvent<PersonalizedUiEvent.RefreshSuccess> {
refreshCount = it.count
showRefreshTip = true
}
}
}
if (showRefreshTip) {
LaunchedEffect(Unit) {
delay(2000)

View File

@ -70,11 +70,13 @@ import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.LoginActivity
import com.huanchengfly.tieba.post.activities.NewSearchActivity
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.onEvent
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.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
import com.huanchengfly.tieba.post.ui.page.main.MainUiEvent
import com.huanchengfly.tieba.post.ui.widgets.Chip
import com.huanchengfly.tieba.post.ui.widgets.compose.ActionItem
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
@ -93,6 +95,7 @@ import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
import com.huanchengfly.tieba.post.utils.ImageUtil
import com.huanchengfly.tieba.post.utils.TiebaUtil
import com.huanchengfly.tieba.post.utils.appPreferences
import kotlinx.coroutines.flow.Flow
private fun getGridCells(context: Context, listSingle: Boolean = context.appPreferences.listSingle): GridCells {
return if (listSingle) {
@ -370,6 +373,7 @@ private fun ForumItem(
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun HomePage(
eventFlow: Flow<MainUiEvent>,
viewModel: HomeViewModel = pageViewModel<HomeUiIntent, HomeViewModel>(
listOf(
HomeUiIntent.Refresh
@ -400,6 +404,10 @@ fun HomePage(
var listSingle by remember { mutableStateOf(context.appPreferences.listSingle) }
val gridCells by remember { derivedStateOf { getGridCells(context, listSingle) } }
eventFlow.onEvent<MainUiEvent.Refresh> {
viewModel.send(HomeUiIntent.Refresh)
}
Scaffold(
backgroundColor = Color.Transparent,
topBar = {