feat: 历史记录新 UI

This commit is contained in:
HuanCheng65 2023-01-08 12:06:56 +08:00
parent 53df4568f3
commit 0c75052380
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
8 changed files with 688 additions and 74 deletions

View File

@ -83,6 +83,7 @@ import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.dataStore import com.huanchengfly.tieba.post.dataStore
import com.huanchengfly.tieba.post.getInt import com.huanchengfly.tieba.post.getInt
import com.huanchengfly.tieba.post.goToActivity 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.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
import com.huanchengfly.tieba.post.ui.page.forum.threadlist.ForumThreadListPage 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.rememberDialogState
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState import com.huanchengfly.tieba.post.ui.widgets.compose.rememberMenuState
import com.huanchengfly.tieba.post.utils.AccountUtil.LocalAccount 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.StringUtil.getShortNumString
import com.huanchengfly.tieba.post.utils.TiebaUtil import com.huanchengfly.tieba.post.utils.TiebaUtil
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
@ -365,6 +367,20 @@ fun ForumPage(
val unlikeDialogState = rememberDialogState() 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) { if (account != null && forum != null) {
ConfirmDialog( ConfirmDialog(
dialogState = unlikeDialogState, dialogState = unlikeDialogState,

View File

@ -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)
}
}
}
}

View File

@ -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,
)
}
}
}
}

View File

@ -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
}
}

View File

@ -47,7 +47,6 @@ import androidx.compose.ui.unit.sp
import com.google.accompanist.placeholder.placeholder import com.google.accompanist.placeholder.placeholder
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.AppThemeActivity 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.UserActivity
import com.huanchengfly.tieba.post.activities.WebViewActivity import com.huanchengfly.tieba.post.activities.WebViewActivity
import com.huanchengfly.tieba.post.arch.collectPartialAsState 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.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.LocalNavigator 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.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.SettingsPageDestination
import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination import com.huanchengfly.tieba.post.ui.page.destinations.ThreadStorePageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar 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), icon = ImageVector.vectorResource(id = R.drawable.ic_outline_watch_later_24),
text = stringResource(id = R.string.title_history), text = stringResource(id = R.string.title_history),
onClick = { onClick = {
context.goToActivity<HistoryActivity>() navigator.navigate(HistoryPageDestination)
} }
) )
ListMenuItem( ListMenuItem(

View File

@ -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);
}
}

View File

@ -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()
}
}
}

View File

@ -626,4 +626,6 @@
<string name="sort_menu">排序菜单</string> <string name="sort_menu">排序菜单</string>
<string name="delete_store_success">取消收藏成功</string> <string name="delete_store_success">取消收藏成功</string>
<string name="delete_store_failure">取消收藏失败 %s</string> <string name="delete_store_failure">取消收藏失败 %s</string>
<string name="delete_history_failure">删除历史记录失败 %s</string>
<string name="delete_history_success">删除历史记录成功</string>
</resources> </resources>