Refactor(ToolStore): Optimize the way to obtain installation status
This commit is contained in:
@@ -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')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -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
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) },
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user