pref: 优化性能
This commit is contained in:
parent
dbd07caaec
commit
b224836d0f
|
|
@ -2,6 +2,7 @@ package com.huanchengfly.tieba.post.api.models
|
|||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import com.huanchengfly.tieba.post.models.BaseBean
|
||||
import javax.annotation.concurrent.Immutable
|
||||
|
||||
data class ThreadStoreBean(
|
||||
@SerializedName("error_code")
|
||||
|
|
@ -10,6 +11,7 @@ data class ThreadStoreBean(
|
|||
@SerializedName("store_thread")
|
||||
val storeThread: List<ThreadStoreInfo>? = null
|
||||
) : BaseBean() {
|
||||
@Immutable
|
||||
data class ThreadStoreInfo(
|
||||
@SerializedName("thread_id")
|
||||
val threadId: String,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ fun <T : UiState, A> Flow<T>.collectPartialAsState(
|
|||
this@collectPartialAsState
|
||||
.map { prop1.get(it) }
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect {
|
||||
value = it
|
||||
}
|
||||
|
|
@ -71,6 +72,7 @@ inline fun <reified Event : UiEvent> Flow<UiEvent>.onEvent(
|
|||
this@onEvent
|
||||
.filterIsInstance<Event>()
|
||||
.cancellable()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect {
|
||||
launch {
|
||||
listener(it)
|
||||
|
|
@ -95,6 +97,7 @@ inline fun <reified Event : UiEvent> BaseViewModel<*, *, *, *>.onEvent(
|
|||
uiEventFlow
|
||||
.filterIsInstance<Event>()
|
||||
.cancellable()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collect {
|
||||
coroutineScope.launch {
|
||||
listener(it)
|
||||
|
|
@ -119,6 +122,7 @@ inline fun <reified VM : BaseViewModel<*, *, *, *>> pageViewModel(): VM {
|
|||
uiEventFlow
|
||||
.filterIsInstance<CommonUiEvent>()
|
||||
.cancellable()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collectIn(context) {
|
||||
context.handleCommonEvent(it)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.Dialog
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.DialogNegativeButton
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.picker.ListSinglePicker
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
|
|
@ -92,6 +94,9 @@ fun ListPref(
|
|||
onClick = { if (enabled) dialogState.show() },
|
||||
)
|
||||
|
||||
val itemTitle = remember(entries) { entries.map { it.value }.toImmutableList() }
|
||||
val itemValues = remember(entries) { entries.map { it.key }.toImmutableList() }
|
||||
|
||||
Dialog(
|
||||
dialogState = dialogState,
|
||||
title = { Text(text = title) },
|
||||
|
|
@ -100,15 +105,15 @@ fun ListPref(
|
|||
}
|
||||
) {
|
||||
ListSinglePicker(
|
||||
itemTitles = entries.map { it.value },
|
||||
itemValues = entries.map { it.key },
|
||||
itemTitles = itemTitle,
|
||||
itemValues = itemValues,
|
||||
selectedPosition = entries.keys.indexOf(selected),
|
||||
onItemSelected = { _, title, value, _ ->
|
||||
edit(current = value to title)
|
||||
dismiss()
|
||||
},
|
||||
itemIcons = icons,
|
||||
modifier = Modifier.padding(bottom = 16.dp)
|
||||
modifier = Modifier.padding(bottom = 16.dp),
|
||||
itemIcons = icons.toImmutableMap()
|
||||
)
|
||||
}
|
||||
// if (showDialog) {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.material.rememberScaffoldState
|
||||
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
|
||||
|
|
@ -24,6 +23,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.arch.emitGlobalEvent
|
||||
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.history.list.HistoryListPage
|
||||
|
|
@ -36,7 +36,6 @@ import com.huanchengfly.tieba.post.utils.HistoryUtil
|
|||
import com.ramcosta.composedestinations.annotation.DeepLink
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
|
|
@ -55,8 +54,6 @@ fun HistoryPage(
|
|||
|
||||
val context = LocalContext.current
|
||||
|
||||
val eventFlow = remember { MutableSharedFlow<HistoryListUiEvent>() }
|
||||
|
||||
MyScaffold(
|
||||
backgroundColor = Color.Transparent,
|
||||
scaffoldState = scaffoldState,
|
||||
|
|
@ -70,7 +67,7 @@ fun HistoryPage(
|
|||
IconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
HistoryUtil.deleteAll()
|
||||
eventFlow.emit(HistoryListUiEvent.DeleteAll)
|
||||
emitGlobalEvent(HistoryListUiEvent.DeleteAll)
|
||||
launch {
|
||||
scaffoldState.snackbarHostState.showSnackbar(
|
||||
context.getString(
|
||||
|
|
@ -148,9 +145,9 @@ fun HistoryPage(
|
|||
userScrollEnabled = true,
|
||||
) {
|
||||
if (it == 0) {
|
||||
HistoryListPage(type = HistoryUtil.TYPE_THREAD, eventFlow = eventFlow)
|
||||
HistoryListPage(type = HistoryUtil.TYPE_THREAD)
|
||||
} else {
|
||||
HistoryListPage(type = HistoryUtil.TYPE_FORUM, eventFlow = eventFlow)
|
||||
HistoryListPage(type = HistoryUtil.TYPE_FORUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -25,6 +24,7 @@ import com.huanchengfly.tieba.post.activities.ForumActivity
|
|||
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.onGlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.fromJson
|
||||
import com.huanchengfly.tieba.post.models.ThreadHistoryInfoBean
|
||||
|
|
@ -45,30 +45,20 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.UserHeader
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||
import com.huanchengfly.tieba.post.utils.HistoryUtil
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HistoryListPage(
|
||||
type: Int,
|
||||
eventFlow: Flow<HistoryListUiEvent>,
|
||||
viewModel: HistoryListViewModel = if (type == HistoryUtil.TYPE_THREAD) pageViewModel<ThreadHistoryListViewModel>() else pageViewModel<ForumHistoryListViewModel>()
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
viewModel.send(HistoryListUiIntent.Refresh)
|
||||
viewModel.initialized = true
|
||||
}
|
||||
LaunchedEffect(null) {
|
||||
launch {
|
||||
eventFlow
|
||||
.filterIsInstance<HistoryListUiEvent.DeleteAll>()
|
||||
.collect {
|
||||
onGlobalEvent<HistoryListUiEvent.DeleteAll> {
|
||||
viewModel.send(HistoryListUiIntent.DeleteAll)
|
||||
}
|
||||
}
|
||||
}
|
||||
val isLoadingMore by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::isLoadingMore,
|
||||
initial = false
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -32,27 +33,28 @@ 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.DisposableEffect
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.github.panpf.sketch.request.PauseLoadWhenScrollingDrawableDecodeInterceptor
|
||||
import com.huanchengfly.tieba.post.R
|
||||
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.BaseComposeActivity
|
||||
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
|
||||
import com.huanchengfly.tieba.post.arch.CommonUiEvent.ScrollToTop.bindScrollToTopEvent
|
||||
import com.huanchengfly.tieba.post.arch.GlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.onEvent
|
||||
import com.huanchengfly.tieba.post.arch.onGlobalEvent
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
|
|
@ -124,6 +126,10 @@ fun PersonalizedPage(
|
|||
) {
|
||||
viewModel.send(PersonalizedUiIntent.Refresh)
|
||||
}
|
||||
viewModel.onEvent<PersonalizedUiEvent.RefreshSuccess> {
|
||||
refreshCount = it.count
|
||||
showRefreshTip = true
|
||||
}
|
||||
|
||||
if (showRefreshTip) {
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
@ -135,14 +141,14 @@ fun PersonalizedPage(
|
|||
showRefreshTip = false
|
||||
}
|
||||
}
|
||||
if (lazyListState.isScrollInProgress) {
|
||||
DisposableEffect(Unit) {
|
||||
PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = true
|
||||
onDispose {
|
||||
PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = false
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (lazyListState.isScrollInProgress) {
|
||||
// DisposableEffect(Unit) {
|
||||
// PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = true
|
||||
// onDispose {
|
||||
// PauseLoadWhenScrollingDrawableDecodeInterceptor.scrolling = false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
|
|
@ -150,6 +156,7 @@ fun PersonalizedPage(
|
|||
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
|
||||
) {
|
||||
FeedList(
|
||||
state = lazyListState,
|
||||
dataProvider = { data },
|
||||
personalizedDataProvider = { threadPersonalizedData },
|
||||
refreshPositionProvider = { refreshPosition },
|
||||
|
|
@ -191,12 +198,10 @@ fun PersonalizedPage(
|
|||
)
|
||||
)
|
||||
},
|
||||
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) },
|
||||
onOpenForum = {
|
||||
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) }
|
||||
) {
|
||||
navigator.navigate(ForumPageDestination(it))
|
||||
},
|
||||
state = lazyListState
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
|
|
@ -213,6 +218,13 @@ fun PersonalizedPage(
|
|||
exit = slideOutVertically() + fadeOut(),
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
) {
|
||||
RefreshTip(refreshCount = refreshCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BoxScope.RefreshTip(refreshCount: Int) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(top = 72.dp)
|
||||
|
|
@ -230,11 +242,10 @@ fun PersonalizedPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FeedList(
|
||||
state: LazyListState,
|
||||
dataProvider: () -> List<ImmutableHolder<ThreadInfo>>,
|
||||
personalizedDataProvider: () -> List<ImmutableHolder<ThreadPersonalized>?>,
|
||||
refreshPositionProvider: () -> Int,
|
||||
|
|
@ -245,17 +256,20 @@ private fun FeedList(
|
|||
onDislike: (ThreadInfo, Long, List<ImmutableHolder<DislikeReason>>) -> Unit,
|
||||
onRefresh: () -> Unit,
|
||||
onOpenForum: (forumName: String) -> Unit = {},
|
||||
state: LazyListState,
|
||||
) {
|
||||
val data = dataProvider()
|
||||
val threadPersonalizedData = personalizedDataProvider()
|
||||
val refreshPosition = refreshPositionProvider()
|
||||
val hiddenThreadIds = hiddenThreadIdsProvider()
|
||||
val windowSizeClass = BaseComposeActivity.LocalWindowSizeClass.current
|
||||
val itemFraction = when (windowSizeClass.widthSizeClass) {
|
||||
val windowWidthSizeClass by rememberUpdatedState(newValue = LocalWindowSizeClass.current.widthSizeClass)
|
||||
val itemFraction by remember {
|
||||
derivedStateOf {
|
||||
when (windowWidthSizeClass) {
|
||||
WindowWidthSizeClass.Expanded -> 0.5f
|
||||
else -> 1f
|
||||
}
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
state = state,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
|
|
@ -273,11 +287,23 @@ private fun FeedList(
|
|||
}
|
||||
}
|
||||
) { index, item ->
|
||||
val isHidden =
|
||||
remember(hiddenThreadIds, item) { hiddenThreadIds.contains(item.get { threadId }) }
|
||||
val personalized =
|
||||
remember(threadPersonalizedData, index) { threadPersonalizedData.getOrNull(index) }
|
||||
val isRefreshPosition =
|
||||
remember(index, refreshPosition) { index + 1 == refreshPosition }
|
||||
val isNotLast = remember(index, data.size) { index < data.size - 1 }
|
||||
val showDivider = remember(
|
||||
isHidden,
|
||||
isRefreshPosition,
|
||||
isNotLast
|
||||
) { !isHidden && !isRefreshPosition && isNotLast }
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(itemFraction)
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = !hiddenThreadIds.contains(item.get { threadId }),
|
||||
visible = !isHidden,
|
||||
enter = EnterTransition.None,
|
||||
exit = shrinkVertically() + fadeOut()
|
||||
) {
|
||||
|
|
@ -290,8 +316,6 @@ private fun FeedList(
|
|||
onOpenForum(it.name)
|
||||
}
|
||||
) {
|
||||
val personalized = threadPersonalizedData.getOrNull(index)
|
||||
|
||||
if (personalized != null) {
|
||||
Dislike(
|
||||
personalized = personalized,
|
||||
|
|
@ -302,15 +326,24 @@ private fun FeedList(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!hiddenThreadIds.contains(item.get { threadId })) {
|
||||
if ((refreshPosition == 0 || index + 1 != refreshPosition) && index < data.size - 1) {
|
||||
if (showDivider) {
|
||||
VerticalDivider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 2.dp
|
||||
)
|
||||
}
|
||||
if (isRefreshPosition) {
|
||||
RefreshTip(onRefresh)
|
||||
}
|
||||
if (refreshPosition != 0 && index + 1 == refreshPosition) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RefreshTip(
|
||||
onRefresh: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -330,7 +363,3 @@ private fun FeedList(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.huanchengfly.tieba.post.ui.page.main.home
|
|||
|
||||
import android.content.Context
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
|
|
@ -22,7 +21,6 @@ import androidx.compose.foundation.lazy.grid.GridCells
|
|||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
|
|
@ -85,6 +83,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MenuState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TextButton
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TipScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
|
||||
|
|
@ -96,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.collections.immutable.persistentListOf
|
||||
|
||||
private fun getGridCells(context: Context, listSingle: Boolean = context.appPreferences.listSingle): GridCells {
|
||||
return if (listSingle) {
|
||||
|
|
@ -230,41 +230,33 @@ private fun ForumItemPlaceholder(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumItem(
|
||||
item: HomeUiState.Forum,
|
||||
showAvatar: Boolean,
|
||||
onClick: (HomeUiState.Forum) -> Unit,
|
||||
onUnfollow: (HomeUiState.Forum) -> Unit,
|
||||
onAddTopForum: (HomeUiState.Forum) -> Unit,
|
||||
onDeleteTopForum: (HomeUiState.Forum) -> Unit,
|
||||
isTopForum: Boolean = false,
|
||||
private fun ForumItemMenuContent(
|
||||
menuState: MenuState,
|
||||
isTopForum: Boolean,
|
||||
onDeleteTopForum: () -> Unit,
|
||||
onAddTopForum: () -> Unit,
|
||||
onCopyName: () -> Unit,
|
||||
onUnfollow: () -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuContent = {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
if (isTopForum) {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onDeleteTopForum(item)
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.menu_top_del))
|
||||
}
|
||||
onDeleteTopForum()
|
||||
} else {
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onAddTopForum(item)
|
||||
onAddTopForum()
|
||||
}
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
if (isTopForum) {
|
||||
Text(text = stringResource(id = R.string.menu_top_del))
|
||||
} else {
|
||||
Text(text = stringResource(id = R.string.menu_top))
|
||||
}
|
||||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
TiebaUtil.copyText(context, item.forumName)
|
||||
onCopyName()
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
|
|
@ -272,24 +264,23 @@ private fun ForumItem(
|
|||
}
|
||||
DropdownMenuItem(
|
||||
onClick = {
|
||||
onUnfollow(item)
|
||||
onUnfollow()
|
||||
menuState.expanded = false
|
||||
}
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.button_unfollow))
|
||||
}
|
||||
},
|
||||
menuState = menuState,
|
||||
onClick = {
|
||||
onClick(item)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumItemContent(
|
||||
item: HomeUiState.Forum,
|
||||
showAvatar: Boolean
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
.padding(vertical = 12.dp)
|
||||
.animateContentSize(),
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||
) {
|
||||
AnimatedVisibility(visible = showAvatar) {
|
||||
Row {
|
||||
|
|
@ -343,16 +334,45 @@ private fun ForumItem(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumItem(
|
||||
item: HomeUiState.Forum,
|
||||
showAvatar: Boolean,
|
||||
onClick: (HomeUiState.Forum) -> Unit,
|
||||
onUnfollow: (HomeUiState.Forum) -> Unit,
|
||||
onAddTopForum: (HomeUiState.Forum) -> Unit,
|
||||
onDeleteTopForum: (HomeUiState.Forum) -> Unit,
|
||||
isTopForum: Boolean = false,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuContent = {
|
||||
ForumItemMenuContent(
|
||||
menuState = menuState,
|
||||
isTopForum = isTopForum,
|
||||
onDeleteTopForum = { onDeleteTopForum(item) },
|
||||
onAddTopForum = { onAddTopForum(item) },
|
||||
onCopyName = {
|
||||
TiebaUtil.copyText(context, item.forumName)
|
||||
},
|
||||
onUnfollow = { onUnfollow(item) }
|
||||
)
|
||||
},
|
||||
menuState = menuState,
|
||||
onClick = {
|
||||
onClick(item)
|
||||
}
|
||||
) {
|
||||
ForumItemContent(item = item, showAvatar = showAvatar)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun HomePage(
|
||||
viewModel: HomeViewModel = pageViewModel<HomeUiIntent, HomeViewModel>(
|
||||
listOf(
|
||||
HomeUiIntent.Refresh
|
||||
)
|
||||
),
|
||||
viewModel: HomeViewModel = pageViewModel<HomeUiIntent, HomeViewModel>(listOf(HomeUiIntent.Refresh)),
|
||||
canOpenExplore: Boolean = false,
|
||||
onOpenExplore: () -> Unit = {},
|
||||
) {
|
||||
|
|
@ -365,11 +385,11 @@ fun HomePage(
|
|||
)
|
||||
val forums by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HomeUiState::forums,
|
||||
initial = emptyList()
|
||||
initial = persistentListOf()
|
||||
)
|
||||
val topForums by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HomeUiState::topForums,
|
||||
initial = emptyList()
|
||||
initial = persistentListOf()
|
||||
)
|
||||
val error by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HomeUiState::error,
|
||||
|
|
@ -431,11 +451,24 @@ fun HomePage(
|
|||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) { contentPaddings ->
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isLoading,
|
||||
onRefresh = { viewModel.send(HomeUiIntent.Refresh) }
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.pullRefresh(pullRefreshState)
|
||||
.padding(contentPaddings)
|
||||
) {
|
||||
Column {
|
||||
SearchBox(modifier = Modifier.padding(bottom = 12.dp)) {
|
||||
context.goToActivity<NewSearchActivity>()
|
||||
}
|
||||
StateScreen(
|
||||
isEmpty = isEmpty,
|
||||
isError = isError,
|
||||
isLoading = isLoading,
|
||||
modifier = Modifier.padding(contentPaddings),
|
||||
modifier = Modifier.weight(1f),
|
||||
onReload = {
|
||||
viewModel.send(HomeUiIntent.Refresh)
|
||||
},
|
||||
|
|
@ -453,26 +486,11 @@ fun HomePage(
|
|||
error?.let { ErrorScreen(error = it) }
|
||||
}
|
||||
) {
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
refreshing = isLoading,
|
||||
onRefresh = { viewModel.send(HomeUiIntent.Refresh) }
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.pullRefresh(pullRefreshState)
|
||||
) {
|
||||
val gridState = rememberLazyGridState()
|
||||
LazyVerticalGrid(
|
||||
state = gridState,
|
||||
columns = gridCells,
|
||||
contentPadding = PaddingValues(bottom = 12.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
item(key = "SearchBox", span = { GridItemSpan(maxLineSpan) }) {
|
||||
SearchBox(modifier = Modifier.padding(bottom = 12.dp)) {
|
||||
context.goToActivity<NewSearchActivity>()
|
||||
}
|
||||
}
|
||||
if (hasTopForum) {
|
||||
item(key = "TopForumHeader", span = { GridItemSpan(maxLineSpan) }) {
|
||||
Column {
|
||||
|
|
@ -506,19 +524,13 @@ fun HomePage(
|
|||
isTopForum = true
|
||||
)
|
||||
}
|
||||
item(
|
||||
key = "Spacer",
|
||||
span = { GridItemSpan(maxLineSpan) }) {
|
||||
Spacer(
|
||||
modifier = Modifier.height(
|
||||
16.dp
|
||||
)
|
||||
)
|
||||
}
|
||||
item(key = "ForumHeader", span = { GridItemSpan(maxLineSpan) }) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.padding(
|
||||
vertical = 8.dp
|
||||
)
|
||||
) {
|
||||
Header(text = stringResource(id = R.string.forum_list_title))
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -545,6 +557,8 @@ fun HomePage(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = isLoading,
|
||||
|
|
@ -556,7 +570,6 @@ fun HomePage(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HomePageSkeletonScreen(
|
||||
|
|
@ -570,11 +583,6 @@ private fun HomePageSkeletonScreen(
|
|||
modifier = Modifier
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
item(key = "SearchBox", span = { GridItemSpan(maxLineSpan) }) {
|
||||
SearchBox(modifier = Modifier.padding(bottom = 12.dp)) {
|
||||
context.goToActivity<NewSearchActivity>()
|
||||
}
|
||||
}
|
||||
item(key = "TopForumHeaderPlaceholder", span = { GridItemSpan(maxLineSpan) }) {
|
||||
Column {
|
||||
Header(
|
||||
|
|
|
|||
|
|
@ -6,12 +6,29 @@ import com.huanchengfly.tieba.post.api.TiebaApi
|
|||
import com.huanchengfly.tieba.post.api.models.CommonResponse
|
||||
import com.huanchengfly.tieba.post.api.models.protos.forumRecommend.ForumRecommendResponse
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||
import com.huanchengfly.tieba.post.arch.*
|
||||
import com.huanchengfly.tieba.post.arch.BaseViewModel
|
||||
import com.huanchengfly.tieba.post.arch.CommonUiEvent
|
||||
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.models.database.TopForum
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import org.litepal.LitePal
|
||||
|
||||
@Stable
|
||||
|
|
@ -114,8 +131,10 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
when (this) {
|
||||
is Success -> {
|
||||
oldState.copy(
|
||||
forums = oldState.forums.filterNot { it.forumId == forumId },
|
||||
topForums = oldState.topForums.filterNot { it.forumId == forumId },
|
||||
forums = oldState.forums.filterNot { it.forumId == forumId }
|
||||
.toImmutableList(),
|
||||
topForums = oldState.topForums.filterNot { it.forumId == forumId }
|
||||
.toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -132,8 +151,8 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
when (this) {
|
||||
is Success -> oldState.copy(
|
||||
isLoading = false,
|
||||
forums = forums,
|
||||
topForums = topForums,
|
||||
forums = forums.toImmutableList(),
|
||||
topForums = topForums.toImmutableList(),
|
||||
error = null
|
||||
)
|
||||
|
||||
|
|
@ -157,7 +176,9 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
sealed interface Delete : HomePartialChange {
|
||||
override fun reduce(oldState: HomeUiState): HomeUiState =
|
||||
when (this) {
|
||||
is Success -> oldState.copy(topForums = oldState.topForums.filterNot { it.forumId == forumId })
|
||||
is Success -> oldState.copy(topForums = oldState.topForums.filterNot { it.forumId == forumId }
|
||||
.toImmutableList())
|
||||
|
||||
is Failure -> oldState
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +195,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
topForumsId.add(forum.forumId)
|
||||
oldState.copy(
|
||||
topForums = oldState.forums.filter { topForumsId.contains(it.forumId) }
|
||||
.toImmutableList()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -190,8 +212,8 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
@Immutable
|
||||
data class HomeUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val forums: List<Forum> = emptyList(),
|
||||
val topForums: List<Forum> = emptyList(),
|
||||
val forums: ImmutableList<Forum> = persistentListOf(),
|
||||
val topForums: ImmutableList<Forum> = persistentListOf(),
|
||||
val error: Throwable? = null,
|
||||
) : UiState {
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import com.huanchengfly.tieba.post.App
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
|
|
@ -595,7 +596,7 @@ private fun SubPostItem(
|
|||
.padding(start = Sizes.Small + 8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
contentRenders.forEach { it.Render() }
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.huanchengfly.tieba.post.App
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
|
|
@ -1683,7 +1685,7 @@ fun PostCard(
|
|||
)
|
||||
}
|
||||
|
||||
contentRenders.forEach { it.Render() }
|
||||
contentRenders.fastForEach { it.Render() }
|
||||
}
|
||||
|
||||
if (showSubPosts && post.sub_post_number > 0 && subPostContents.isNotEmpty() && !immersiveMode) {
|
||||
|
|
@ -1696,7 +1698,7 @@ fun PostCard(
|
|||
.padding(vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
) {
|
||||
subPostContents.forEachIndexed { index, text ->
|
||||
subPostContents.fastForEachIndexed { index, text ->
|
||||
SubPostItem(
|
||||
subPostList = subPosts[index],
|
||||
subPostContent = text,
|
||||
|
|
|
|||
|
|
@ -54,10 +54,12 @@ import androidx.compose.ui.text.withStyle
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.util.fastForEachIndexed
|
||||
import com.google.accompanist.placeholder.PlaceholderHighlight
|
||||
import com.google.accompanist.placeholder.material.fade
|
||||
import com.google.accompanist.placeholder.material.placeholder
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
import com.huanchengfly.tieba.post.App
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
import com.huanchengfly.tieba.post.api.models.protos.Media
|
||||
|
|
@ -85,14 +87,14 @@ import kotlin.math.max
|
|||
import kotlin.math.min
|
||||
|
||||
private val ImmutableHolder<Media>.url: String
|
||||
@Composable get() =
|
||||
ImageUtil.getUrl(
|
||||
LocalContext.current,
|
||||
get() = ImageUtil.getUrl(
|
||||
App.INSTANCE,
|
||||
true,
|
||||
get { originPic },
|
||||
get { dynamicPic },
|
||||
get { bigPic },
|
||||
get { srcPic })
|
||||
get { srcPic }
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun DefaultUserHeader(
|
||||
|
|
@ -329,45 +331,57 @@ private fun ThreadMedia(
|
|||
val medias = remember(item) {
|
||||
item.getImmutableList { media }
|
||||
}
|
||||
val singleMediaFraction =
|
||||
if (LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact)
|
||||
val hasMedia = remember(medias) { medias.isNotEmpty() }
|
||||
val isSingleMedia = remember(medias) { medias.size == 1 }
|
||||
val windowWidthSizeClass = LocalWindowSizeClass.current.widthSizeClass
|
||||
val singleMediaFraction = remember(windowWidthSizeClass) {
|
||||
if (windowWidthSizeClass == WindowWidthSizeClass.Compact)
|
||||
1f
|
||||
else 0.6f
|
||||
|
||||
}
|
||||
|
||||
if (isVideo) {
|
||||
val videoInfo = item.getImmutable { videoInfo!! }
|
||||
VideoPlayer(
|
||||
videoUrl = videoInfo.get { videoUrl },
|
||||
thumbnailUrl = videoInfo.get { thumbnailUrl },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(singleMediaFraction)
|
||||
.aspectRatio(
|
||||
val videoInfo = remember(item) { item.getImmutable { videoInfo!! } }
|
||||
val aspectRatio = remember(videoInfo) {
|
||||
max(
|
||||
videoInfo
|
||||
.get { thumbnailWidth }
|
||||
.toFloat() / videoInfo.get { thumbnailHeight },
|
||||
16f / 9
|
||||
)
|
||||
)
|
||||
}
|
||||
VideoPlayer(
|
||||
videoUrl = videoInfo.get { videoUrl },
|
||||
thumbnailUrl = videoInfo.get { thumbnailUrl },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(singleMediaFraction)
|
||||
.aspectRatio(aspectRatio)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
)
|
||||
} else if (medias.isNotEmpty()) {
|
||||
} else if (hasMedia) {
|
||||
val mediaWidthFraction = remember(isSingleMedia, singleMediaFraction) {
|
||||
if (isSingleMedia) singleMediaFraction else 1f
|
||||
}
|
||||
val mediaAspectRatio = remember(isSingleMedia) {
|
||||
if (isSingleMedia) 2f else 3f
|
||||
}
|
||||
val showMediaCount = remember(medias) { min(medias.size, 3) }
|
||||
val hasMoreMedia = remember(medias) { medias.size > 3 }
|
||||
val showMedias = remember(medias) { medias.subList(0, showMediaCount) }
|
||||
Box {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(if (medias.size == 1) singleMediaFraction else 1f)
|
||||
.aspectRatio(if (medias.size == 1) 2f else 3f)
|
||||
.fillMaxWidth(mediaWidthFraction)
|
||||
.aspectRatio(mediaAspectRatio)
|
||||
.clip(RoundedCornerShape(8.dp)),
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
medias.subList(0, min(medias.size, 3))
|
||||
.forEachIndexed { index, media ->
|
||||
showMedias.fastForEachIndexed { index, media ->
|
||||
val photoViewData = remember(item, index) {
|
||||
getImmutablePhotoViewData(item.get(), index)
|
||||
}
|
||||
NetworkImage(
|
||||
imageUri = media.url,
|
||||
imageUri = remember(media) { media.url },
|
||||
contentDescription = null,
|
||||
modifier = Modifier.weight(1f),
|
||||
photoViewData = photoViewData,
|
||||
|
|
@ -375,7 +389,7 @@ private fun ThreadMedia(
|
|||
)
|
||||
}
|
||||
}
|
||||
if (medias.size > 3) {
|
||||
if (hasMoreMedia) {
|
||||
Badge(
|
||||
icon = Icons.Rounded.PhotoSizeSelectActual,
|
||||
text = "${medias.size}",
|
||||
|
|
@ -396,7 +410,8 @@ private fun ThreadForumInfo(
|
|||
val hasForumInfo = remember(item) { item.isNotNull { forumInfo } }
|
||||
if (hasForumInfo) {
|
||||
val forumInfo = remember(item) { item.getImmutable { forumInfo!! } }
|
||||
if (forumInfo.get { name }.isNotBlank()) {
|
||||
val hasForum = remember(forumInfo) { forumInfo.get { name }.isNotBlank() }
|
||||
if (hasForum) {
|
||||
ForumInfoChip(
|
||||
imageUriProvider = { StringUtil.getAvatarUrl(forumInfo.get { avatar }) },
|
||||
nameProvider = { forumInfo.get { name } },
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ import android.os.Parcelable
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
|
@ -38,11 +40,14 @@ fun NetworkImage(
|
|||
}
|
||||
}
|
||||
} else Modifier
|
||||
AsyncImage(
|
||||
request = DisplayRequest(context, imageUri) {
|
||||
val request = remember(imageUri) {
|
||||
DisplayRequest(context, imageUri) {
|
||||
placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
crossfade()
|
||||
},
|
||||
}
|
||||
}
|
||||
AsyncImage(
|
||||
request = request,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.then(clickableModifier),
|
||||
contentScale = contentScale,
|
||||
|
|
@ -57,29 +62,14 @@ fun NetworkImage(
|
|||
photoViewDataProvider: (() -> ImmutableHolder<PhotoViewData>)? = null,
|
||||
contentScale: ContentScale = ContentScale.Fit,
|
||||
) {
|
||||
val imageUri = imageUriProvider()
|
||||
val photoViewData = photoViewDataProvider?.invoke()
|
||||
val imageUri by rememberUpdatedState(newValue = imageUriProvider())
|
||||
val photoViewData by rememberUpdatedState(newValue = photoViewDataProvider?.invoke())
|
||||
|
||||
val context = LocalContext.current
|
||||
val clickableModifier = if (photoViewData != null) {
|
||||
Modifier.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember {
|
||||
MutableInteractionSource()
|
||||
}
|
||||
) {
|
||||
context.goToActivity<PhotoViewActivity> {
|
||||
putExtra(EXTRA_PHOTO_VIEW_DATA, photoViewData.get() as Parcelable)
|
||||
}
|
||||
}
|
||||
} else Modifier
|
||||
AsyncImage(
|
||||
request = DisplayRequest(context, imageUri) {
|
||||
placeholder(ImageUtil.getPlaceHolder(context, 0))
|
||||
crossfade()
|
||||
},
|
||||
NetworkImage(
|
||||
imageUri = imageUri,
|
||||
contentDescription = contentDescription,
|
||||
modifier = modifier.then(clickableModifier),
|
||||
modifier = modifier,
|
||||
photoViewData = photoViewData,
|
||||
contentScale = contentScale,
|
||||
)
|
||||
}
|
||||
|
|
@ -20,7 +20,6 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.listSaver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
|
|
@ -118,16 +117,15 @@ fun LongClickMenu(
|
|||
indication: Indication? = LocalIndication.current,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
LaunchedEffect(key1 = null) {
|
||||
coroutineScope.launch {
|
||||
launch {
|
||||
interactionSource.interactions
|
||||
.filterIsInstance<PressInteraction.Press>()
|
||||
.collect {
|
||||
menuState.offset = it.pressPosition
|
||||
}
|
||||
}
|
||||
coroutineScope.launch {
|
||||
launch {
|
||||
interactionSource.interactions
|
||||
.collect {
|
||||
Log.i("Indication", "$it")
|
||||
|
|
@ -172,18 +170,15 @@ fun LongClickMenu(
|
|||
|
||||
@Composable
|
||||
fun rememberMenuState(): MenuState {
|
||||
return rememberSaveable(saver = MenuState.Saver) {
|
||||
MenuState()
|
||||
}
|
||||
return rememberSaveable(
|
||||
saver = MenuState.Saver,
|
||||
init = { MenuState() }
|
||||
)
|
||||
}
|
||||
|
||||
@Stable
|
||||
class MenuState(
|
||||
expanded: Boolean = false,
|
||||
offsetX: Float = 0f,
|
||||
offsetY: Float = 0f,
|
||||
) {
|
||||
private var _expanded by mutableStateOf(expanded)
|
||||
class MenuState {
|
||||
private var _expanded by mutableStateOf(false)
|
||||
|
||||
var expanded: Boolean
|
||||
get() = _expanded
|
||||
|
|
@ -193,7 +188,7 @@ class MenuState(
|
|||
}
|
||||
}
|
||||
|
||||
private var _offset by mutableStateOf(Offset(offsetX, offsetY))
|
||||
private var _offset by mutableStateOf(Offset(0f, 0f))
|
||||
|
||||
var offset: Offset
|
||||
get() = _offset
|
||||
|
|
@ -213,11 +208,10 @@ class MenuState(
|
|||
)
|
||||
},
|
||||
restore = {
|
||||
MenuState(
|
||||
expanded = it[0] as Boolean,
|
||||
offsetX = it[1] as Float,
|
||||
offsetY = it[2] as Float,
|
||||
)
|
||||
MenuState().apply {
|
||||
expanded = it[0] as Boolean
|
||||
offset = Offset(it[1] as Float, it[2] as Float)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,18 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ProvideContentColor
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
|
||||
@Composable
|
||||
fun <ItemValue> ListSinglePicker(
|
||||
itemTitles: List<String>,
|
||||
itemValues: List<ItemValue>,
|
||||
itemTitles: ImmutableList<String>,
|
||||
itemValues: ImmutableList<ItemValue>,
|
||||
selectedPosition: Int,
|
||||
onItemSelected: (position: Int, title: String, value: ItemValue, changed: Boolean) -> Unit,
|
||||
itemIcons: Map<ItemValue, @Composable () -> Unit> = emptyMap(),
|
||||
modifier: Modifier = Modifier,
|
||||
itemIcons: ImmutableMap<ItemValue, @Composable () -> Unit> = persistentMapOf(),
|
||||
selectedIndicator: @Composable () -> Unit = {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Check,
|
||||
|
|
@ -38,7 +42,6 @@ fun <ItemValue> ListSinglePicker(
|
|||
},
|
||||
colors: PickerColors = PickerDefaults.pickerColors(),
|
||||
enabled: Boolean = true,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (itemTitles.size != itemValues.size) error("titles and values do not match!")
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -63,7 +66,7 @@ fun <ItemValue> ListSinglePicker(
|
|||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
(itemIcons.getOrDefault(itemValues[it]) {}).invoke()
|
||||
itemIcons[itemValues[it]]?.invoke()
|
||||
ProvideContentColor(
|
||||
color = if (selected) colors.selectedItemColor(enabled).value else colors.itemColor(
|
||||
enabled
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import android.widget.Toast
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
|
|
@ -29,6 +30,7 @@ import org.litepal.extension.findAllAsync
|
|||
import org.litepal.extension.findFirst
|
||||
import java.util.UUID
|
||||
|
||||
@Stable
|
||||
object AccountUtil {
|
||||
const val TAG = "AccountUtil"
|
||||
const val ACTION_SWITCH_ACCOUNT = "com.huanchengfly.tieba.post.action.SWITCH_ACCOUNT"
|
||||
|
|
|
|||
Loading…
Reference in New Issue