From 0c750523803153f70bd03b54b01ddb58143e82e7 Mon Sep 17 00:00:00 2001 From: HuanCheng65 <22636177+HuanCheng65@users.noreply.github.com> Date: Sun, 8 Jan 2023 12:06:56 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95?= =?UTF-8?q?=E6=96=B0=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tieba/post/ui/page/forum/ForumPage.kt | 16 ++ .../tieba/post/ui/page/history/HistoryPage.kt | 130 +++++++++ .../ui/page/history/list/HistoryListPage.kt | 248 ++++++++++++++++++ .../page/history/list/HistoryListViewModel.kt | 197 ++++++++++++++ .../tieba/post/ui/page/main/user/UserPage.kt | 4 +- .../tieba/post/utils/HistoryUtil.java | 72 ----- .../tieba/post/utils/HistoryUtil.kt | 93 +++++++ app/src/main/res/values/strings.xml | 2 + 8 files changed, 688 insertions(+), 74 deletions(-) create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/HistoryPage.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListViewModel.kt delete mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.java create mode 100644 app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.kt diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/ForumPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/ForumPage.kt index c58d9755..efc5ad6d 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/ForumPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/forum/ForumPage.kt @@ -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, diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/HistoryPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/HistoryPage.kt new file mode 100644 index 00000000..6b2fa95b --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/HistoryPage.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt new file mode 100644 index 00000000..795df75f --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListPage.kt @@ -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() else pageViewModel() +) { + 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(viewModel) { + snackbarHostState.showSnackbar( + context.getString( + R.string.delete_history_failure, + it.errorMsg + ) + ) + } + onEvent(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() 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() 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, + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListViewModel.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListViewModel.kt new file mode 100644 index 00000000..9874fcf4 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/history/list/HistoryListViewModel.kt @@ -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() { + 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 = + HistoryListPartialChangeProducer(HistoryUtil.TYPE_THREAD) +} + +@HiltViewModel +class ForumHistoryListViewModel @Inject constructor() : HistoryListViewModel() { + override fun createPartialChangeProducer(): PartialChangeProducer = + HistoryListPartialChangeProducer(HistoryUtil.TYPE_FORUM) +} + +private class HistoryListPartialChangeProducer(val type: Int) : + PartialChangeProducer { + @OptIn(FlowPreview::class) + override fun toPartialChangeFlow(intentFlow: Flow): Flow = + merge( + intentFlow.filterIsInstance() + .flatMapConcat { produceRefreshPartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, + intentFlow.filterIsInstance() + .flatMapConcat { it.producePartialChange() }, + ) + + private fun produceRefreshPartialChange() = + HistoryUtil.getFlow(type, 0) + .map, 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, 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("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 { + 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, + val beforeHistoryData: List, + 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, + val beforeHistoryData: List, + 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 = emptyList(), + val beforeHistoryData: List = emptyList(), +) : UiState + +sealed interface HistoryListUiEvent : UiEvent { + sealed interface Delete : HistoryListUiEvent { + object Success : Delete + + data class Failure( + val errorMsg: String + ) : Delete + } +} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/user/UserPage.kt b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/user/UserPage.kt index dc83dffb..d5b14025 100644 --- a/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/user/UserPage.kt +++ b/app/src/main/java/com/huanchengfly/tieba/post/ui/page/main/user/UserPage.kt @@ -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() + navigator.navigate(HistoryPageDestination) } ) ListMenuItem( diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.java b/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.java deleted file mode 100644 index 2164d14d..00000000 --- a/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.java +++ /dev/null @@ -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 getAll() { - return LitePal.order("timestamp desc, count desc").limit(100).find(History.class); - } - - public static List getAll(int type) { - return LitePal.order("timestamp desc, count desc").where("type = ?", String.valueOf(type)).limit(100).find(History.class); - } - - public static FindMultiExecutor 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); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.kt b/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.kt new file mode 100644 index 00000000..b4cc0a43 --- /dev/null +++ b/app/src/main/java/com/huanchengfly/tieba/post/utils/HistoryUtil.kt @@ -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 + get() = order("timestamp desc, count desc").limit(100).find( + History::class.java + ) + + fun getAll(type: Int): List { + return order("timestamp desc, count desc").where("type = ?", type.toString()) + .limit(PAGE_SIZE) + .find( + History::class.java + ) + } + + fun getAllAsync(type: Int): FindMultiExecutor { + return order("timestamp desc, count desc").where("type = ?", type.toString()) + .limit(PAGE_SIZE) + .findAsync( + History::class.java + ) + } + + fun getFlow( + type: Int, + page: Int + ): Flow> { + return flow { + delay(100) + emit( + where("type = ?", "$type") + .order("timestamp desc, count desc") + .limit(PAGE_SIZE) + .offset(page * 100) + .find() + ) + }.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() + } + } +} \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 10339c7c..698b6bf0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -626,4 +626,6 @@ 排序菜单 取消收藏成功 取消收藏失败 %s + 删除历史记录失败 %s + 删除历史记录成功