feat: 历史记录新 UI
This commit is contained in:
parent
53df4568f3
commit
0c75052380
|
|
@ -83,6 +83,7 @@ import com.huanchengfly.tieba.post.arch.pageViewModel
|
|||
import com.huanchengfly.tieba.post.dataStore
|
||||
import com.huanchengfly.tieba.post.getInt
|
||||
import com.huanchengfly.tieba.post.goToActivity
|
||||
import com.huanchengfly.tieba.post.models.database.History
|
||||
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.forum.threadlist.ForumThreadListPage
|
||||
|
|
@ -102,6 +103,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.picker.ListSinglePicker
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
|
||||
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount
|
||||
import com.huanchengfly.tieba.post.utils.HistoryUtil
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
|
||||
import com.huanchengfly.tieba.post.utils.TiebaUtil
|
||||
import com.huanchengfly.tieba.post.utils.appPreferences
|
||||
|
|
@ -365,6 +367,20 @@ fun ForumPage(
|
|||
|
||||
val unlikeDialogState = rememberDialogState()
|
||||
|
||||
if (forum != null) {
|
||||
LaunchedEffect(forum) {
|
||||
HistoryUtil.writeHistory(
|
||||
History()
|
||||
.setTitle(context.getString(R.string.title_forum, forumName))
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.setAvatar(forum.avatar)
|
||||
.setType(HistoryUtil.TYPE_FORUM)
|
||||
.setData(forumName),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (account != null && forum != null) {
|
||||
ConfirmDialog(
|
||||
dialogState = unlikeDialogState,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,130 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.history
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Tab
|
||||
import androidx.compose.material.TabRow
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Delete
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.history.list.HistoryListPage
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BackNavigationIcon
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.PagerTabIndicator
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
|
||||
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.launch
|
||||
|
||||
@OptIn(ExperimentalPagerApi::class)
|
||||
@Destination(
|
||||
deepLinks = [
|
||||
DeepLink(uriPattern = "tblite://history")
|
||||
]
|
||||
)
|
||||
@Composable
|
||||
fun HistoryPage(
|
||||
navigator: DestinationsNavigator
|
||||
) {
|
||||
val pagerState = rememberPagerState()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
MyScaffold(
|
||||
topBar = {
|
||||
TitleCentredToolbar(
|
||||
title = stringResource(id = R.string.title_history),
|
||||
navigationIcon = {
|
||||
BackNavigationIcon(onBackPressed = { navigator.navigateUp() })
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Delete,
|
||||
contentDescription = stringResource(id = R.string.title_history_delete),
|
||||
tint = ExtendedTheme.colors.onTopBar
|
||||
)
|
||||
}
|
||||
}
|
||||
) {
|
||||
TabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
indicator = { tabPositions ->
|
||||
PagerTabIndicator(
|
||||
pagerState = pagerState,
|
||||
tabPositions = tabPositions
|
||||
)
|
||||
},
|
||||
divider = {},
|
||||
backgroundColor = ExtendedTheme.colors.topBar,
|
||||
contentColor = ExtendedTheme.colors.accent,
|
||||
modifier = Modifier
|
||||
.width(100.dp * 2)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
) {
|
||||
Tab(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_history_thread),
|
||||
fontSize = 13.sp
|
||||
)
|
||||
},
|
||||
selected = pagerState.currentPage == 0,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(0)
|
||||
}
|
||||
},
|
||||
selectedContentColor = ExtendedTheme.colors.accent,
|
||||
unselectedContentColor = ExtendedTheme.colors.onTopBarSecondary
|
||||
)
|
||||
Tab(
|
||||
text = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_history_forum),
|
||||
fontSize = 13.sp
|
||||
)
|
||||
},
|
||||
selected = pagerState.currentPage == 1,
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
pagerState.animateScrollToPage(1)
|
||||
}
|
||||
},
|
||||
selectedContentColor = ExtendedTheme.colors.accent,
|
||||
unselectedContentColor = ExtendedTheme.colors.onTopBarSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
HorizontalPager(
|
||||
count = 2,
|
||||
state = pagerState,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
userScrollEnabled = true,
|
||||
) {
|
||||
if (it == 0) {
|
||||
HistoryListPage(type = HistoryUtil.TYPE_THREAD)
|
||||
} else {
|
||||
HistoryListPage(type = HistoryUtil.TYPE_FORUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.history.list
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
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
|
||||
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.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.pageViewModel
|
||||
import com.huanchengfly.tieba.post.fromJson
|
||||
import com.huanchengfly.tieba.post.models.ThreadHistoryInfoBean
|
||||
import com.huanchengfly.tieba.post.models.database.History
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
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.LocalSnackbarHostState
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
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
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun HistoryListPage(
|
||||
type: Int,
|
||||
viewModel: HistoryListViewModel = if (type == HistoryUtil.TYPE_THREAD) pageViewModel<ThreadHistoryListViewModel>() else pageViewModel<ForumHistoryListViewModel>()
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
viewModel.send(HistoryListUiIntent.Refresh)
|
||||
viewModel.initialized = true
|
||||
}
|
||||
val isLoadingMore by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::isLoadingMore,
|
||||
initial = false
|
||||
)
|
||||
val hasMore by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::hasMore,
|
||||
initial = true
|
||||
)
|
||||
val currentPage by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::currentPage,
|
||||
initial = 0
|
||||
)
|
||||
val todayHistoryData by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::todayHistoryData,
|
||||
initial = emptyList()
|
||||
)
|
||||
val beforeHistoryData by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HistoryListUiState::beforeHistoryData,
|
||||
initial = emptyList()
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
LaunchedEffect(null) {
|
||||
onEvent<HistoryListUiEvent.Delete.Failure>(viewModel) {
|
||||
snackbarHostState.showSnackbar(
|
||||
context.getString(
|
||||
R.string.delete_history_failure,
|
||||
it.errorMsg
|
||||
)
|
||||
)
|
||||
}
|
||||
onEvent<HistoryListUiEvent.Delete.Success>(viewModel) {
|
||||
snackbarHostState.showSnackbar(context.getString(R.string.delete_history_success))
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
LoadMoreLayout(
|
||||
isLoading = isLoadingMore,
|
||||
onLoadMore = { viewModel.send(HistoryListUiIntent.LoadMore(currentPage + 1)) },
|
||||
loadEnd = !hasMore
|
||||
) {
|
||||
LazyColumn {
|
||||
if (todayHistoryData.isNotEmpty()) {
|
||||
stickyHeader(key = "TodayHistoryHeader") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(ExtendedTheme.colors.background)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Chip(
|
||||
text = stringResource(id = R.string.title_history_today),
|
||||
invertColor = true
|
||||
)
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = todayHistoryData,
|
||||
key = { it.id }
|
||||
) { info ->
|
||||
HistoryItem(
|
||||
info,
|
||||
onDelete = {
|
||||
viewModel.send(HistoryListUiIntent.Delete(it.id))
|
||||
},
|
||||
onClick = {
|
||||
when (it.type) {
|
||||
HistoryUtil.TYPE_FORUM -> ForumActivity.launch(
|
||||
context,
|
||||
it.data
|
||||
)
|
||||
|
||||
HistoryUtil.TYPE_THREAD -> {
|
||||
val extra =
|
||||
if (it.extras != null) it.extras.fromJson<ThreadHistoryInfoBean>() else null
|
||||
ThreadActivity.launch(
|
||||
context,
|
||||
it.data,
|
||||
extra?.pid,
|
||||
extra?.isSeeLz,
|
||||
ThreadActivity.FROM_HISTORY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
if (beforeHistoryData.isNotEmpty()) {
|
||||
stickyHeader(key = "BeforeHistoryHeader") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(ExtendedTheme.colors.background)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||
) {
|
||||
Chip(text = stringResource(id = R.string.title_history_before))
|
||||
}
|
||||
}
|
||||
items(
|
||||
items = beforeHistoryData,
|
||||
key = { it.id }
|
||||
) { info ->
|
||||
HistoryItem(
|
||||
info,
|
||||
onDelete = {
|
||||
viewModel.send(HistoryListUiIntent.Delete(it.id))
|
||||
},
|
||||
onClick = {
|
||||
when (it.type) {
|
||||
HistoryUtil.TYPE_FORUM -> ForumActivity.launch(
|
||||
context,
|
||||
it.data
|
||||
)
|
||||
|
||||
HistoryUtil.TYPE_THREAD -> {
|
||||
val extra =
|
||||
if (it.extras != null) it.extras.fromJson<ThreadHistoryInfoBean>() else null
|
||||
ThreadActivity.launch(
|
||||
context,
|
||||
it.data,
|
||||
extra?.pid,
|
||||
extra?.isSeeLz,
|
||||
ThreadActivity.FROM_HISTORY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HistoryItem(
|
||||
info: History,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: (History) -> Unit = {},
|
||||
onDelete: (History) -> Unit = {},
|
||||
) {
|
||||
val menuState = rememberMenuState()
|
||||
LongClickMenu(
|
||||
menuState = menuState,
|
||||
menuContent = {
|
||||
DropdownMenuItem(onClick = {
|
||||
onDelete(info)
|
||||
menuState.expanded = false
|
||||
}) {
|
||||
Text(text = stringResource(id = R.string.title_delete))
|
||||
}
|
||||
},
|
||||
onClick = { onClick(info) }
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
UserHeader(
|
||||
avatar = {
|
||||
Avatar(
|
||||
data = info.avatar,
|
||||
size = Sizes.Small,
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
name = { Text(text = if (info.type == HistoryUtil.TYPE_THREAD) info.username else info.title) },
|
||||
) {
|
||||
Text(
|
||||
text = DateTimeUtils.getRelativeTimeString(
|
||||
LocalContext.current,
|
||||
info.timestamp
|
||||
),
|
||||
fontSize = 15.sp,
|
||||
color = ExtendedTheme.colors.text,
|
||||
)
|
||||
}
|
||||
if (info.type == HistoryUtil.TYPE_THREAD) {
|
||||
Text(
|
||||
text = info.title,
|
||||
fontSize = 15.sp,
|
||||
color = ExtendedTheme.colors.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.history.list
|
||||
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||
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 com.huanchengfly.tieba.post.models.database.History
|
||||
import com.huanchengfly.tieba.post.utils.DateTimeUtils
|
||||
import com.huanchengfly.tieba.post.utils.HistoryUtil
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
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
|
||||
import org.litepal.extension.deleteAll
|
||||
import javax.inject.Inject
|
||||
|
||||
abstract class HistoryListViewModel :
|
||||
BaseViewModel<HistoryListUiIntent, HistoryListPartialChange, HistoryListUiState, HistoryListUiEvent>() {
|
||||
override fun createInitialState(): HistoryListUiState = HistoryListUiState()
|
||||
|
||||
override fun dispatchEvent(partialChange: HistoryListPartialChange): UiEvent? {
|
||||
return when (partialChange) {
|
||||
is HistoryListPartialChange.Delete.Success -> HistoryListUiEvent.Delete.Success
|
||||
is HistoryListPartialChange.Delete.Failure -> HistoryListUiEvent.Delete.Failure(
|
||||
partialChange.error.getErrorMessage()
|
||||
)
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@HiltViewModel
|
||||
class ThreadHistoryListViewModel @Inject constructor() : HistoryListViewModel() {
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<HistoryListUiIntent, HistoryListPartialChange, HistoryListUiState> =
|
||||
HistoryListPartialChangeProducer(HistoryUtil.TYPE_THREAD)
|
||||
}
|
||||
|
||||
@HiltViewModel
|
||||
class ForumHistoryListViewModel @Inject constructor() : HistoryListViewModel() {
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<HistoryListUiIntent, HistoryListPartialChange, HistoryListUiState> =
|
||||
HistoryListPartialChangeProducer(HistoryUtil.TYPE_FORUM)
|
||||
}
|
||||
|
||||
private class HistoryListPartialChangeProducer(val type: Int) :
|
||||
PartialChangeProducer<HistoryListUiIntent, HistoryListPartialChange, HistoryListUiState> {
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun toPartialChangeFlow(intentFlow: Flow<HistoryListUiIntent>): Flow<HistoryListPartialChange> =
|
||||
merge(
|
||||
intentFlow.filterIsInstance<HistoryListUiIntent.Refresh>()
|
||||
.flatMapConcat { produceRefreshPartialChange() },
|
||||
intentFlow.filterIsInstance<HistoryListUiIntent.LoadMore>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
intentFlow.filterIsInstance<HistoryListUiIntent.Delete>()
|
||||
.flatMapConcat { it.producePartialChange() },
|
||||
)
|
||||
|
||||
private fun produceRefreshPartialChange() =
|
||||
HistoryUtil.getFlow(type, 0)
|
||||
.map<List<History>, HistoryListPartialChange.Refresh> { histories ->
|
||||
HistoryListPartialChange.Refresh.Success(
|
||||
histories.filter { DateTimeUtils.isToday(it.timestamp) },
|
||||
histories.filterNot { DateTimeUtils.isToday(it.timestamp) },
|
||||
histories.size == HistoryUtil.PAGE_SIZE,
|
||||
)
|
||||
}
|
||||
.catch { HistoryListPartialChange.Refresh.Failure(it) }
|
||||
|
||||
private fun HistoryListUiIntent.LoadMore.producePartialChange() =
|
||||
HistoryUtil.getFlow(type, page)
|
||||
.map<List<History>, HistoryListPartialChange.LoadMore> { histories ->
|
||||
HistoryListPartialChange.LoadMore.Success(
|
||||
histories.filter { DateTimeUtils.isToday(it.timestamp) },
|
||||
histories.filterNot { DateTimeUtils.isToday(it.timestamp) },
|
||||
histories.size == HistoryUtil.PAGE_SIZE,
|
||||
page
|
||||
)
|
||||
}
|
||||
.onStart { HistoryListPartialChange.LoadMore.Start }
|
||||
.catch { HistoryListPartialChange.LoadMore.Failure(it) }
|
||||
|
||||
private fun HistoryListUiIntent.Delete.producePartialChange() =
|
||||
flow { emit(LitePal.deleteAll<History>("id = ?", "$id")) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.map {
|
||||
if (it > 0) HistoryListPartialChange.Delete.Success(id)
|
||||
else HistoryListPartialChange.Delete.Failure(IllegalStateException("未知错误"))
|
||||
}
|
||||
.catch { emit(HistoryListPartialChange.Delete.Failure(it)) }
|
||||
}
|
||||
|
||||
sealed interface HistoryListUiIntent : UiIntent {
|
||||
object Refresh : HistoryListUiIntent
|
||||
|
||||
data class LoadMore(val page: Int) : HistoryListUiIntent
|
||||
|
||||
data class Delete(val id: Int) : HistoryListUiIntent
|
||||
}
|
||||
|
||||
sealed interface HistoryListPartialChange : PartialChange<HistoryListUiState> {
|
||||
sealed class Refresh : HistoryListPartialChange {
|
||||
override fun reduce(oldState: HistoryListUiState): HistoryListUiState = when (this) {
|
||||
is Failure -> oldState
|
||||
is Success -> oldState.copy(
|
||||
todayHistoryData = todayHistoryData,
|
||||
beforeHistoryData = beforeHistoryData,
|
||||
currentPage = 0,
|
||||
hasMore = hasMore
|
||||
)
|
||||
}
|
||||
|
||||
data class Success(
|
||||
val todayHistoryData: List<History>,
|
||||
val beforeHistoryData: List<History>,
|
||||
val hasMore: Boolean
|
||||
) : Refresh()
|
||||
|
||||
data class Failure(
|
||||
val error: Throwable
|
||||
) : Refresh()
|
||||
}
|
||||
|
||||
sealed class LoadMore : HistoryListPartialChange {
|
||||
override fun reduce(oldState: HistoryListUiState): HistoryListUiState = when (this) {
|
||||
is Failure -> oldState.copy(isLoadingMore = false)
|
||||
Start -> oldState.copy(isLoadingMore = true)
|
||||
is Success -> oldState.copy(
|
||||
isLoadingMore = false,
|
||||
todayHistoryData = oldState.todayHistoryData + todayHistoryData,
|
||||
beforeHistoryData = oldState.beforeHistoryData + beforeHistoryData,
|
||||
currentPage = currentPage,
|
||||
hasMore = hasMore
|
||||
)
|
||||
}
|
||||
|
||||
object Start : LoadMore()
|
||||
|
||||
data class Success(
|
||||
val todayHistoryData: List<History>,
|
||||
val beforeHistoryData: List<History>,
|
||||
val hasMore: Boolean,
|
||||
val currentPage: Int
|
||||
) : LoadMore()
|
||||
|
||||
data class Failure(
|
||||
val error: Throwable
|
||||
) : LoadMore()
|
||||
}
|
||||
|
||||
sealed class Delete : HistoryListPartialChange {
|
||||
override fun reduce(oldState: HistoryListUiState): HistoryListUiState = when (this) {
|
||||
is Failure -> oldState
|
||||
is Success -> oldState.copy(
|
||||
todayHistoryData = oldState.todayHistoryData.filterNot { it.id == id },
|
||||
beforeHistoryData = oldState.beforeHistoryData.filterNot { it.id == id })
|
||||
}
|
||||
|
||||
data class Success(
|
||||
val id: Int
|
||||
) : Delete()
|
||||
|
||||
data class Failure(
|
||||
val error: Throwable
|
||||
) : Delete()
|
||||
}
|
||||
}
|
||||
|
||||
data class HistoryListUiState(
|
||||
val isRefreshing: Boolean = false,
|
||||
val isLoadingMore: Boolean = false,
|
||||
val hasMore: Boolean = true,
|
||||
val currentPage: Int = 0,
|
||||
val todayHistoryData: List<History> = emptyList(),
|
||||
val beforeHistoryData: List<History> = emptyList(),
|
||||
) : UiState
|
||||
|
||||
sealed interface HistoryListUiEvent : UiEvent {
|
||||
sealed interface Delete : HistoryListUiEvent {
|
||||
object Success : Delete
|
||||
|
||||
data class Failure(
|
||||
val errorMsg: String
|
||||
) : Delete
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,6 @@ import androidx.compose.ui.unit.sp
|
|||
import com.google.accompanist.placeholder.placeholder
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.activities.AppThemeActivity
|
||||
import com.huanchengfly.tieba.post.activities.HistoryActivity
|
||||
import com.huanchengfly.tieba.post.activities.UserActivity
|
||||
import com.huanchengfly.tieba.post.activities.WebViewActivity
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
|
|
@ -57,6 +56,7 @@ import com.huanchengfly.tieba.post.models.database.Account
|
|||
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.AboutPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.HistoryPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.SettingsPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
|
|
@ -319,7 +319,7 @@ fun UserPage(
|
|||
icon = ImageVector.vectorResource(id = R.drawable.ic_outline_watch_later_24),
|
||||
text = stringResource(id = R.string.title_history),
|
||||
onClick = {
|
||||
context.goToActivity<HistoryActivity>()
|
||||
navigator.navigate(HistoryPageDestination)
|
||||
}
|
||||
)
|
||||
ListMenuItem(
|
||||
|
|
|
|||
|
|
@ -1,72 +0,0 @@
|
|||
package com.huanchengfly.tieba.post.utils;
|
||||
|
||||
import com.huanchengfly.tieba.post.models.database.History;
|
||||
|
||||
import org.litepal.LitePal;
|
||||
import org.litepal.crud.async.FindMultiExecutor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public final class HistoryUtil {
|
||||
public static final int TYPE_FORUM = 1;
|
||||
public static final int TYPE_THREAD = 2;
|
||||
|
||||
private HistoryUtil() {
|
||||
}
|
||||
|
||||
public static void deleteAll() {
|
||||
LitePal.deleteAll(History.class);
|
||||
}
|
||||
|
||||
public static void writeHistory(History history) {
|
||||
writeHistory(history, false);
|
||||
}
|
||||
|
||||
public static void writeHistory(History history, boolean async) {
|
||||
add(history, async);
|
||||
}
|
||||
|
||||
public static List<History> getAll() {
|
||||
return LitePal.order("timestamp desc, count desc").limit(100).find(History.class);
|
||||
}
|
||||
|
||||
public static List<History> getAll(int type) {
|
||||
return LitePal.order("timestamp desc, count desc").where("type = ?", String.valueOf(type)).limit(100).find(History.class);
|
||||
}
|
||||
|
||||
public static FindMultiExecutor<History> getAllAsync(int type) {
|
||||
return LitePal.order("timestamp desc, count desc").where("type = ?", String.valueOf(type)).limit(100).findAsync(History.class);
|
||||
}
|
||||
|
||||
private static boolean update(History history) {
|
||||
History historyBean = LitePal.where("data = ?", history.getData()).findFirst(History.class);
|
||||
if (historyBean != null) {
|
||||
historyBean.setTimestamp(System.currentTimeMillis())
|
||||
.setTitle(history.getTitle())
|
||||
.setExtras(history.getExtras())
|
||||
.setAvatar(history.getAvatar())
|
||||
.setUsername(history.getUsername())
|
||||
.setCount(historyBean.getCount() + 1)
|
||||
.update(historyBean.getId());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void add(History history, boolean async) {
|
||||
if (update(history)) {
|
||||
return;
|
||||
}
|
||||
history.setCount(1)
|
||||
.setTimestamp(System.currentTimeMillis());
|
||||
if (async) {
|
||||
history.saveAsync().listen(null);
|
||||
} else {
|
||||
history.save();
|
||||
}
|
||||
}
|
||||
|
||||
private static void add(History history) {
|
||||
add(history, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.huanchengfly.tieba.post.utils
|
||||
|
||||
import com.huanchengfly.tieba.post.models.database.History
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import org.litepal.LitePal.deleteAll
|
||||
import org.litepal.LitePal.order
|
||||
import org.litepal.LitePal.where
|
||||
import org.litepal.crud.async.FindMultiExecutor
|
||||
import org.litepal.extension.find
|
||||
|
||||
object HistoryUtil {
|
||||
const val PAGE_SIZE = 100
|
||||
const val TYPE_FORUM = 1
|
||||
const val TYPE_THREAD = 2
|
||||
fun deleteAll() {
|
||||
deleteAll(History::class.java)
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun writeHistory(history: History, async: Boolean = false) {
|
||||
add(history, async)
|
||||
}
|
||||
|
||||
val all: List<History>
|
||||
get() = order("timestamp desc, count desc").limit(100).find(
|
||||
History::class.java
|
||||
)
|
||||
|
||||
fun getAll(type: Int): List<History> {
|
||||
return order("timestamp desc, count desc").where("type = ?", type.toString())
|
||||
.limit(PAGE_SIZE)
|
||||
.find(
|
||||
History::class.java
|
||||
)
|
||||
}
|
||||
|
||||
fun getAllAsync(type: Int): FindMultiExecutor<History> {
|
||||
return order("timestamp desc, count desc").where("type = ?", type.toString())
|
||||
.limit(PAGE_SIZE)
|
||||
.findAsync(
|
||||
History::class.java
|
||||
)
|
||||
}
|
||||
|
||||
fun getFlow(
|
||||
type: Int,
|
||||
page: Int
|
||||
): Flow<List<History>> {
|
||||
return flow {
|
||||
delay(100)
|
||||
emit(
|
||||
where("type = ?", "$type")
|
||||
.order("timestamp desc, count desc")
|
||||
.limit(PAGE_SIZE)
|
||||
.offset(page * 100)
|
||||
.find<History>()
|
||||
)
|
||||
}.flowOn(Dispatchers.IO)
|
||||
}
|
||||
|
||||
private fun update(history: History): Boolean {
|
||||
val historyBean = where("data = ?", history.data).findFirst(
|
||||
History::class.java
|
||||
)
|
||||
if (historyBean != null) {
|
||||
historyBean.setTimestamp(System.currentTimeMillis())
|
||||
.setTitle(history.title)
|
||||
.setExtras(history.extras)
|
||||
.setAvatar(history.avatar)
|
||||
.setUsername(history.username)
|
||||
.setCount(historyBean.count + 1)
|
||||
.update(historyBean.id.toLong())
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun add(history: History, async: Boolean = false) {
|
||||
if (update(history)) {
|
||||
return
|
||||
}
|
||||
history.setCount(1).timestamp = System.currentTimeMillis()
|
||||
if (async) {
|
||||
history.saveAsync().listen(null)
|
||||
} else {
|
||||
history.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -626,4 +626,6 @@
|
|||
<string name="sort_menu">排序菜单</string>
|
||||
<string name="delete_store_success">取消收藏成功</string>
|
||||
<string name="delete_store_failure">取消收藏失败 %s</string>
|
||||
<string name="delete_history_failure">删除历史记录失败 %s</string>
|
||||
<string name="delete_history_success">删除历史记录成功</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue