refactor: extract database module

This commit is contained in:
二刺螈
2026-08-27 12:07:38 +08:00
parent 1b79589142
commit eab049d270
79 changed files with 533 additions and 458 deletions

View File

@@ -16,12 +16,10 @@ val debugSuffixResources = project.readDebugSuffixResources()
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.androidx.room)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlinx.atomicfu)
alias(libs.plugins.google.ksp)
alias(libs.plugins.remap)
alias(libs.plugins.loc)
}
@@ -195,11 +193,6 @@ if (buildProperty("GKD_RENAME_APK_FLAG").isPresent) {
}
}
// https://developer.android.com/jetpack/androidx/releases/room?hl=zh-cn#compiler-options
room {
schemaDirectory("$projectDir/schemas")
}
composeCompiler {
if (providers.gradleProperty("composeReports").isPresent) {
reportsDestination = layout.buildDirectory.dir("compose_compiler")
@@ -216,6 +209,7 @@ loc {
dependencies {
implementation(libs.kotlin.stdlib)
implementation(project(":gkd-db"))
implementation(project(":gkd-selector"))
implementation(libs.androidx.appcompat)
@@ -253,11 +247,6 @@ dependencies {
implementation(libs.priv.kit.ui)
implementation(libs.lsposed.hiddenapibypass)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
implementation(libs.androidx.room.paging)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.compose)

View File

@@ -41,12 +41,14 @@ import li.gkd.app.store.initStore
import li.gkd.app.util.AndroidTarget
import li.gkd.app.util.LogUtils
import li.gkd.app.util.PKG_FLAGS
import li.gkd.app.util.dbFolder
import li.gkd.app.util.deviceInfoDesc
import li.gkd.app.util.initAppState
import li.gkd.app.util.SubscriptionStore
import li.gkd.app.util.initToast
import li.gkd.app.util.launchTry
import li.gkd.app.util.toast
import li.gkd.db.Db
import org.lsposed.hiddenapibypass.HiddenApiBypass
import priv.kit.core.PrivilegeConfig
import priv.kit.ui.PrivilegeUi
@@ -221,6 +223,7 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
Db.initialize(this, dbFolder.resolve("gkd.db").absolutePath)
LogUtils.d()
Thread.setDefaultUncaughtExceptionHandler { t, e ->
toast(e.message ?: e.toString())

View File

@@ -18,7 +18,7 @@ import li.gkd.app.a11y.useA11yServiceEnabledFlow
import li.gkd.app.a11y.useEnabledA11yServicesFlow
import li.gkd.app.data.CrashData
import li.gkd.app.data.RawSubscription
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.entry.EntryActivity
import li.gkd.app.entry.OpenFileActivity
import li.gkd.app.priv.AutomationService
@@ -158,8 +158,8 @@ class MainViewModel : BaseViewModel(), OnSimpleLife by DefaultSimpleLifeImpl() {
val subsSheet = SubsSheetState()
val appOrderListState = DbSet.actionLogDao.queryLatestUniqueAppIds().stateLoadable()
val appVisitOrderMapState = DbSet.appVisitLogDao.query().map {
val appOrderListState = Db.actionLogDao.queryLatestUniqueAppIds().stateLoadable()
val appVisitOrderMapState = Db.appVisitLogDao.query().map {
it.mapIndexed { i, appId -> appId to i }.toMap()
}.debounce(500).stateLoadable()

View File

@@ -13,15 +13,16 @@ import kotlinx.coroutines.sync.withLock
import li.gkd.app.META
import li.gkd.app.app
import li.gkd.app.appScope
import li.gkd.app.data.ActionLog
import li.gkd.db.ActionLog
import li.gkd.app.data.ActionResult
import li.gkd.app.data.ActivityLog
import li.gkd.db.ActivityLog
import li.gkd.app.data.AttrInfo
import li.gkd.app.data.ResetMatchType
import li.gkd.app.data.ResolvedRule
import li.gkd.app.data.RuleStatus
import li.gkd.app.data.insert
import li.gkd.app.data.isSystem
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.service.updateTopTaskAppId
import li.gkd.app.store.actionCountFlow
import li.gkd.app.store.checkAppBlockMatch
@@ -187,11 +188,11 @@ fun updateTopActivity(
val logs = tempActivityLogList.toTypedArray()
tempActivityLogList.clear()
appScope.launchTry {
DbSet.activityLogDao.insert(*logs)
Db.activityLogDao.insert(*logs)
}
}
if (activityLogCount++ % 100 == 0) {
appScope.launchTry { DbSet.activityLogDao.deleteKeepLatest() }
appScope.launchTry { Db.activityLogDao.deleteKeepLatest() }
}
val topActivity = topActivityFlow.value
val ruleSummary = ruleSummaryFlow.value
@@ -206,7 +207,7 @@ fun updateTopActivity(
val oldAppId = lastAppId
lastAppId = appId
appScope.launchTry {
DbSet.appVisitLogDao.insert(oldAppId, appId, t)
Db.appVisitLogDao.insert(oldAppId, appId, t)
}
appChangeTime = t
ruleSummary.globalRules.forEach { it.resetState(t) }
@@ -297,9 +298,9 @@ fun addActionLog(
ruleKey = rule.key,
ctime = ctime,
)
DbSet.actionLogDao.insert(actionLog)
Db.actionLogDao.insert(actionLog)
if (actionCountFlow.value % 100 == 0L) {
DbSet.actionLogDao.deleteKeepLatest()
Db.actionLogDao.deleteKeepLatest()
}
}
LogUtils.d(

View File

@@ -0,0 +1,34 @@
package li.gkd.app.data
import android.view.accessibility.AccessibilityEvent
import li.gkd.db.A11yEventLog
fun AccessibilityEvent.toA11yEventLog(id: Int) = A11yEventLog(
id = id,
ctime = System.currentTimeMillis(),
type = eventType,
appId = packageName.toString(),
name = className.toString(),
desc = contentDescription?.toString(),
text = text.map { it.toString() },
)
val A11yEventLog.isStateChanged: Boolean
get() = type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
val A11yEventLog.fixedName: String
get() {
if (isStateChanged && name.startsWith(appId)) {
return name.substring(appId.length)
}
if (name.contains("View") || name.contains("Layout") || viewSuffixes.any(name::startsWith)) {
return name.substring(name.lastIndexOf('.') + 1)
}
return name
}
private val viewSuffixes = listOf(
"android.widget.",
"android.view.",
"android.support.",
)

View File

@@ -0,0 +1,11 @@
package li.gkd.app.data
import li.gkd.app.util.format
import li.gkd.app.util.getShowActivityId
import li.gkd.db.ActionLog
val ActionLog.showActivityId: String?
get() = getShowActivityId(appId, activityId)
val ActionLog.date: String
get() = ctime.format("MM-dd HH:mm:ss SSS")

View File

@@ -0,0 +1,11 @@
package li.gkd.app.data
import li.gkd.app.util.format
import li.gkd.app.util.getShowActivityId
import li.gkd.db.ActivityLog
val ActivityLog.showActivityId: String?
get() = getShowActivityId(appId, activityId)
val ActivityLog.date: String
get() = ctime.format("HH:mm:ss SSS")

View File

@@ -0,0 +1,27 @@
package li.gkd.app.data
import li.gkd.app.META
import li.gkd.app.a11y.launcherAppId
import li.gkd.app.util.systemUiAppId
import li.gkd.db.AppVisitLog
import li.gkd.db.Db
private var appLogCount = 0
suspend fun AppVisitLog.AppLogDao.insert(oldAppId: String, newAppId: String, mtime: Long) {
Db.withTransaction {
insert(
AppVisitLog(oldAppId, fixAppVisitTime(oldAppId, mtime - 1)),
AppVisitLog(newAppId, fixAppVisitTime(newAppId, mtime)),
)
if (appLogCount++ % 100 == 0) {
deleteKeepLatest()
}
}
}
private fun fixAppVisitTime(appId: String, time: Long): Long = when (appId) {
META.appId -> time - 120_000
launcherAppId, systemUiAppId -> time - 60_000
else -> time
}

View File

@@ -2,6 +2,8 @@ package li.gkd.app.data
import kotlinx.serialization.Serializable
import li.gkd.app.util.appInfoMapFlow
import li.gkd.db.BaseSnapshot
import li.gkd.db.Snapshot
@Serializable
data class ComplexSnapshot(

View File

@@ -0,0 +1,104 @@
package li.gkd.app.data
import li.gkd.app.util.isValidActivityId
import li.gkd.app.util.isValidAppId
data class ExcludeData(
val appIds: Map<String, Boolean>,
val activityIds: Set<Pair<String, String>>,
) {
val excludeAppIds = appIds.entries.filter { entry -> entry.value }.map { entry -> entry.key }.toHashSet()
val includeAppIds = appIds.entries.filter { entry -> !entry.value }.map { entry -> entry.key }.toHashSet()
fun stringify(appId: String? = null): String {
return if (appId != null) {
activityIds.filter { entry -> entry.first == appId }.map { entry -> entry.second }.sorted()
.joinToString("\n\n")
} else {
(appIds.entries.map { entry ->
if (entry.value) {
entry.key
} else {
"!${entry.key}"
}
} + activityIds.map { entry -> "${entry.first}/${entry.second}" })
.sorted()
.joinToString("\n\n")
}
}
fun clear(appId: String): ExcludeData {
return copy(
appIds = appIds.toMutableMap().apply {
remove(appId)
},
)
}
fun switch(appId: String, activityId: String? = null): ExcludeData {
return if (activityId == null) {
copy(
appIds = appIds.toMutableMap().apply {
if (get(appId) != false) {
set(appId, false)
} else {
set(appId, true)
}
},
)
} else {
copy(activityIds = activityIds.toMutableSet().apply {
val entry = appId to activityId
if (contains(entry)) {
remove(entry)
} else {
add(entry)
}
})
}
}
companion object {
private val empty = ExcludeData(emptyMap(), emptySet())
fun parse(exclude: String?): ExcludeData {
if (exclude.isNullOrBlank()) {
return empty
}
val appIds = HashMap<String, Boolean>()
val activityIds = HashSet<Pair<String, String>>()
exclude.split('\n')
.filter { it.isNotBlank() }
.forEach { value ->
if (value[0] == '!') {
val appId = value.substring(1)
if (appId.isValidAppId()) {
appIds[appId] = false
}
} else {
val parts = value.split('/', limit = 2)
val appId = parts[0]
if (appId.isValidAppId()) {
val activityId = parts.getOrNull(1)
if (activityId != null) {
if (activityId.isValidActivityId()) {
activityIds.add(appId to activityId)
}
} else {
appIds[appId] = true
}
}
}
}
return ExcludeData(
appIds = appIds,
activityIds = activityIds,
)
}
fun parse(exclude: String?, appId: String): ExcludeData {
if (exclude.isNullOrBlank()) return empty
return parse(exclude.split('\n').joinToString("\n") { "$appId/$it" })
}
}
}

View File

@@ -15,7 +15,6 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import li.gkd.app.a11y.typeInfo
import li.gkd.app.util.LOCAL_SUBS_IDS
import li.gkd.app.util.LogUtils
import li.gkd.app.util.ScreenUtils
import li.gkd.app.util.appInfoMapFlow
@@ -24,6 +23,8 @@ import li.gkd.app.util.filterIfNotAll
import li.gkd.app.util.json
import li.gkd.app.util.toJson5String
import li.gkd.app.util.toast
import li.gkd.db.LOCAL_SUBS_IDS
import li.gkd.db.SubsConfig
import li.songe.json5.Json5
import li.gkd.selector.Selector
import net.objecthunter.exp4j.Expression

View File

@@ -1,5 +1,8 @@
package li.gkd.app.data
import li.gkd.db.SubsConfig
import li.gkd.db.SubsItem
sealed class ResolvedGroup(
open val group: RawSubscription.RawGroupProps,
val subscription: RawSubscription,
@@ -35,4 +38,4 @@ class ResolvedGlobalGroup(
val groupExcludeAppIds by lazy {
subscription.globalGroupAppGroupNameDisableMap[group.key] ?: emptySet()
}
}
}

View File

@@ -0,0 +1,11 @@
package li.gkd.app.data
import li.gkd.app.snapshot.SnapshotStore
import li.gkd.app.util.format
import li.gkd.db.Snapshot
val Snapshot.screenshotFile
get() = SnapshotStore.screenshotFile(id)
val Snapshot.date: String
get() = id.format("MM-dd HH:mm:ss")

View File

@@ -0,0 +1,13 @@
package li.gkd.app.data
import li.gkd.db.Db
import li.gkd.db.SubsConfig
suspend fun SubsConfig.SubsConfigDao.batchResetAppGroupEnable(
subsItemId: Long,
list: List<Pair<RawSubscription.RawAppGroup, RawSubscription.RawApp>>,
): List<Pair<RawSubscription.RawAppGroup, RawSubscription.RawApp>> = Db.withTransaction {
list.filter { (group, app) ->
resetAppGroupTypeEnable(subsItemId, app.id, group.key) > 0
}
}

View File

@@ -0,0 +1,7 @@
package li.gkd.app.data
import li.gkd.app.util.format
import li.gkd.db.SubsItem
val SubsItem.mtimeStr: String
get() = mtime.format("yyyy-MM-dd HH:mm:ss")

View File

@@ -36,9 +36,9 @@ import kotlinx.coroutines.launch
import li.gkd.app.META
import li.gkd.app.MainViewModel
import li.gkd.app.appScope
import li.gkd.app.data.A11yEventLog
import li.gkd.db.A11yEventLog
import li.gkd.app.data.toA11yEventLog
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.notif.NotificationCatalog
import li.gkd.app.notif.StopServiceReceiver
import li.gkd.app.permission.PermissionStates
@@ -129,7 +129,7 @@ class EventService : OverlayWindowService(positionKey = "event") {
delay(1000.milliseconds)
val list = getAndUpdate { emptyList() }
if (list.isNotEmpty()) {
DbSet.a11yEventLogDao.insert(list)
Db.a11yEventLogDao.insert(list)
}
}
}
@@ -143,7 +143,7 @@ class EventService : OverlayWindowService(positionKey = "event") {
logAutoId = 0
}
scope.launch {
logAutoId = (DbSet.a11yEventLogDao.maxId() ?: 0).coerceAtLeast(1)
logAutoId = (Db.a11yEventLogDao.maxId() ?: 0).coerceAtLeast(1)
}
useLogLifecycle()
@@ -171,7 +171,7 @@ class EventService : OverlayWindowService(positionKey = "event") {
service.eventLogs.removeRange(0, 64)
}
if (eventLog.id % 100 == 0) {
appScope.launchTry { DbSet.a11yEventLogDao.deleteKeepLatest() }
appScope.launchTry { Db.a11yEventLogDao.deleteKeepLatest() }
}
}

View File

@@ -42,15 +42,15 @@ import li.gkd.app.data.DeviceInfo
import li.gkd.app.data.GkdAction
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.RpcError
import li.gkd.app.data.SubsItem
import li.gkd.db.SubsItem
import li.gkd.app.data.selfAppInfo
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.notif.NotificationCatalog
import li.gkd.app.notif.StopServiceReceiver
import li.gkd.app.permission.PermissionStates
import li.gkd.app.store.storeFlow
import li.gkd.app.util.DefaultSimpleLifeImpl
import li.gkd.app.util.LOCAL_HTTP_SUBS_ID
import li.gkd.db.LOCAL_HTTP_SUBS_ID
import li.gkd.app.util.LogUtils
import li.gkd.app.util.OnSimpleLife
import li.gkd.app.util.SERVER_SCRIPT_URL
@@ -212,7 +212,7 @@ private fun CoroutineScope.createServer(port: Int) = embeddedServer(CIO, port) {
call.respond(SnapshotCapture.capture())
}
post("/getSnapshots") {
val list = DbSet.snapshotDao.query().first().mapNotNull {
val list = Db.snapshotDao.query().first().mapNotNull {
try {
SnapshotStore.getMinSnapshot(it.id)
} catch (_: Throwable) {
@@ -223,7 +223,7 @@ private fun CoroutineScope.createServer(port: Int) = embeddedServer(CIO, port) {
}
post("/deleteSnapshot") {
val data = call.receive<ReqId>()
val allSnapshots = DbSet.snapshotDao.query().first()
val allSnapshots = Db.snapshotDao.query().first()
val snapshot = allSnapshots.find { it.id == data.id }
if (snapshot != null) {
SnapshotStore.delete(snapshot)

View File

@@ -11,8 +11,8 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import li.gkd.app.data.ComplexSnapshot
import li.gkd.app.data.Snapshot
import li.gkd.app.db.DbSet
import li.gkd.db.Snapshot
import li.gkd.db.Db
import li.gkd.app.util.LogUtils
import li.gkd.app.util.ZipUtils
import li.gkd.app.util.appInfoMapFlow
@@ -64,7 +64,7 @@ object SnapshotStore {
mutationMutex.withLock {
withContext(Dispatchers.IO) {
fileLayout.committed(snapshot.id).directory.deleteRecursivelyOrThrow()
DbSet.snapshotDao.delete(snapshot)
Db.snapshotDao.delete(snapshot)
}
}
}
@@ -74,7 +74,7 @@ object SnapshotStore {
snapshotFolder.listFiles()?.forEach { file ->
file.deleteRecursivelyOrThrow()
}
DbSet.snapshotDao.deleteAll()
Db.snapshotDao.deleteAll()
}
}
@@ -111,7 +111,7 @@ object SnapshotStore {
LogUtils.d("无法删除旧快照截图", files.legacyPngFile.absolutePath)
}
if (snapshot.githubAssetId != null) {
DbSet.snapshotDao.deleteGithubAssetId(snapshot.id)
Db.snapshotDao.deleteGithubAssetId(snapshot.id)
}
true
} finally {
@@ -201,7 +201,7 @@ object SnapshotStore {
)
},
publish = {
DbSet.snapshotDao.insert(snapshot.toSnapshot())
Db.snapshotDao.insert(snapshot.toSnapshot())
},
)
}

View File

@@ -45,7 +45,8 @@ import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import li.gkd.app.MainActivity
import li.gkd.app.data.A11yEventLog
import li.gkd.app.data.fixedName
import li.gkd.app.data.isStateChanged
import li.gkd.app.ui.component.AppNameText
import li.gkd.app.ui.component.CopyableText
import li.gkd.app.ui.component.EmptyText
@@ -68,6 +69,7 @@ import li.gkd.app.util.launchTry
import li.gkd.app.util.throttle
import li.gkd.app.util.toJson5String
import li.gkd.app.util.toast
import li.gkd.db.A11yEventLog
@Serializable
data object A11yEventLogRoute : NavKey
@@ -285,7 +287,8 @@ fun EventLogCard(eventLog: A11yEventLog, modifier: Modifier = Modifier) {
softWrap = false,
overflow = TextOverflow.MiddleEllipsis,
)
if (eventLog.desc != null) {
val desc = eventLog.desc
if (desc != null) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(2.dp)
@@ -297,7 +300,7 @@ fun EventLogCard(eventLog: A11yEventLog, modifier: Modifier = Modifier) {
),
)
Text(
text = eventLog.desc,
text = desc,
modifier = Modifier
.background(
color = MaterialTheme.colorScheme.secondaryContainer,

View File

@@ -5,13 +5,13 @@ import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import li.gkd.app.data.A11yEventLog
import li.gkd.app.db.DbSet
import li.gkd.db.A11yEventLog
import li.gkd.db.Db
import li.gkd.app.ui.share.BaseViewModel
class A11yEventLogVm : BaseViewModel() {
val pagingDataFlow =
Pager(PagingConfig(pageSize = 100)) { DbSet.a11yEventLogDao.pagingSource() }
Pager(PagingConfig(pageSize = 100)) { Db.a11yEventLogDao.pagingSource() }
.flow.cachedIn(scope)
val showEventLogFlow: StateFlow<A11yEventLog?>
@@ -26,6 +26,6 @@ class A11yEventLogVm : BaseViewModel() {
}
suspend fun deleteAll() {
DbSet.a11yEventLogDao.deleteAll()
Db.a11yEventLogDao.deleteAll()
}
}

View File

@@ -41,7 +41,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import kotlinx.serialization.Serializable
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.date
import li.gkd.app.data.showActivityId
import li.gkd.app.ui.component.AppNameText
import li.gkd.app.ui.component.EmptyText
import li.gkd.app.ui.component.FixedTimeText
@@ -64,6 +65,7 @@ import li.gkd.app.ui.style.scaffoldPadding
import li.gkd.app.util.throttle
import li.gkd.app.util.launchTry
import li.gkd.app.util.toast
import li.gkd.db.SubsConfig
@Serializable
data class ActionLogRoute(

View File

@@ -10,11 +10,11 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import li.gkd.app.data.ActionLog
import li.gkd.db.ActionLog
import li.gkd.app.data.ExcludeData
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.db.DbSet
import li.gkd.db.SubsConfig
import li.gkd.db.Db
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.util.subsMapFlow
@@ -36,9 +36,9 @@ class ActionLogVm(val route: ActionLogRoute) : BaseViewModel() {
val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) {
when {
route.subsId != null -> DbSet.actionLogDao.pagingSubsSource(subsId = route.subsId)
route.appId != null -> DbSet.actionLogDao.pagingAppSource(appId = route.appId)
else -> DbSet.actionLogDao.pagingSource()
route.subsId != null -> Db.actionLogDao.pagingSubsSource(subsId = route.subsId)
route.appId != null -> Db.actionLogDao.pagingAppSource(appId = route.appId)
else -> Db.actionLogDao.pagingSource()
}
}
.flow
@@ -72,13 +72,13 @@ class ActionLogVm(val route: ActionLogRoute) : BaseViewModel() {
flowOf(null)
} else {
val configFlow = if (actionLog.groupType == SubsConfig.AppGroupType) {
DbSet.subsConfigDao.queryAppGroupTypeConfig(
Db.subsConfigDao.queryAppGroupTypeConfig(
actionLog.subsId,
actionLog.appId,
actionLog.groupKey,
)
} else {
DbSet.subsConfigDao.queryGlobalGroupTypeConfig(
Db.subsConfigDao.queryGlobalGroupTypeConfig(
actionLog.subsId,
actionLog.groupKey,
)
@@ -121,9 +121,9 @@ class ActionLogVm(val route: ActionLogRoute) : BaseViewModel() {
suspend fun deleteLogs() {
when {
route.subsId != null -> DbSet.actionLogDao.deleteSubsAll(route.subsId)
route.appId != null -> DbSet.actionLogDao.deleteAppAll(route.appId)
else -> DbSet.actionLogDao.deleteAll()
route.subsId != null -> Db.actionLogDao.deleteSubsAll(route.subsId)
route.appId != null -> Db.actionLogDao.deleteAppAll(route.appId)
else -> Db.actionLogDao.deleteAll()
}
}
@@ -137,7 +137,7 @@ class ActionLogVm(val route: ActionLogRoute) : BaseViewModel() {
groupKey = actionLog.groupKey,
)
val oldExclude = ExcludeData.parse(subsConfig.exclude)
DbSet.subsConfigDao.insert(
Db.subsConfigDao.insert(
subsConfig.copy(
exclude = oldExclude.copy(
appIds = oldExclude.appIds.toMutableMap().apply {
@@ -167,7 +167,7 @@ class ActionLogVm(val route: ActionLogRoute) : BaseViewModel() {
)
}
val oldExclude = ExcludeData.parse(subsConfig.exclude)
DbSet.subsConfigDao.insert(
Db.subsConfigDao.insert(
subsConfig.copy(
exclude = oldExclude.switch(actionLog.appId, activityId).stringify(),
),

View File

@@ -35,7 +35,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import kotlinx.serialization.Serializable
import li.gkd.app.data.ActivityLog
import li.gkd.app.data.date
import li.gkd.app.data.showActivityId
import li.gkd.app.ui.component.AppNameText
import li.gkd.app.ui.component.EmptyText
import li.gkd.app.ui.component.FixedTimeText
@@ -54,6 +55,7 @@ import li.gkd.app.util.appInfoMapFlow
import li.gkd.app.util.launchTry
import li.gkd.app.util.throttle
import li.gkd.app.util.toast
import li.gkd.db.ActivityLog
@Serializable
data object ActivityLogRoute : NavKey

View File

@@ -3,13 +3,13 @@ package li.gkd.app.ui
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.cachedIn
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.share.BaseViewModel
class ActivityLogVm : BaseViewModel() {
val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) { DbSet.activityLogDao.pagingSource() }
val pagingDataFlow = Pager(PagingConfig(pageSize = 100)) { Db.activityLogDao.pagingSource() }
.flow.cachedIn(scope)
suspend fun deleteAll() {
DbSet.activityLogDao.deleteAll()
Db.activityLogDao.deleteAll()
}
}

View File

@@ -37,7 +37,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.NavKey
import kotlinx.coroutines.Dispatchers
import kotlinx.serialization.Serializable
import li.gkd.app.data.ActionLog
import li.gkd.db.ActionLog
import li.gkd.app.data.RawSubscription
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.component.AnimatedBooleanContent
@@ -65,7 +65,7 @@ import li.gkd.app.ui.share.noRippleClickable
import li.gkd.app.ui.style.EmptyHeight
import li.gkd.app.ui.style.iconTextSize
import li.gkd.app.ui.style.scaffoldPadding
import li.gkd.app.util.LOCAL_SUBS_ID
import li.gkd.db.LOCAL_SUBS_ID
import li.gkd.app.util.RuleSortOption
import li.gkd.app.util.copyText
import li.gkd.app.util.findOption

View File

@@ -14,11 +14,11 @@ import kotlinx.coroutines.flow.runningFold
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import li.gkd.app.data.ActionLog
import li.gkd.app.data.CategoryConfig
import li.gkd.db.ActionLog
import li.gkd.db.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.db.DbSet
import li.gkd.db.SubsConfig
import li.gkd.db.Db
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.component.ShowGroupState
import li.gkd.app.ui.component.batchUpdateGroupEnable
@@ -76,8 +76,8 @@ class AppConfigVm(val route: AppConfigRoute) : BaseViewModel() {
Loadable.Loading -> flowOf(Loadable.Loading)
is Loadable.Failure -> flowOf(snapshotState)
is Loadable.Ready -> combine(
DbSet.subsItemDao.query(),
DbSet.appConfigDao.queryAppUsedList(route.appId),
Db.subsItemDao.query(),
Db.appConfigDao.queryAppUsedList(route.appId),
) { items, appConfigs ->
val usedSubsIds = items.filter { it.enable }.map { it.id }.sorted()
val appUsedSubsIds = usedSubsIds.filter { id ->
@@ -89,9 +89,9 @@ class AppConfigVm(val route: AppConfigRoute) : BaseViewModel() {
appUsedSubsIds to entries
}.distinctUntilChanged().flatMapLatest { (usedSubsIds, entries) ->
combine(
DbSet.subsConfigDao.queryUsedGlobalConfig(),
DbSet.subsConfigDao.queryAppConfig(usedSubsIds, route.appId),
DbSet.categoryConfigDao.queryBySubsIds(usedSubsIds),
Db.subsConfigDao.queryUsedGlobalConfig(),
Db.subsConfigDao.queryAppConfig(usedSubsIds, route.appId),
Db.categoryConfigDao.queryBySubsIds(usedSubsIds),
) { globalConfigs, appConfigs, categoryConfigs ->
val subsPairs = entries.map { entry ->
val globalGroups = entry.subscription.globalGroups.filter { group ->
@@ -190,7 +190,7 @@ class AppConfigVm(val route: AppConfigRoute) : BaseViewModel() {
private val sortStateFlow = storeFlow.flatMapLatest { store ->
val option = RuleSortOption.objects.findOption(store.appRuleSort)
val logsFlow = if (option == RuleSortOption.ByActionTime) {
DbSet.actionLogDao.queryLatestByAppId(route.appId)
Db.actionLogDao.queryLatestByAppId(route.appId)
} else {
flowOf(emptyList())
}

View File

@@ -36,7 +36,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import li.gkd.app.MainActivity
import li.gkd.app.data.Snapshot
import li.gkd.app.data.date
import li.gkd.app.data.screenshotFile
import li.gkd.app.permission.PermissionStates
import li.gkd.app.snapshot.SnapshotStore
import li.gkd.app.ui.component.EmptyText
@@ -61,6 +62,7 @@ import li.gkd.app.util.copyText
import li.gkd.app.util.launchTry
import li.gkd.app.util.throttle
import li.gkd.app.util.toast
import li.gkd.db.Snapshot
@Serializable
data object SnapshotPageRoute : NavKey
@@ -330,11 +332,12 @@ private fun SnapshotCard(
style = MaterialTheme.typography.bodySmall,
)
}
val showActivityId = if (snapshot.activityId != null) {
if (snapshot.activityId.startsWith(snapshot.appId)) {
snapshot.activityId.substring(snapshot.appId.length)
val activityId = snapshot.activityId
val showActivityId = if (activityId != null) {
if (activityId.startsWith(snapshot.appId)) {
activityId.substring(snapshot.appId.length)
} else {
snapshot.activityId
activityId
}
} else {
null

View File

@@ -4,12 +4,13 @@ import android.graphics.BitmapFactory
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
import li.gkd.app.data.Snapshot
import li.gkd.app.db.DbSet
import li.gkd.app.data.screenshotFile
import li.gkd.app.snapshot.SnapshotStore
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.util.ImageUtils
import li.gkd.app.util.appInfoMapFlow
import li.gkd.db.Db
import li.gkd.db.Snapshot
import java.io.File
data class SnapshotUiState(
@@ -18,7 +19,7 @@ data class SnapshotUiState(
)
class SnapshotVm : BaseViewModel() {
private val snapshotsFlow = DbSet.snapshotDao.query()
private val snapshotsFlow = Db.snapshotDao.query()
val uiState = combine(
snapshotsFlow,
@@ -47,7 +48,7 @@ class SnapshotVm : BaseViewModel() {
suspend fun markUploaded(snapshot: Snapshot, githubAssetId: Int) =
withContext(Dispatchers.IO) {
DbSet.snapshotDao.update(snapshot.copy(githubAssetId = githubAssetId))
Db.snapshotDao.update(snapshot.copy(githubAssetId = githubAssetId))
}
suspend fun replaceScreenshot(snapshot: Snapshot, newBytes: ByteArray): Boolean {

View File

@@ -5,11 +5,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.withContext
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.db.SubsConfig
import li.gkd.app.data.edit
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.component.batchUpdateGroupEnable
import li.gkd.app.ui.component.toGroupState
import li.gkd.app.ui.component.updateRuleGroupEnable
@@ -33,9 +33,9 @@ class SubsAppGroupListVm(val route: SubsAppGroupListRoute) : BaseViewModel() {
private val subscription = requiredSubscription(route.subsItemId)
private val subsConfigsFlow =
DbSet.subsConfigDao.queryAppGroupTypeConfig(route.subsItemId, route.appId)
Db.subsConfigDao.queryAppGroupTypeConfig(route.subsItemId, route.appId)
private val categoryConfigsFlow = DbSet.categoryConfigDao.queryConfig(route.subsItemId)
private val categoryConfigsFlow = Db.categoryConfigDao.queryConfig(route.subsItemId)
val uiState = subscription.buildUiState(
initialValue = { rawSubscription ->

View File

@@ -49,11 +49,11 @@ import li.gkd.app.ui.style.EmptyHeight
import li.gkd.app.ui.style.scaffoldPadding
import li.gkd.app.util.AppGroupOption
import li.gkd.app.util.AppSortOption
import li.gkd.app.util.LOCAL_SUBS_IDS
import li.gkd.app.util.appInfoMapFlow
import li.gkd.app.util.findOption
import li.gkd.app.util.launchTry
import li.gkd.app.util.throttle
import li.gkd.db.LOCAL_SUBS_IDS
@Serializable
data class SubsAppListRoute(val subsItemId: Long) : NavKey

View File

@@ -9,10 +9,10 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import li.gkd.app.MainViewModel
import li.gkd.app.data.AppConfig
import li.gkd.db.AppConfig
import li.gkd.app.data.AppInfo
import li.gkd.app.data.RawSubscription
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.store.blockMatchAppListFlow
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.share.BaseViewModel
@@ -36,12 +36,12 @@ class SubsAppListVm(
private val subscription = requiredSubscription(route.subsItemId)
private val appConfigsFlow = DbSet.appConfigDao.queryAppTypeConfig(route.subsItemId)
private val appConfigsFlow = Db.appConfigDao.queryAppTypeConfig(route.subsItemId)
private val groupSubsConfigsFlow =
DbSet.subsConfigDao.querySubsGroupTypeConfig(route.subsItemId)
Db.subsConfigDao.querySubsGroupTypeConfig(route.subsItemId)
private val categoryConfigsFlow = DbSet.categoryConfigDao.queryConfig(route.subsItemId)
private val categoryConfigsFlow = Db.categoryConfigDao.queryConfig(route.subsItemId)
val searchStrFlow: StateFlow<String>
field = MutableStateFlow("")
@@ -188,6 +188,6 @@ class SubsAppListVm(
subsId = route.subsItemId,
appId = appId,
)
DbSet.appConfigDao.insert(newConfig)
Db.appConfigDao.insert(newConfig)
}
}

View File

@@ -6,11 +6,10 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.update
import li.gkd.app.MainViewModel
import li.gkd.app.data.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.batchResetAppGroupEnable
import li.gkd.app.data.edit
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.component.updateRuleGroupEnable
import li.gkd.app.ui.share.BaseViewModel
@@ -23,6 +22,8 @@ import li.gkd.app.util.EnableGroupOption
import li.gkd.app.util.appInfoMapFlow
import li.gkd.app.util.findOption
import li.gkd.app.store.blockMatchAppListFlow
import li.gkd.db.CategoryConfig
import li.gkd.db.SubsConfig
data class SubsCategoryGroupConfigs(
val subsConfigs: List<SubsConfig>,
@@ -45,9 +46,9 @@ class SubsCategoryGroupVm(
field = MutableStateFlow(false)
private val subscription = requiredSubscription(route.subsId)
private val subsConfigsFlow = DbSet.subsConfigDao.querySubsGroupTypeConfig(route.subsId)
private val subsConfigsFlow = Db.subsConfigDao.querySubsGroupTypeConfig(route.subsId)
private val categoryConfigFlow =
DbSet.categoryConfigDao.queryCategoryConfig(route.subsId, route.categoryKey)
Db.categoryConfigDao.queryCategoryConfig(route.subsId, route.categoryKey)
private val appActionOrderMapState = subsAppActionOrderMapState(route.subsId)
val uiState = subscription.buildUiState(
@@ -142,7 +143,7 @@ class SubsCategoryGroupVm(
true -> false
}
val option = EnableGroupOption.objects.findOption(newValue)
DbSet.categoryConfigDao.insert(
Db.categoryConfigDao.insert(
(categoryConfig ?: CategoryConfig(
enable = option.value,
subsId = rawSubscription.id,
@@ -155,7 +156,7 @@ class SubsCategoryGroupVm(
suspend fun resetAllRuleSwitches(): Int {
val state = uiState.value.value ?: error("订阅尚未加载")
val rawSubscription = subscription.requireValue()
return DbSet.subsConfigDao.batchResetAppGroupEnable(
return Db.subsConfigDao.batchResetAppGroupEnable(
rawSubscription.id,
state.apps.flatMap { app -> app.groups }.map { group ->
group to rawSubscription.getAppByGroup(group)
@@ -197,7 +198,7 @@ class SubsCategoryGroupVm(
subscription.update { current ->
current.edit { removeCategory(route.categoryKey) }
}
DbSet.categoryConfigDao.deleteByCategoryKey(
Db.categoryConfigDao.deleteByCategoryKey(
route.subsId,
route.categoryKey,
)

View File

@@ -31,7 +31,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation3.runtime.NavKey
import kotlinx.serialization.Serializable
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.ui.component.EmptyText
import li.gkd.app.ui.component.FullscreenDialog

View File

@@ -3,10 +3,10 @@ package li.gkd.app.ui
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.edit
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.ui.share.Loadable
import li.gkd.app.util.EnableGroupOption
@@ -22,7 +22,7 @@ class SubsCategoryVm(val route: SubsCategoryRoute) : BaseViewModel() {
field = MutableStateFlow(false)
private val subscription = requiredSubscription(route.subsItemId)
private val categoryConfigsFlow = DbSet.categoryConfigDao.queryConfig(route.subsItemId)
private val categoryConfigsFlow = Db.categoryConfigDao.queryConfig(route.subsItemId)
val uiState = subscription.buildUiState(
initialValue = { rawSubscription ->
@@ -61,7 +61,7 @@ class SubsCategoryVm(val route: SubsCategoryRoute) : BaseViewModel() {
val categoryConfigMap = state.categoryConfigMap.value
?: error("类别配置尚未加载")
val oldConfig = categoryConfigMap[category.key]
DbSet.categoryConfigDao.insert(
Db.categoryConfigDao.insert(
(oldConfig ?: CategoryConfig(
enable = option.value,
subsId = rawSubscription.id,

View File

@@ -9,8 +9,8 @@ import li.gkd.app.MainViewModel
import li.gkd.app.data.AppInfo
import li.gkd.app.data.ExcludeData
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.db.DbSet
import li.gkd.db.SubsConfig
import li.gkd.db.Db
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.ui.share.Loadable
@@ -38,7 +38,7 @@ class SubsGlobalGroupExcludeVm(
) : BaseViewModel() {
private val subscription = requiredSubscription(route.subsItemId)
private val subsConfigFlow = DbSet.subsConfigDao
private val subsConfigFlow = Db.subsConfigDao
.queryGlobalGroupTypeConfig(route.subsItemId, route.groupKey)
private val appFilter = useAppFilter(
@@ -208,7 +208,7 @@ class SubsGlobalGroupExcludeVm(
subsId = route.subsItemId,
groupKey = route.groupKey,
)).copy(exclude = newExclude.stringify())
DbSet.subsConfigDao.insert(subsConfig)
Db.subsConfigDao.insert(subsConfig)
return true
}
@@ -227,7 +227,7 @@ class SubsGlobalGroupExcludeVm(
},
).stringify(),
)
DbSet.subsConfigDao.insert(subsConfig)
Db.subsConfigDao.insert(subsConfig)
}
}

View File

@@ -4,9 +4,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.map
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.db.SubsConfig
import li.gkd.app.data.edit
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.component.batchUpdateGroupEnable
import li.gkd.app.ui.component.toGroupState
import li.gkd.app.ui.component.updateRuleGroupEnable
@@ -22,7 +22,7 @@ class SubsGlobalGroupListVm(val route: SubsGlobalGroupListRoute) : BaseViewModel
private val subscription = requiredSubscription(route.subsItemId)
private val subsConfigsFlow =
DbSet.subsConfigDao.queryGlobalGroupTypeConfig(route.subsItemId)
Db.subsConfigDao.queryGlobalGroupTypeConfig(route.subsItemId)
val uiState = subscription.buildUiState(
initialValue = { rawSubscription ->
@@ -79,6 +79,6 @@ class SubsGlobalGroupListVm(val route: SubsGlobalGroupListRoute) : BaseViewModel
subscription.update { current ->
current.edit { removeGlobalGroups { it.key in selectedKeys } }
}
DbSet.subsConfigDao.batchDeleteGlobalGroupConfig(route.subsItemId, selectedKeys.toList())
Db.subsConfigDao.batchDeleteGlobalGroupConfig(route.subsItemId, selectedKeys.toList())
}
}

View File

@@ -1,6 +1,6 @@
package li.gkd.app.ui
import li.gkd.app.data.SubsItem
import li.gkd.db.SubsItem
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.util.SubscriptionResult
import li.gkd.app.util.SubscriptionStore

View File

@@ -28,10 +28,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.ExcludeData
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.db.SubsConfig
import li.gkd.app.ui.getGlobalGroupChecked
import li.gkd.app.ui.icon.ResetSettings
import li.gkd.app.ui.share.noRippleClickable

View File

@@ -11,12 +11,12 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.first
import li.gkd.app.MainViewModel
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.ExcludeData
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.db.SubsConfig
import li.gkd.app.data.edit
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.SubsGlobalGroupExcludeRoute
import li.gkd.app.ui.UpsertRuleGroupRoute
import li.gkd.app.ui.getGlobalGroupChecked
@@ -52,7 +52,7 @@ data class ShowGroupState(
subs.globalGroups
}?.find { it.key == groupKey } ?: error("require group")
val category = subs.getCategory(group.name) ?: return null
return DbSet.categoryConfigDao.queryCategoryConfig(subsId, category.key).first()
return Db.categoryConfigDao.queryCategoryConfig(subsId, category.key).first()
}
}
@@ -60,9 +60,9 @@ private fun ShowGroupState.querySubsConfigFlow(): Flow<SubsConfig?> {
val groupKey = groupKey ?: error("require groupKey")
return if (groupType == SubsConfig.AppGroupType) {
val appId = appId ?: error("require appId")
DbSet.subsConfigDao.queryAppGroupTypeConfig(subsId, appId, groupKey)
Db.subsConfigDao.queryAppGroupTypeConfig(subsId, appId, groupKey)
} else {
DbSet.subsConfigDao.queryGlobalGroupTypeConfig(subsId, groupKey)
Db.subsConfigDao.queryGlobalGroupTypeConfig(subsId, groupKey)
}
}
@@ -132,7 +132,7 @@ suspend fun updateRuleGroupEnable(
)
}
}
DbSet.subsConfigDao.insert(newConfig)
Db.subsConfigDao.insert(newConfig)
}
suspend fun batchUpdateGroupEnable(
@@ -245,7 +245,7 @@ suspend fun batchUpdateGroupEnable(
val canDeleteList = newSubsConfigs.filter {
it.type == SubsConfig.AppGroupType && it.enable == null && it.exclude.isEmpty()
}
DbSet.subsConfigDao.insertAndDelete(
Db.subsConfigDao.insertAndDelete(
newSubsConfigs.filterNot { canDeleteList.contains(it) },
canDeleteList
)
@@ -303,14 +303,14 @@ class RuleGroupState(
): String {
if (group is RawSubscription.RawGlobalGroup && state.pageAppId != null) {
val excludeData = ExcludeData.parse(subsConfig.exclude)
DbSet.subsConfigDao.update(
Db.subsConfigDao.update(
subsConfig.copy(
exclude = excludeData.clear(appId = state.pageAppId).stringify(),
),
)
return "已重置局部开关至默认值"
}
DbSet.subsConfigDao.update(subsConfig.copy(enable = null))
Db.subsConfigDao.update(subsConfig.copy(enable = null))
return "已重置开关至默认值"
}
@@ -352,7 +352,7 @@ class RuleGroupState(
appId = appId,
groupKey = groupKey,
)).copy(exclude = excludeData.stringify())
DbSet.subsConfigDao.insert(newSubsConfig)
Db.subsConfigDao.insert(newSubsConfig)
}
@Composable

View File

@@ -15,7 +15,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import li.gkd.app.data.AppConfig
import li.gkd.db.AppConfig
import li.gkd.app.data.AppInfo
import li.gkd.app.data.RawSubscription
import li.gkd.app.store.blockMatchAppListFlow

View File

@@ -32,7 +32,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import li.gkd.app.META
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsItem
import li.gkd.db.SubsItem
import li.gkd.app.util.formatTimeAgo
import li.gkd.app.util.throttle

View File

@@ -20,7 +20,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.util.isLocalNetworkUrl
import li.gkd.app.util.throttle
import li.gkd.app.util.toast
@@ -80,7 +80,7 @@ class SubsLinkDialogState(
suspend fun request(initialValue: String = ""): String? {
val existingUrls = withContext(Dispatchers.IO) {
DbSet.subsItemDao.queryAll().mapNotNullTo(mutableSetOf()) { it.updateUrl }
Db.subsItemDao.queryAll().mapNotNullTo(mutableSetOf()) { it.updateUrl }
}
val value = withContext(Dispatchers.Main.immediate) {
requestMutex.withLock {

View File

@@ -36,6 +36,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlinx.coroutines.flow.MutableStateFlow
import li.gkd.app.META
import li.gkd.app.data.mtimeStr
import li.gkd.app.ui.ActionLogRoute
import li.gkd.app.ui.SubsAppListRoute
import li.gkd.app.ui.SubsCategoryRoute
@@ -44,7 +45,7 @@ import li.gkd.app.ui.SubsSheetVm
import li.gkd.app.ui.share.LocalMainViewModel
import li.gkd.app.ui.style.EmptyHeight
import li.gkd.app.ui.style.itemHorizontalPadding
import li.gkd.app.util.LOCAL_SUBS_ID
import li.gkd.db.LOCAL_SUBS_ID
import li.gkd.app.util.SubscriptionResult
import li.gkd.app.util.launchTry
import li.gkd.app.util.subsItemsFlow
@@ -312,7 +313,8 @@ class SubsSheetState {
)
}
}
if (!subsItem.isLocal && subsItem.updateUrl != null) {
val updateUrl = subsItem.updateUrl
if (!subsItem.isLocal && updateUrl != null) {
Row(
modifier = Modifier
.clickable(onClickLabel = "编辑订阅链接", onClick = throttle {
@@ -321,8 +323,9 @@ class SubsSheetState {
return@throttle
}
scope.launchTry {
val url =
mainVm.subsLinkDialog.request(initialValue = subsItem.updateUrl)
val url = mainVm.subsLinkDialog.request(
initialValue = updateUrl,
)
?: return@launchTry
vm.addOrModifySubscription(url, subsItem).message?.let {
toast(it)
@@ -340,7 +343,7 @@ class SubsSheetState {
style = MaterialTheme.typography.labelLarge,
)
Text(
text = subsItem.updateUrl,
text = updateUrl,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.secondary,
softWrap = false,
@@ -348,7 +351,7 @@ class SubsSheetState {
modifier = Modifier
.clearAndSetSemantics {}
.clickable(onClickLabel = "查看订阅链接", onClick = {
mainVm.openUrl(subsItem.updateUrl)
mainVm.openUrl(updateUrl)
})
)
}

View File

@@ -37,7 +37,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import li.gkd.app.MainActivity
import li.gkd.app.R
import li.gkd.app.data.SubsConfig
import li.gkd.db.SubsConfig
import li.gkd.app.permission.PermissionStates
import li.gkd.app.priv.PrivilegeServiceStatus
import li.gkd.app.priv.privilegeContextFlow

View File

@@ -62,7 +62,7 @@ import li.gkd.app.ui.share.ListPlaceholder
import li.gkd.app.ui.share.Loadable
import li.gkd.app.ui.share.LocalMainViewModel
import li.gkd.app.ui.style.EmptyHeight
import li.gkd.app.util.LOCAL_SUBS_ID
import li.gkd.db.LOCAL_SUBS_ID
import li.gkd.app.util.ShortUrlSet
import li.gkd.app.util.SubscriptionResult
import li.gkd.app.util.UpdateTimeOption

View File

@@ -12,8 +12,8 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsItem
import li.gkd.app.db.DbSet
import li.gkd.db.SubsItem
import li.gkd.db.Db
import li.gkd.app.store.storeFlow
import li.gkd.app.ui.share.BaseViewModel
import li.gkd.app.ui.share.Loadable
@@ -55,7 +55,7 @@ class SubsManageVm : BaseViewModel() {
Loadable.Loading -> flowOf(Loadable.Loading)
is Loadable.Failure -> flowOf(snapshotState)
is Loadable.Ready -> combine(
DbSet.subsItemDao.query(),
Db.subsItemDao.query(),
SubscriptionStore.updating,
) { subItems, refreshing ->
buildSubsManageUiState(
@@ -95,7 +95,7 @@ class SubsManageVm : BaseViewModel() {
fun updateOrder(items: List<SubsItem>) {
scope.launchTry(Dispatchers.IO) {
DbSet.subsItemDao.batchUpdateOrder(items)
Db.subsItemDao.batchUpdateOrder(items)
}
}
@@ -131,7 +131,7 @@ class SubsManageVm : BaseViewModel() {
private fun setSubscriptionEnabled(item: SubsItem, enabled: Boolean) {
scope.launchTry(Dispatchers.IO) {
DbSet.subsItemDao.updateEnable(item.id, enabled)
Db.subsItemDao.updateEnable(item.id, enabled)
}
}

View File

@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.map
import li.gkd.app.MainViewModel
import li.gkd.app.data.AppInfo
import li.gkd.app.data.RawSubscription
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.store.SettingsStore
import li.gkd.app.store.blockMatchAppListFlow
import li.gkd.app.store.storeFlow
@@ -28,7 +28,7 @@ class AppFilter(
fun BaseViewModel.subsAppActionOrderMapState(
subsId: Long,
): StateFlow<Loadable<Map<String, Int>>> =
DbSet.actionLogDao.queryLatestUniqueAppIds(subsId).map { appIds ->
Db.actionLogDao.queryLatestUniqueAppIds(subsId).map { appIds ->
appIds.mapIndexed { index, appId -> appId to index }.toMap()
}.stateLoadable()
@@ -36,7 +36,7 @@ fun BaseViewModel.globalGroupAppOrderListState(
subsId: Long,
groupKey: Int,
): StateFlow<Loadable<List<String>>> =
DbSet.actionLogDao.queryLatestUniqueAppIds(subsId, groupKey).stateLoadable()
Db.actionLogDao.queryLatestUniqueAppIds(subsId, groupKey).stateLoadable()
fun BaseViewModel.useAppFilter(
mainVm: MainViewModel,

View File

@@ -5,12 +5,12 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import li.gkd.app.data.AppConfig
import li.gkd.app.data.CategoryConfig
import li.gkd.db.AppConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.SubsItem
import li.gkd.app.db.DbSet
import li.gkd.db.SubsConfig
import li.gkd.db.SubsItem
import li.gkd.db.Db
import li.gkd.app.store.a11yScopeAppListFlow
import li.gkd.app.store.actionCountFlow
import li.gkd.app.store.blockA11yAppListFlow
@@ -53,10 +53,10 @@ object BackupUtils {
tempDir.resolve("db.json").writeText(
json.encodeToString(
DbData(
subsItems = DbSet.subsItemDao.queryAll(),
subsConfigs = DbSet.subsConfigDao.queryAll(),
categoryConfigs = DbSet.categoryConfigDao.queryAll(),
appConfigs = DbSet.appConfigDao.queryAll(),
subsItems = Db.subsItemDao.queryAll(),
subsConfigs = Db.subsConfigDao.queryAll(),
categoryConfigs = Db.categoryConfigDao.queryAll(),
appConfigs = Db.appConfigDao.queryAll(),
)
)
)
@@ -90,18 +90,18 @@ object BackupUtils {
val prepared = prepareBackup(unzipDir)
prepared.dbData?.let { dbData ->
DbSet.withTransaction {
Db.withTransaction {
if (!dbData.subsItems.isNullOrEmpty()) {
DbSet.subsItemDao.insertOrIgnore(*dbData.subsItems.toTypedArray())
Db.subsItemDao.insertOrIgnore(*dbData.subsItems.toTypedArray())
}
if (!dbData.subsConfigs.isNullOrEmpty()) {
DbSet.subsConfigDao.insertOrIgnore(*dbData.subsConfigs.toTypedArray())
Db.subsConfigDao.insertOrIgnore(*dbData.subsConfigs.toTypedArray())
}
if (!dbData.categoryConfigs.isNullOrEmpty()) {
DbSet.categoryConfigDao.insertOrIgnore(*dbData.categoryConfigs.toTypedArray())
Db.categoryConfigDao.insertOrIgnore(*dbData.categoryConfigs.toTypedArray())
}
if (!dbData.appConfigs.isNullOrEmpty()) {
DbSet.appConfigDao.insertOrIgnore(*dbData.appConfigs.toTypedArray())
Db.appConfigDao.insertOrIgnore(*dbData.appConfigs.toTypedArray())
}
}
}

View File

@@ -11,10 +11,6 @@ const val ISSUES_URL = "${REPOSITORY_URL}/issues"
const val HOME_PAGE_URL = "https://gkd.li"
const val LOCAL_SUBS_ID = -2L
const val LOCAL_HTTP_SUBS_ID = -1L
val LOCAL_SUBS_IDS = arrayOf(LOCAL_SUBS_ID, LOCAL_HTTP_SUBS_ID)
const val EMPTY_RULE_TIP = "暂无规则"
object ShortUrlSet {

View File

@@ -8,18 +8,18 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import li.gkd.app.appScope
import li.gkd.app.data.AppRule
import li.gkd.app.data.CategoryConfig
import li.gkd.db.CategoryConfig
import li.gkd.app.data.GlobalRule
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.ResolvedAppGroup
import li.gkd.app.data.ResolvedGlobalGroup
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.SubsItem
import li.gkd.app.db.DbSet
import li.gkd.db.SubsConfig
import li.gkd.db.SubsItem
import li.gkd.db.Db
import java.net.URI
val subsItemsFlow by lazy {
DbSet.subsItemDao.query().stateIn(appScope, SharingStarted.Eagerly, emptyList())
Db.subsItemDao.query().stateIn(appScope, SharingStarted.Eagerly, emptyList())
}
private fun getCheckUpdateUrl(
@@ -78,7 +78,7 @@ val subsRefreshErrorsFlow by lazy {
}
val latestRecordFlow by lazy {
DbSet.actionLogDao.queryLatest().stateIn(appScope, SharingStarted.Eagerly, null)
Db.actionLogDao.queryLatest().stateIn(appScope, SharingStarted.Eagerly, null)
}
val latestRecordDescFlow by lazy {
combine(
@@ -217,9 +217,9 @@ val ruleSummaryFlow by lazy {
combine(
usedSubsEntriesFlow,
appInfoMapFlow,
DbSet.appConfigDao.queryUsedList(),
DbSet.subsConfigDao.queryUsedList(),
DbSet.categoryConfigDao.queryUsedList(),
Db.appConfigDao.queryUsedList(),
Db.subsConfigDao.queryUsedList(),
Db.categoryConfigDao.queryUsedList(),
) { subsEntries, appInfoCache, appConfigs, subsConfigs, categoryConfigs ->
val globalSubsConfigs = subsConfigs.filter { c -> c.type == SubsConfig.GlobalGroupType }
val groupSubsConfigs = subsConfigs.filter { c -> c.type == SubsConfig.AppGroupType }

View File

@@ -9,11 +9,13 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withContext
import li.gkd.app.data.RawSubscription
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.SubsItem
import li.gkd.db.SubsConfig
import li.gkd.db.SubsItem
import li.gkd.app.data.SubsVersion
import li.gkd.app.db.DbSet
import li.gkd.db.Db
import li.gkd.app.ui.share.Loadable
import li.gkd.db.LOCAL_HTTP_SUBS_ID
import li.gkd.db.LOCAL_SUBS_ID
import li.songe.json5.decodeFromJson5String
import java.io.File
import java.io.FileOutputStream
@@ -59,7 +61,7 @@ object SubscriptionStore {
snapshotFlow.value = Loadable.Loading
try {
refreshRawSubscriptions(
items = DbSet.subsItemDao.queryAll(),
items = Db.subsItemDao.queryAll(),
previous = SubscriptionSnapshot(),
)
} catch (e: Exception) {
@@ -73,7 +75,7 @@ object SubscriptionStore {
private suspend fun ensureLocalSubscription() = withContext(Dispatchers.IO) {
updateMutex.withStateLock {
try {
val items = DbSet.subsItemDao.queryAll()
val items = Db.subsItemDao.queryAll()
if (snapshotFlow.value !is Loadable.Ready) {
refreshRawSubscriptions(
items = items,
@@ -87,7 +89,7 @@ object SubscriptionStore {
)
val file = subsFolder.resolve("$LOCAL_SUBS_ID.json")
if (file.exists()) {
DbSet.subsItemDao.insert(item)
Db.subsItemDao.insert(item)
refreshRawSubscriptions(listOf(item))
} else {
saveLocked(
@@ -142,7 +144,7 @@ object SubscriptionStore {
"订阅与订阅项id不一致: ${subscription.id} != ${defaultItem.id}"
}
updateMutex.withStateLock {
val currentItem = DbSet.subsItemDao.queryAll().find { it.id == subscription.id }
val currentItem = Db.subsItemDao.queryAll().find { it.id == subscription.id }
try {
saveLocked(
subscription = subscription,
@@ -185,12 +187,12 @@ object SubscriptionStore {
var result: SubscriptionResult = SubscriptionResult.Busy
updateMutex.withStateLock {
val deleteSize = try {
DbSet.withTransaction {
val size = DbSet.subsItemDao.deleteById(*subscriptionIds)
Db.withTransaction {
val size = Db.subsItemDao.deleteById(*subscriptionIds)
if (size > 0) {
DbSet.subsConfigDao.deleteBySubsId(*subscriptionIds)
DbSet.actionLogDao.deleteBySubsId(*subscriptionIds)
DbSet.categoryConfigDao.deleteBySubsId(*subscriptionIds)
Db.subsConfigDao.deleteBySubsId(*subscriptionIds)
Db.actionLogDao.deleteBySubsId(*subscriptionIds)
Db.categoryConfigDao.deleteBySubsId(*subscriptionIds)
}
size
}
@@ -251,7 +253,7 @@ object SubscriptionStore {
if (updateMutex.mutex.isLocked) return@withContext SubscriptionResult.Busy
var result: SubscriptionResult = SubscriptionResult.Busy
updateMutex.withStateLock {
val items = DbSet.subsItemDao.queryAll()
val items = Db.subsItemDao.queryAll()
if (items.any { it.updateUrl == url && it.id != oldItem?.id }) {
result = failure("已有相同链接订阅")
return@withStateLock
@@ -326,7 +328,7 @@ object SubscriptionStore {
var result: SubscriptionResult = SubscriptionResult.Busy
updateMutex.withStateLock {
val items = try {
DbSet.subsItemDao.queryAll()
Db.subsItemDao.queryAll()
} catch (e: Exception) {
if (snapshotFlow.value !is Loadable.Ready) {
snapshotFlow.value = Loadable.Failure(e)
@@ -380,7 +382,7 @@ object SubscriptionStore {
val id = subscription.id
val snapshot = snapshotFlow.value.value
?: refreshRawSubscriptions(
items = DbSet.subsItemDao.queryAll(),
items = Db.subsItemDao.queryAll(),
previous = SubscriptionSnapshot(),
)
val nextSubscription = if (
@@ -398,15 +400,15 @@ object SubscriptionStore {
val previousBytes = file.takeIf { it.exists() }?.readBytes()
writeAtomic(file, json.encodeToString(nextSubscription).encodeToByteArray())
try {
DbSet.withTransaction {
Db.withTransaction {
if (newItem != null) {
if (insertItem) {
DbSet.subsItemDao.insert(newItem)
Db.subsItemDao.insert(newItem)
} else {
DbSet.subsItemDao.update(newItem)
Db.subsItemDao.update(newItem)
}
}
DbSet.subsItemDao.updateMtime(id, System.currentTimeMillis())
Db.subsItemDao.updateMtime(id, System.currentTimeMillis())
cleanupConfigs(id, nextSubscription)
}
} catch (e: Exception) {
@@ -510,7 +512,7 @@ object SubscriptionStore {
val appKeys = subscription.apps.associate { app ->
app.id to app.groups.map { it.key }.toHashSet()
}
val configs = DbSet.subsConfigDao.querySubsItemConfig(listOf(id))
val configs = Db.subsConfigDao.querySubsItemConfig(listOf(id))
val obsolete = configs.filter { config ->
when (config.type) {
SubsConfig.AppGroupType -> appKeys[config.appId]?.contains(config.groupKey) != true
@@ -519,7 +521,7 @@ object SubscriptionStore {
}
}
if (obsolete.isEmpty()) return 0
DbSet.subsConfigDao.delete(*obsolete.toTypedArray())
Db.subsConfigDao.delete(*obsolete.toTypedArray())
LogUtils.d("清理已移除规则配置", "subsId=$id, delete=${obsolete.size}")
return obsolete.size
}
@@ -527,7 +529,8 @@ object SubscriptionStore {
private suspend fun fetchUpdate(entry: SubsEntry): RawSubscription? {
val item = entry.subsItem
val current = entry.subscription
if (item.updateUrl == null || item.id < 0) return null
val itemUpdateUrl = item.updateUrl ?: return null
if (item.id < 0) return null
val checkUrl = entry.checkUpdateUrl
if (checkUrl != null && current != null) {
try {
@@ -539,7 +542,7 @@ object SubscriptionStore {
LogUtils.d("快速检测更新失败", item, e.message)
}
}
val updateUrl = current?.updateUrl ?: item.updateUrl
val updateUrl = current?.updateUrl ?: itemUpdateUrl
val text = try {
client.get(updateUrl).bodyAsText()
} catch (e: Exception) {

26
gkd-db/build.gradle.kts Normal file
View File

@@ -0,0 +1,26 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.androidx.room)
alias(libs.plugins.kotlin.serialization)
alias(libs.plugins.google.ksp)
}
android {
namespace = "li.gkd.db"
}
room {
schemaDirectory("$projectDir/schemas")
}
dependencies {
implementation(libs.kotlin.stdlib)
api(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
api(libs.androidx.room.paging)
ksp(libs.androidx.room.compiler)
api(libs.kotlinx.serialization.core)
implementation(libs.kotlinx.serialization.json)
}

View File

@@ -1,6 +1,5 @@
package li.gkd.app.data
package li.gkd.db
import android.view.accessibility.AccessibilityEvent
import androidx.paging.PagingSource
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -10,7 +9,6 @@ import androidx.room.PrimaryKey
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
import li.gkd.app.a11y.STATE_CHANGED
@Serializable
@Entity(tableName = "a11y_event_log")
@@ -32,24 +30,6 @@ class A11yEventLog(
return id
}
val isStateChanged: Boolean
get() = type == STATE_CHANGED
val fixedName: String
get() {
if (isStateChanged && name.startsWith(appId)) {
return name.substring(appId.length)
}
if (name.contains("View") || name.contains("Layout") || viewSuffixes.any {
name.startsWith(
it
)
}) {
return name.substring(name.lastIndexOf('.') + 1)
}
return name
}
@Dao
interface A11yEventLogDao {
@Insert
@@ -88,19 +68,3 @@ class A11yEventLog(
}
}
private val viewSuffixes = listOf(
"android.widget.",
"android.view.",
"android.support.",
)
fun AccessibilityEvent.toA11yEventLog(id: Int) = A11yEventLog(
id = id,
ctime = System.currentTimeMillis(),
type = eventType,
appId = packageName.toString(),
name = className.toString(),
desc = contentDescription?.toString(),
text = text.map { it.toString() }
)

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.paging.PagingSource
import androidx.room.ColumnInfo
@@ -11,8 +11,6 @@ import androidx.room.Query
import androidx.room.migration.AutoMigrationSpec
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
import li.gkd.app.util.format
import li.gkd.app.util.getShowActivityId
@Serializable
@Entity(
@@ -30,11 +28,6 @@ data class ActionLog(
@ColumnInfo(name = "rule_index") val ruleIndex: Int,
@ColumnInfo(name = "rule_key") val ruleKey: Int? = null,
) {
val showActivityId by lazy { getShowActivityId(appId, activityId) }
val date by lazy { ctime.format("MM-dd HH:mm:ss SSS") }
@DeleteTable.Entries(
DeleteTable(tableName = "click_log")
)

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.paging.PagingSource
import androidx.room.ColumnInfo
@@ -10,23 +10,16 @@ import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.migration.AutoMigrationSpec
import kotlinx.coroutines.flow.Flow
import li.gkd.app.util.format
import li.gkd.app.util.getShowActivityId
@Entity(
tableName = "activity_log_v2",
)
data class ActivityLog(
// 不使用时间戳作为主键的原因
// https://github.com/gkd-kit/gkd/issues/704
@PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") val id: Int = 0,
@ColumnInfo(name = "ctime") val ctime: Long,
@ColumnInfo(name = "app_id") val appId: String,
@ColumnInfo(name = "activity_id") val activityId: String? = null,
) {
val showActivityId by lazy { getShowActivityId(appId, activityId) }
val date by lazy { ctime.format("HH:mm:ss SSS") }
@Dao
interface ActivityLogDao {
@Insert

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -16,7 +16,7 @@ import kotlinx.serialization.Serializable
tableName = "app_config",
)
data class AppConfig(
@PrimaryKey @ColumnInfo(name = "id") val id: Long = System.currentTimeMillis(),
@PrimaryKey @ColumnInfo(name = "id") val id: Long = buildUniqueTimeMillisId(),
@ColumnInfo(name = "enable") val enable: Boolean,
@ColumnInfo(name = "subs_id") val subsId: Long,
@ColumnInfo(name = "app_id") val appId: String,

View File

@@ -1,27 +1,17 @@
package li.gkd.app.db
package li.gkd.db
import android.content.Context
import androidx.room.AutoMigration
import androidx.room.Database
import androidx.room.DeleteColumn
import androidx.room.RenameColumn
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.withTransaction
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import androidx.room.migration.AutoMigrationSpec
import li.gkd.app.app
import li.gkd.app.data.A11yEventLog
import li.gkd.app.data.ActionLog
import li.gkd.app.data.ActivityLog
import li.gkd.app.data.AppConfig
import li.gkd.app.data.AppVisitLog
import li.gkd.app.data.CategoryConfig
import li.gkd.app.data.Snapshot
import li.gkd.app.data.SubsConfig
import li.gkd.app.data.SubsItem
import li.gkd.app.util.dbFolder
import li.gkd.app.util.json
import androidx.room.withTransaction
import kotlinx.serialization.json.Json
@Database(
version = 14,
@@ -93,6 +83,12 @@ class Migration10To11Spec : AutoMigrationSpec
@Suppress("unused")
class DbConverters {
private val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
encodeDefaults = true
}
@TypeConverter
fun fromListStringToString(list: List<String>): String {
return json.encodeToString(list)
@@ -109,23 +105,35 @@ class DbConverters {
}
}
object DbSet {
private val db by lazy {
Room.databaseBuilder(
app,
AppDb::class.java,
dbFolder.resolve("gkd.db").absolutePath
).fallbackToDestructiveMigration(false).build()
}
val subsItemDao get() = db.subsItemDao()
val subsConfigDao get() = db.subsConfigDao()
val snapshotDao get() = db.snapshotDao()
val actionLogDao get() = db.actionLogDao()
val categoryConfigDao get() = db.categoryConfigDao()
val activityLogDao get() = db.activityLogDao()
val appConfigDao get() = db.appConfigDao()
val appVisitLogDao get() = db.appVisitLogDao()
val a11yEventLogDao get() = db.a11yEventLogDao()
object Db {
private var createDatabase: (() -> AppDb)? = null
suspend fun <T> withTransaction(block: suspend () -> T): T = db.withTransaction(block)
fun initialize(context: Context, databasePath: String) {
check(createDatabase == null) { "Db is already initialized" }
val applicationContext = context.applicationContext
createDatabase = {
Room.databaseBuilder(
applicationContext,
AppDb::class.java,
databasePath,
).build()
}
}
private val database by lazy {
checkNotNull(createDatabase) { "Db is not initialized" }.invoke()
}
val subsItemDao get() = database.subsItemDao()
val subsConfigDao get() = database.subsConfigDao()
val snapshotDao get() = database.snapshotDao()
val actionLogDao get() = database.actionLogDao()
val categoryConfigDao get() = database.categoryConfigDao()
val activityLogDao get() = database.activityLogDao()
val appConfigDao get() = database.appConfigDao()
val appVisitLogDao get() = database.appVisitLogDao()
val a11yEventLogDao get() = database.a11yEventLogDao()
suspend fun <T> withTransaction(block: suspend () -> T): T =
database.withTransaction(block)
}

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -7,11 +7,7 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
import li.gkd.app.META
import li.gkd.app.a11y.launcherAppId
import li.gkd.app.util.systemUiAppId
@Entity(
tableName = "app_visit_log",
@@ -25,17 +21,6 @@ data class AppVisitLog(
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(vararg objects: AppVisitLog): List<Long>
@Transaction
suspend fun insert(oldAppId: String, newAppId: String, mtime: Long) {
insert(
AppVisitLog(oldAppId, fixAppVisitTime(oldAppId, mtime - 1)),
AppVisitLog(newAppId, fixAppVisitTime(newAppId, mtime)),
)
if (appLogCount++ % 100 == 0) {
deleteKeepLatest()
}
}
@Query("SELECT DISTINCT id FROM app_visit_log ORDER BY mtime DESC")
fun query(): Flow<List<String>>
@@ -57,11 +42,3 @@ data class AppVisitLog(
suspend fun deleteKeepLatest(): Int
}
}
private fun fixAppVisitTime(appId: String, t: Long): Long = when (appId) {
META.appId -> t - 120_000
launcherAppId, systemUiAppId -> t - 60_000
else -> t
}
private var appLogCount = 0

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
interface BaseSnapshot {
val id: Long

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable
tableName = "category_config",
)
data class CategoryConfig(
@PrimaryKey @ColumnInfo(name = "id") val id: Long = System.currentTimeMillis(),
@PrimaryKey @ColumnInfo(name = "id") val id: Long = buildUniqueTimeMillisId(),
@ColumnInfo(name = "enable") val enable: Boolean? = null,
@ColumnInfo(name = "subs_id") val subsId: Long,
@ColumnInfo(name = "category_key") val categoryKey: Int,

View File

@@ -0,0 +1,14 @@
package li.gkd.db
private var lastEntityId = 0L
@Synchronized
internal fun buildUniqueTimeMillisId(): Long {
val currentTimeMillis = System.currentTimeMillis()
if (currentTimeMillis > lastEntityId) {
lastEntityId = currentTimeMillis
} else {
lastEntityId += 1
}
return lastEntityId
}

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -10,8 +10,6 @@ import androidx.room.Query
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
import li.gkd.app.snapshot.SnapshotStore
import li.gkd.app.util.format
@Entity(
tableName = "snapshot",
@@ -30,11 +28,6 @@ data class Snapshot(
@ColumnInfo(name = "github_asset_id") val githubAssetId: Int? = null,
) : BaseSnapshot {
val date by lazy { id.format("MM-dd HH:mm:ss") }
val screenshotFile by lazy { SnapshotStore.screenshotFile(id) }
@Dao
interface SnapshotDao {
@Update
@@ -60,5 +53,3 @@ data class Snapshot(
}
}

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -12,22 +12,6 @@ import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
import li.gkd.app.util.isValidActivityId
import li.gkd.app.util.isValidAppId
private var lastId = 0L
@Synchronized
private fun buildUniqueTimeMillisId(): Long {
val id = System.currentTimeMillis()
if (id > lastId) {
lastId = id
} else {
lastId += 1
}
return lastId
}
@Serializable
@Entity(
@@ -134,113 +118,6 @@ data class SubsConfig(
@Query("UPDATE subs_config SET enable = null WHERE type=${AppGroupType} AND subs_id=:subsItemId AND app_id=:appId AND group_key=:groupKey AND enable IS NOT NULL")
suspend fun resetAppGroupTypeEnable(subsItemId: Long, appId: String, groupKey: Int): Int
@Transaction
suspend fun batchResetAppGroupEnable(
subsItemId: Long,
list: List<Pair<RawSubscription.RawAppGroup, RawSubscription.RawApp>>
): List<Pair<RawSubscription.RawAppGroup, RawSubscription.RawApp>> {
return list.filter { (g, a) ->
resetAppGroupTypeEnable(subsItemId, a.id, g.key) > 0
}
}
}
}
data class ExcludeData(
val appIds: Map<String, Boolean>,
val activityIds: Set<Pair<String, String>>,
) {
val excludeAppIds = appIds.entries.filter { e -> e.value }.map { e -> e.key }.toHashSet()
val includeAppIds = appIds.entries.filter { e -> !e.value }.map { e -> e.key }.toHashSet()
fun stringify(appId: String? = null): String {
return if (appId != null) {
activityIds.filter { e -> e.first == appId }.map { e -> e.second }.sorted()
.joinToString("\n\n")
} else {
(appIds.entries.map { e ->
if (e.value) {
e.key
} else {
"!${e.key}"
}
} + activityIds.map { e -> "${e.first}/${e.second}" }).sorted().joinToString("\n\n")
}
}
fun clear(appId: String): ExcludeData {
return copy(
appIds = appIds.toMutableMap().apply {
remove(appId)
},
)
}
fun switch(appId: String, activityId: String? = null): ExcludeData {
return if (activityId == null) {
copy(
appIds = appIds.toMutableMap().apply {
if (get(appId) != false) {
set(appId, false)
} else {
set(appId, true)
}
},
)
} else {
copy(activityIds = activityIds.toMutableSet().apply {
val e = appId to activityId
if (contains(e)) {
remove(e)
} else {
add(e)
}
})
}
}
companion object {
private val empty = ExcludeData(emptyMap(), emptySet())
fun parse(exclude: String?): ExcludeData {
if (exclude.isNullOrBlank()) {
return empty
}
val appIds = HashMap<String, Boolean>()
val activityIds = HashSet<Pair<String, String>>()
exclude.split('\n')
.filter { it.isNotBlank() }
.forEach { s ->
if (s[0] == '!') {
val appId = s.substring(1)
if (appId.isValidAppId()) {
appIds[appId] = false
}
} else {
val a = s.split('/', limit = 2)
val appId = a[0]
if (appId.isValidAppId()) {
val activityId = a.getOrNull(1)
if (activityId != null) {
if (activityId.isValidActivityId()) {
activityIds.add(appId to activityId)
}
} else {
appIds[appId] = true
}
}
}
}
return ExcludeData(
appIds = appIds,
activityIds = activityIds,
)
}
fun parse(exclude: String?, appId: String): ExcludeData {
if (exclude.isNullOrBlank()) return empty
return parse(exclude.split('\n').joinToString("\n") { "$appId/$it" })
}
}
}

View File

@@ -1,4 +1,4 @@
package li.gkd.app.data
package li.gkd.db
import androidx.room.ColumnInfo
import androidx.room.Dao
@@ -12,8 +12,10 @@ import androidx.room.Transaction
import androidx.room.Update
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.Serializable
import li.gkd.app.util.LOCAL_SUBS_IDS
import li.gkd.app.util.format
const val LOCAL_SUBS_ID = -2L
const val LOCAL_HTTP_SUBS_ID = -1L
val LOCAL_SUBS_IDS = arrayOf(LOCAL_SUBS_ID, LOCAL_HTTP_SUBS_ID)
@Serializable
@Entity(
@@ -34,8 +36,6 @@ data class SubsItem(
val isLocal: Boolean
get() = LOCAL_SUBS_IDS.contains(id)
val mtimeStr by lazy { mtime.format("yyyy-MM-dd HH:mm:ss") }
@Dao
interface SubsItemDao {
@Update

View File

@@ -20,6 +20,7 @@ plugins {
rootProject.name = "gkd"
include(
":gkd-app",
":gkd-db",
":gkd-hidden-api",
":gkd-selector",
)