Refactor(ToolStore): Optimize the way to obtain installation status

This commit is contained in:
2024-08-13 18:04:49 +08:00
parent 60ffc569a5
commit 5246715d78
7 changed files with 41 additions and 53 deletions

View File

@@ -2,11 +2,11 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "adfae7fd1829b1afdfd27eb282388074", "identityHash": "34c5a37d790e5542a93e0dc27bb3f4f1",
"entities": [ "entities": [
{ {
"tableName": "tools", "tableName": "tools",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `toolId` TEXT NOT NULL, `icon` TEXT NOT NULL, `platform` TEXT NOT NULL, `description` TEXT, `base` TEXT, `authorUsername` TEXT NOT NULL, `authorNickname` TEXT NOT NULL, `authorAvatar` TEXT NOT NULL, `ver` TEXT NOT NULL, `keywords` TEXT NOT NULL, `categories` TEXT NOT NULL, `source` TEXT, `dist` TEXT, `entryPoint` TEXT NOT NULL, `createTime` TEXT NOT NULL, `updateTime` TEXT NOT NULL, `isStar` INTEGER NOT NULL DEFAULT false, `upgrade` TEXT DEFAULT NULL, PRIMARY KEY(`id`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `toolId` TEXT NOT NULL, `icon` TEXT NOT NULL, `platform` TEXT NOT NULL, `description` TEXT, `base` TEXT, `authorUsername` TEXT NOT NULL, `authorNickname` TEXT NOT NULL, `authorAvatar` TEXT NOT NULL, `ver` TEXT NOT NULL, `keywords` TEXT NOT NULL, `categories` TEXT NOT NULL, `source` TEXT, `dist` TEXT, `entryPoint` TEXT NOT NULL, `createTime` TEXT NOT NULL, `updateTime` TEXT NOT NULL, `isStar` INTEGER NOT NULL, `isInstalled` INTEGER NOT NULL, `upgrade` TEXT DEFAULT NULL, PRIMARY KEY(`id`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@@ -120,8 +120,13 @@
"fieldPath": "isStar", "fieldPath": "isStar",
"columnName": "isStar", "columnName": "isStar",
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true, "notNull": true
"defaultValue": "false" },
{
"fieldPath": "isInstalled",
"columnName": "isInstalled",
"affinity": "INTEGER",
"notNull": true
}, },
{ {
"fieldPath": "upgrade", "fieldPath": "upgrade",
@@ -144,7 +149,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'adfae7fd1829b1afdfd27eb282388074')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '34c5a37d790e5542a93e0dc27bb3f4f1')"
] ]
} }
} }

View File

@@ -47,9 +47,10 @@ data class ToolEntity(
val updateTime: LocalDateTime, val updateTime: LocalDateTime,
@ColumnInfo(defaultValue = "false")
val isStar: Boolean = false, val isStar: Boolean = false,
val isInstalled: Boolean = false,
@ColumnInfo(defaultValue = "NULL") @ColumnInfo(defaultValue = "NULL")
val upgrade: String? = null val upgrade: String? = null
) { ) {

View File

@@ -2,13 +2,16 @@ package top.fatweb.oxygen.toolbox.network.paging
import androidx.paging.PagingSource import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import kotlinx.coroutines.flow.first
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
import top.fatweb.oxygen.toolbox.network.model.ToolVo import top.fatweb.oxygen.toolbox.network.model.ToolVo
import top.fatweb.oxygen.toolbox.network.model.asExternalModel import top.fatweb.oxygen.toolbox.network.model.asExternalModel
internal class ToolStorePagingSource( internal class ToolStorePagingSource(
private val oxygenNetworkDataSource: OxygenNetworkDataSource, private val oxygenNetworkDataSource: OxygenNetworkDataSource,
private val toolDao: ToolDao,
private val searchValue: String private val searchValue: String
) : PagingSource<Int, ToolEntity>() { ) : PagingSource<Int, ToolEntity>() {
override fun getRefreshKey(state: PagingState<Int, ToolEntity>): Int? = null override fun getRefreshKey(state: PagingState<Int, ToolEntity>): Int? = null
@@ -25,7 +28,20 @@ internal class ToolStorePagingSource(
val nextPage = if (currentPage < pages) currentPage + 1 else null val nextPage = if (currentPage < pages) currentPage + 1 else null
LoadResult.Page( LoadResult.Page(
data = records.map(ToolVo::asExternalModel), data = records.map(ToolVo::asExternalModel).map { toolEntity ->
toolDao.selectToolByUsernameAndToolId(
toolEntity.authorUsername,
toolEntity.toolId
).first()?.let {
if (it.id == toolEntity.id) {
it
} else {
it.copy(upgrade = toolEntity.ver).also { copy ->
toolDao.updateTool(copy)
}
}
} ?: toolEntity
},
prevKey = null, prevKey = null,
nextKey = nextPage nextKey = nextPage
) )

View File

@@ -6,6 +6,7 @@ import androidx.paging.PagingData
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource import top.fatweb.oxygen.toolbox.data.network.OxygenNetworkDataSource
import top.fatweb.oxygen.toolbox.data.tool.dao.ToolDao
import top.fatweb.oxygen.toolbox.model.Result import top.fatweb.oxygen.toolbox.model.Result
import top.fatweb.oxygen.toolbox.model.asExternalModel import top.fatweb.oxygen.toolbox.model.asExternalModel
import top.fatweb.oxygen.toolbox.model.tool.ToolEntity import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
@@ -19,7 +20,8 @@ import javax.inject.Inject
private const val PAGE_SIZE = 20 private const val PAGE_SIZE = 20
internal class NetworkStoreRepository @Inject constructor( internal class NetworkStoreRepository @Inject constructor(
private val oxygenNetworkDataSource: OxygenNetworkDataSource private val oxygenNetworkDataSource: OxygenNetworkDataSource,
private val toolDao: ToolDao
) : StoreRepository { ) : StoreRepository {
override suspend fun getStore( override suspend fun getStore(
@@ -28,7 +30,13 @@ internal class NetworkStoreRepository @Inject constructor(
): Flow<PagingData<ToolEntity>> = ): Flow<PagingData<ToolEntity>> =
Pager( Pager(
config = PagingConfig(PAGE_SIZE), config = PagingConfig(PAGE_SIZE),
pagingSourceFactory = { ToolStorePagingSource(oxygenNetworkDataSource, searchValue) } pagingSourceFactory = {
ToolStorePagingSource(
oxygenNetworkDataSource,
toolDao,
searchValue
)
}
).flow ).flow
override fun detail( override fun detail(

View File

@@ -24,7 +24,7 @@ class OfflineToolRepository @Inject constructor(
toolDao.selectToolByUsernameAndToolId(username, toolId) toolDao.selectToolByUsernameAndToolId(username, toolId)
override suspend fun saveTool(toolEntity: ToolEntity) = override suspend fun saveTool(toolEntity: ToolEntity) =
toolDao.insertTool(toolEntity) toolDao.insertTool(toolEntity.copy(isInstalled = true))
override suspend fun updateTool(toolEntity: ToolEntity) = override suspend fun updateTool(toolEntity: ToolEntity) =
toolDao.updateTool(toolEntity) toolDao.updateTool(toolEntity)

View File

@@ -51,7 +51,6 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.LoadState import androidx.paging.LoadState
import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.StateFlow
import top.fatweb.oxygen.toolbox.R import top.fatweb.oxygen.toolbox.R
import top.fatweb.oxygen.toolbox.icon.Loading import top.fatweb.oxygen.toolbox.icon.Loading
import top.fatweb.oxygen.toolbox.icon.OxygenIcons import top.fatweb.oxygen.toolbox.icon.OxygenIcons
@@ -74,7 +73,6 @@ internal fun ToolStoreRoute(
modifier = modifier, modifier = modifier,
onNavigateToToolView = onNavigateToToolView, onNavigateToToolView = onNavigateToToolView,
toolStorePagingItems = toolStorePagingItems, toolStorePagingItems = toolStorePagingItems,
hasInstalled = { viewModel.hasInstalled(it) },
onChangeInstallStatus = viewModel::changeInstallStatus, onChangeInstallStatus = viewModel::changeInstallStatus,
onInstallTool = viewModel::installTool, onInstallTool = viewModel::installTool,
installInfo = installInfo installInfo = installInfo
@@ -86,7 +84,6 @@ internal fun ToolStoreScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onNavigateToToolView: (username: String, toolId: String) -> Unit, onNavigateToToolView: (username: String, toolId: String) -> Unit,
toolStorePagingItems: LazyPagingItems<ToolEntity>, toolStorePagingItems: LazyPagingItems<ToolEntity>,
hasInstalled: (ToolEntity) -> StateFlow<Boolean>,
onChangeInstallStatus: (installStatus: ToolStoreUiState.Status) -> Unit, onChangeInstallStatus: (installStatus: ToolStoreUiState.Status) -> Unit,
onInstallTool: (username: String, toolId: String) -> Unit, onInstallTool: (username: String, toolId: String) -> Unit,
installInfo: ToolStoreUiState.InstallInfo installInfo: ToolStoreUiState.InstallInfo
@@ -119,7 +116,6 @@ internal fun ToolStoreScreen(
) { ) {
toolsPanel( toolsPanel(
toolStorePagingItems = toolStorePagingItems, toolStorePagingItems = toolStorePagingItems,
hasInstalled = hasInstalled,
onAction = { username, toolId -> onAction = { username, toolId ->
installToolUsername = username installToolUsername = username
installToolId = toolId installToolId = toolId
@@ -179,7 +175,6 @@ internal fun ToolStoreScreen(
private fun LazyStaggeredGridScope.toolsPanel( private fun LazyStaggeredGridScope.toolsPanel(
toolStorePagingItems: LazyPagingItems<ToolEntity>, toolStorePagingItems: LazyPagingItems<ToolEntity>,
hasInstalled: (ToolEntity) -> StateFlow<Boolean>,
onAction: (username: String, toolId: String) -> Unit, onAction: (username: String, toolId: String) -> Unit,
onClick: (username: String, toolId: String) -> Unit onClick: (username: String, toolId: String) -> Unit
) { ) {
@@ -187,10 +182,9 @@ private fun LazyStaggeredGridScope.toolsPanel(
items = toolStorePagingItems.itemSnapshotList, items = toolStorePagingItems.itemSnapshotList,
key = { it!!.id }, key = { it!!.id },
) { ) {
val installed by hasInstalled(it!!).collectAsState()
ToolCard( ToolCard(
tool = it!!, tool = it!!,
actionIcon = if (installed) null else OxygenIcons.Download, actionIcon = if (!it.isInstalled) OxygenIcons.Download else null,
actionIconContentDescription = stringResource(R.string.core_install), actionIconContentDescription = stringResource(R.string.core_install),
onAction = { onAction(it.authorUsername, it.toolId) }, onAction = { onAction(it.authorUsername, it.toolId) },
onClick = { onClick(it.authorUsername, it.toolId) }, onClick = { onClick(it.authorUsername, it.toolId) },

View File

@@ -9,13 +9,8 @@ import androidx.paging.cachedIn
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import top.fatweb.oxygen.toolbox.model.Result import top.fatweb.oxygen.toolbox.model.Result
@@ -23,7 +18,6 @@ import top.fatweb.oxygen.toolbox.model.tool.ToolEntity
import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository import top.fatweb.oxygen.toolbox.repository.tool.StoreRepository
import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository import top.fatweb.oxygen.toolbox.repository.tool.ToolRepository
import javax.inject.Inject import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds
@HiltViewModel @HiltViewModel
class ToolStoreViewModel @Inject constructor( class ToolStoreViewModel @Inject constructor(
@@ -33,10 +27,6 @@ class ToolStoreViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "") private val searchValue = savedStateHandle.getStateFlow(SEARCH_VALUE, "")
private val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1) private val currentPage = savedStateHandle.getStateFlow(CURRENT_PAGE, 1)
private val installedStatus =
savedStateHandle.getStateFlow<MutableMap<String, MutableMap<String, Boolean>>>(
INSTALLED_STATUS, mutableMapOf()
)
val installInfo = savedStateHandle.getStateFlow(INSTALL_INFO, ToolStoreUiState.InstallInfo()) val installInfo = savedStateHandle.getStateFlow(INSTALL_INFO, ToolStoreUiState.InstallInfo())
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
@@ -48,26 +38,6 @@ class ToolStoreViewModel @Inject constructor(
) )
} }
fun hasInstalled(toolEntity: ToolEntity): StateFlow<Boolean> =
installedStatus.value[toolEntity.authorUsername]?.get(toolEntity.toolId)
?.let { MutableStateFlow(it) } ?: toolRepository.getToolByUsernameAndToolId(
toolEntity.authorUsername, toolEntity.toolId
).map {
if (installedStatus.value[toolEntity.authorUsername] == null) {
installedStatus.value[toolEntity.authorUsername] =
mutableMapOf(toolEntity.toolId to (it != null))
} else {
installedStatus.value[toolEntity.authorUsername]?.set(
toolEntity.toolId, it != null
)
}
it != null
}.stateIn(
scope = viewModelScope,
initialValue = true,
started = SharingStarted.WhileSubscribed(5.seconds.inWholeMilliseconds)
)
fun changeInstallStatus(installStatus: ToolStoreUiState.Status) { fun changeInstallStatus(installStatus: ToolStoreUiState.Status) {
savedStateHandle[INSTALL_INFO] = ToolStoreUiState.InstallInfo(installStatus) savedStateHandle[INSTALL_INFO] = ToolStoreUiState.InstallInfo(installStatus)
} }
@@ -86,11 +56,6 @@ class ToolStoreViewModel @Inject constructor(
toolRepository.saveTool(it.data) toolRepository.saveTool(it.data)
savedStateHandle[INSTALL_INFO] = savedStateHandle[INSTALL_INFO] =
ToolStoreUiState.InstallInfo(ToolStoreUiState.Status.Success) ToolStoreUiState.InstallInfo(ToolStoreUiState.Status.Success)
if (installedStatus.value[username] == null) {
installedStatus.value[username] = mutableMapOf(toolId to true)
} else {
installedStatus.value[username]?.set(toolId, true)
}
} }
} }
} }
@@ -114,5 +79,4 @@ data class ToolStoreUiState(
private const val SEARCH_VALUE = "searchValue" private const val SEARCH_VALUE = "searchValue"
private const val CURRENT_PAGE = "currentPage" private const val CURRENT_PAGE = "currentPage"
private const val INSTALLED_STATUS = "installedStatus"
private const val INSTALL_INFO = "installInfo" private const val INSTALL_INFO = "installInfo"