mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
feat: filter version (#483)
This commit is contained in:
parent
9ce2846f92
commit
705d931dcf
2
.github/workflows/Build-Apk.yml
vendored
2
.github/workflows/Build-Apk.yml
vendored
|
@ -25,8 +25,6 @@ jobs:
|
|||
|
||||
- name: write secrets info
|
||||
run: |
|
||||
echo GKD_APP_CENTER_SECRET='${{ secrets.GKD_APP_CENTER_SECRET }}' >> gradle.properties
|
||||
echo GKD_DEBUG_APP_CENTER_SECRET='${{ secrets.GKD_DEBUG_APP_CENTER_SECRET }}' >> gradle.properties
|
||||
echo ${{ secrets.GKD_STORE_FILE_BASE64 }} | base64 --decode > ${{ github.workspace }}/key.jks
|
||||
echo GKD_STORE_FILE='${{ github.workspace }}/key.jks' >> gradle.properties
|
||||
echo GKD_STORE_PASSWORD='${{ secrets.GKD_STORE_PASSWORD }}' >> gradle.properties
|
||||
|
|
2
.github/workflows/Build-Release.yml
vendored
2
.github/workflows/Build-Release.yml
vendored
|
@ -18,8 +18,6 @@ jobs:
|
|||
|
||||
- name: write secrets info
|
||||
run: |
|
||||
echo GKD_APP_CENTER_SECRET='${{ secrets.GKD_APP_CENTER_SECRET }}' >> gradle.properties
|
||||
echo GKD_DEBUG_APP_CENTER_SECRET='${{ secrets.GKD_DEBUG_APP_CENTER_SECRET }}' >> gradle.properties
|
||||
echo ${{ secrets.GKD_STORE_FILE_BASE64 }} | base64 --decode > ${{ github.workspace }}/key.jks
|
||||
echo GKD_STORE_FILE='${{ github.workspace }}/key.jks' >> gradle.properties
|
||||
echo GKD_STORE_PASSWORD='${{ secrets.GKD_STORE_PASSWORD }}' >> gradle.properties
|
||||
|
|
|
@ -69,9 +69,6 @@ android {
|
|||
"GIT_COMMIT_ID",
|
||||
jsonStringOf(gitInfo?.commitId)
|
||||
)
|
||||
buildConfigField(
|
||||
"String", "GKD_BUGLY_APP_ID", jsonStringOf(project.properties["GKD_BUGLY_APP_ID"])
|
||||
)
|
||||
resourceConfigurations.addAll(listOf("zh", "en"))
|
||||
ndk {
|
||||
// noinspection ChromeOsAbiSupport
|
||||
|
@ -181,7 +178,6 @@ dependencies {
|
|||
implementation(libs.rikka.shizuku.provider)
|
||||
implementation(libs.lsposed.hiddenapibypass)
|
||||
|
||||
implementation(libs.tencent.bugly)
|
||||
implementation(libs.tencent.mmkv)
|
||||
|
||||
implementation(libs.androidx.room.runtime)
|
||||
|
|
|
@ -4,12 +4,10 @@ import android.app.Application
|
|||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.tencent.bugly.crashreport.CrashReport
|
||||
import com.tencent.mmkv.MMKV
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.MainScope
|
||||
import li.songe.gkd.data.DeviceInfo
|
||||
import li.songe.gkd.debug.clearHttpSubs
|
||||
import li.songe.gkd.notif.initChannel
|
||||
import li.songe.gkd.util.GIT_COMMIT_URL
|
||||
|
@ -22,9 +20,9 @@ import org.lsposed.hiddenapibypass.HiddenApiBypass
|
|||
|
||||
val appScope by lazy { MainScope() }
|
||||
|
||||
private lateinit var _app: Application
|
||||
private lateinit var innerApp: Application
|
||||
val app: Application
|
||||
get() = _app
|
||||
get() = innerApp
|
||||
|
||||
|
||||
@HiltAndroidApp
|
||||
|
@ -38,27 +36,12 @@ class App : Application() {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
_app = this
|
||||
innerApp = this
|
||||
|
||||
@Suppress("SENSELESS_COMPARISON") if (BuildConfig.GKD_BUGLY_APP_ID != null) {
|
||||
CrashReport.setDeviceModel(this, DeviceInfo.instance.model)
|
||||
CrashReport.setIsDevelopmentDevice(this, BuildConfig.DEBUG)
|
||||
CrashReport.initCrashReport(applicationContext,
|
||||
BuildConfig.GKD_BUGLY_APP_ID,
|
||||
BuildConfig.DEBUG,
|
||||
CrashReport.UserStrategy(this).apply {
|
||||
setCrashHandleCallback(object : CrashReport.CrashHandleCallback() {
|
||||
override fun onCrashHandleStart(
|
||||
p0: Int,
|
||||
p1: String?,
|
||||
p2: String?,
|
||||
p3: String?,
|
||||
): MutableMap<String, String> {
|
||||
LogUtils.d(p0, p1, p2, p3) // 将报错日志输出到本地
|
||||
return super.onCrashHandleStart(p0, p1, p2, p3)
|
||||
}
|
||||
})
|
||||
})
|
||||
val errorHandler = Thread.getDefaultUncaughtExceptionHandler()
|
||||
Thread.setDefaultUncaughtExceptionHandler { t, e ->
|
||||
LogUtils.d("UncaughtExceptionHandler", t, e)
|
||||
errorHandler?.uncaughtException(t, e)
|
||||
}
|
||||
|
||||
MMKV.initialize(this)
|
||||
|
|
|
@ -3,8 +3,8 @@ package li.songe.gkd
|
|||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.viewModels
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.dylanc.activityresult.launcher.PickContentLauncher
|
||||
|
@ -31,6 +31,7 @@ import li.songe.gkd.util.storeFlow
|
|||
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : CompositionActivity({
|
||||
this as MainActivity
|
||||
useLifeCycleLog()
|
||||
val launcher = StartActivityLauncher(this)
|
||||
val pickContentLauncher = PickContentLauncher(this)
|
||||
|
@ -62,7 +63,8 @@ class MainActivity : CompositionActivity({
|
|||
LocalNavController provides navController
|
||||
) {
|
||||
DestinationsNavHost(
|
||||
navGraph = NavGraphs.root, navController = navController, modifier = Modifier
|
||||
navGraph = NavGraphs.root,
|
||||
navController = navController
|
||||
)
|
||||
}
|
||||
ConfirmDialog()
|
||||
|
@ -70,7 +72,9 @@ class MainActivity : CompositionActivity({
|
|||
UpgradeDialog()
|
||||
}
|
||||
}
|
||||
})
|
||||
}) {
|
||||
val mainVm by viewModels<MainViewModel>()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
65
app/src/main/kotlin/li/songe/gkd/MainViewModel.kt
Normal file
65
app/src/main/kotlin/li/songe/gkd/MainViewModel.kt
Normal file
|
@ -0,0 +1,65 @@
|
|||
package li.songe.gkd
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import li.songe.gkd.data.SubsItem
|
||||
import li.songe.gkd.db.DbSet
|
||||
import li.songe.gkd.util.authActionFlow
|
||||
import li.songe.gkd.util.checkUpdate
|
||||
import li.songe.gkd.util.initFolder
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.logZipDir
|
||||
import li.songe.gkd.util.newVersionApkDir
|
||||
import li.songe.gkd.util.snapshotZipDir
|
||||
import li.songe.gkd.util.storeFlow
|
||||
|
||||
class MainViewModel : ViewModel() {
|
||||
init {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val localSubsItem = SubsItem(
|
||||
id = -2, order = -2, mtime = System.currentTimeMillis()
|
||||
)
|
||||
if (!DbSet.subsItemDao.query().first().any { s -> s.id == localSubsItem.id }) {
|
||||
DbSet.subsItemDao.insert(localSubsItem)
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launchTry(Dispatchers.IO) {
|
||||
// 每次进入删除缓存
|
||||
listOf(snapshotZipDir, newVersionApkDir, logZipDir).forEach { dir ->
|
||||
if (dir.isDirectory && dir.exists()) {
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launchTry(Dispatchers.IO) {
|
||||
// 在某些机型由于未知原因创建失败
|
||||
// 在此保证每次重新打开APP都能重新检测创建
|
||||
initFolder()
|
||||
}
|
||||
|
||||
if (storeFlow.value.autoCheckAppUpdate) {
|
||||
appScope.launch {
|
||||
try {
|
||||
checkUpdate()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
authActionFlow.value = null
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ class AppRule(
|
|||
rawSubs: RawSubscription,
|
||||
exclude: String?,
|
||||
val app: RawSubscription.RawApp,
|
||||
val appInfo: AppInfo?,
|
||||
) : ResolvedRule(
|
||||
rule = rule,
|
||||
group = group,
|
||||
|
@ -14,9 +15,29 @@ class AppRule(
|
|||
rawSubs = rawSubs,
|
||||
exclude = exclude,
|
||||
) {
|
||||
val enable = appInfo?.let {
|
||||
if ((rule.excludeVersionCodes
|
||||
?: group.excludeVersionCodes)?.contains(appInfo.versionCode) == true
|
||||
) {
|
||||
return@let false
|
||||
}
|
||||
if ((rule.excludeVersionNames
|
||||
?: group.excludeVersionNames)?.contains(appInfo.versionName) == true
|
||||
) {
|
||||
return@let false
|
||||
}
|
||||
(rule.versionCodes ?: group.versionCodes)?.apply {
|
||||
return@let contains(appInfo.versionCode)
|
||||
}
|
||||
(rule.versionNames ?: group.versionNames)?.apply {
|
||||
return@let contains(appInfo.versionName)
|
||||
}
|
||||
|
||||
null
|
||||
} ?: true
|
||||
val appId = app.id
|
||||
val activityIds = getFixActivityIds(app.id, rule.activityIds ?: group.activityIds)
|
||||
val excludeActivityIds =
|
||||
private val activityIds = getFixActivityIds(app.id, rule.activityIds ?: group.activityIds)
|
||||
private val excludeActivityIds =
|
||||
(getFixActivityIds(
|
||||
app.id,
|
||||
rule.excludeActivityIds ?: group.excludeActivityIds
|
||||
|
@ -25,6 +46,7 @@ class AppRule(
|
|||
|
||||
override val type = "app"
|
||||
override fun matchActivity(appId: String, activityId: String?): Boolean {
|
||||
if (!enable) return false
|
||||
if (appId != app.id) return false
|
||||
activityId ?: return true
|
||||
if (excludeActivityIds.any { activityId.startsWith(it) }) return false
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import li.songe.gkd.service.launcherAppId
|
||||
import li.songe.gkd.util.systemAppsFlow
|
||||
|
||||
|
@ -16,6 +17,7 @@ class GlobalRule(
|
|||
group: RawSubscription.RawGlobalGroup,
|
||||
rawSubs: RawSubscription,
|
||||
exclude: String?,
|
||||
appInfoCache: ImmutableMap<String, AppInfo>,
|
||||
) : ResolvedRule(
|
||||
rule = rule,
|
||||
group = group,
|
||||
|
@ -23,17 +25,31 @@ class GlobalRule(
|
|||
rawSubs = rawSubs,
|
||||
exclude = exclude,
|
||||
) {
|
||||
|
||||
val matchAnyApp = rule.matchAnyApp ?: group.matchAnyApp ?: true
|
||||
val matchLauncher = rule.matchLauncher ?: group.matchLauncher ?: false
|
||||
val matchSystemApp = rule.matchSystemApp ?: group.matchSystemApp ?: false
|
||||
private val matchAnyApp = rule.matchAnyApp ?: group.matchAnyApp ?: true
|
||||
private val matchLauncher = rule.matchLauncher ?: group.matchLauncher ?: false
|
||||
private val matchSystemApp = rule.matchSystemApp ?: group.matchSystemApp ?: false
|
||||
val apps = mutableMapOf<String, GlobalApp>().apply {
|
||||
(rule.apps ?: group.apps ?: emptyList()).forEach { a ->
|
||||
val enable = a.enable ?: appInfoCache[a.id]?.let { appInfo ->
|
||||
if (a.excludeVersionCodes?.contains(appInfo.versionCode) == true) {
|
||||
return@let false
|
||||
}
|
||||
if (a.excludeVersionNames?.contains(appInfo.versionName) == true) {
|
||||
return@let false
|
||||
}
|
||||
a.versionCodes?.apply {
|
||||
return@let contains(appInfo.versionCode)
|
||||
}
|
||||
a.versionNames?.apply {
|
||||
return@let contains(appInfo.versionName)
|
||||
}
|
||||
null
|
||||
} ?: true
|
||||
this[a.id] = GlobalApp(
|
||||
id = a.id,
|
||||
enable = a.enable ?: true,
|
||||
enable = enable,
|
||||
activityIds = getFixActivityIds(a.id, a.activityIds),
|
||||
excludeActivityIds = getFixActivityIds(a.id, a.excludeActivityIds)
|
||||
excludeActivityIds = getFixActivityIds(a.id, a.excludeActivityIds),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
@ -16,6 +17,7 @@ import li.songe.gkd.util.json
|
|||
import li.songe.gkd.util.json5ToJson
|
||||
import li.songe.selector.Selector
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawSubscription(
|
||||
val id: Long,
|
||||
|
@ -76,6 +78,7 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawApp(
|
||||
val id: String,
|
||||
|
@ -83,6 +86,7 @@ data class RawSubscription(
|
|||
val groups: List<RawAppGroup> = emptyList(),
|
||||
)
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawCategory(val key: Int, val name: String, val enable: Boolean?)
|
||||
|
||||
|
@ -107,7 +111,7 @@ data class RawSubscription(
|
|||
val key: Int?
|
||||
val preKeys: List<Int>?
|
||||
val action: String?
|
||||
val matches: List<String>
|
||||
val matches: List<String>?
|
||||
val excludeMatches: List<String>?
|
||||
}
|
||||
|
||||
|
@ -123,6 +127,11 @@ data class RawSubscription(
|
|||
interface RawAppRuleProps {
|
||||
val activityIds: List<String>?
|
||||
val excludeActivityIds: List<String>?
|
||||
|
||||
val versionNames: List<String>?
|
||||
val excludeVersionNames: List<String>?
|
||||
val versionCodes: List<Long>?
|
||||
val excludeVersionCodes: List<Long>?
|
||||
}
|
||||
|
||||
interface RawGlobalRuleProps {
|
||||
|
@ -132,16 +141,20 @@ data class RawSubscription(
|
|||
val apps: List<RawGlobalApp>?
|
||||
}
|
||||
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawGlobalApp(
|
||||
val id: String,
|
||||
val enable: Boolean?,
|
||||
override val activityIds: List<String>?,
|
||||
override val excludeActivityIds: List<String>?,
|
||||
override val versionNames: List<String>?,
|
||||
override val excludeVersionNames: List<String>?,
|
||||
override val versionCodes: List<Long>?,
|
||||
override val excludeVersionCodes: List<Long>?,
|
||||
) : RawAppRuleProps
|
||||
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawGlobalGroup(
|
||||
override val name: String,
|
||||
|
@ -174,6 +187,7 @@ data class RawSubscription(
|
|||
|
||||
val allSelectorStrings by lazy {
|
||||
rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten()
|
||||
|
||||
}
|
||||
|
||||
val allSelector by lazy {
|
||||
|
@ -206,6 +220,7 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawGlobalRule(
|
||||
override val actionCd: Long?,
|
||||
|
@ -232,6 +247,7 @@ data class RawSubscription(
|
|||
override val apps: List<RawGlobalApp>?
|
||||
) : RawRuleProps, RawGlobalRuleProps
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawAppGroup(
|
||||
override val name: String,
|
||||
|
@ -254,10 +270,15 @@ data class RawSubscription(
|
|||
override val activityIds: List<String>?,
|
||||
override val excludeActivityIds: List<String>?,
|
||||
override val rules: List<RawAppRule>,
|
||||
override val versionNames: List<String>?,
|
||||
override val excludeVersionNames: List<String>?,
|
||||
override val versionCodes: List<Long>?,
|
||||
override val excludeVersionCodes: List<Long>?,
|
||||
) : RawGroupProps, RawAppRuleProps {
|
||||
|
||||
val allSelectorStrings by lazy {
|
||||
rules.map { r -> r.matches + (r.excludeMatches ?: emptyList()) }.flatten()
|
||||
rules.map { r -> (r.matches ?: emptyList()) + (r.excludeMatches ?: emptyList()) }
|
||||
.flatten()
|
||||
}
|
||||
|
||||
val allSelector by lazy {
|
||||
|
@ -290,13 +311,14 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
@Serializable
|
||||
data class RawAppRule(
|
||||
override val name: String?,
|
||||
override val key: Int?,
|
||||
override val preKeys: List<Int>?,
|
||||
override val action: String?,
|
||||
override val matches: List<String>,
|
||||
override val matches: List<String>?,
|
||||
override val excludeMatches: List<String>?,
|
||||
|
||||
override val actionCdKey: Int?,
|
||||
|
@ -314,11 +336,19 @@ data class RawSubscription(
|
|||
|
||||
override val activityIds: List<String>?,
|
||||
override val excludeActivityIds: List<String>?,
|
||||
|
||||
override val versionNames: List<String>?,
|
||||
override val excludeVersionNames: List<String>?,
|
||||
override val versionCodes: List<Long>?,
|
||||
override val excludeVersionCodes: List<Long>?,
|
||||
) : RawRuleProps, RawAppRuleProps
|
||||
|
||||
companion object {
|
||||
|
||||
private fun getStringIArray(json: JsonObject? = null, name: String): List<String>? {
|
||||
private fun getStringIArray(
|
||||
json: JsonObject? = null,
|
||||
name: String
|
||||
): List<String>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
is JsonObject -> error("Element ${this::class} can not be object")
|
||||
|
@ -333,7 +363,6 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("SameParameterValue")
|
||||
private fun getIntIArray(json: JsonObject? = null, name: String): List<Int>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
|
@ -349,6 +378,21 @@ data class RawSubscription(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLongIArray(json: JsonObject? = null, name: String): List<Long>? {
|
||||
return when (val element = json?.get(name)) {
|
||||
JsonNull, null -> null
|
||||
is JsonArray -> element.map {
|
||||
when (it) {
|
||||
is JsonObject, is JsonArray, JsonNull -> error("Element $it is not a int")
|
||||
is JsonPrimitive -> it.long
|
||||
}
|
||||
}
|
||||
|
||||
is JsonPrimitive -> listOf(element.long)
|
||||
else -> error("Element $element is not a Array")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getString(json: JsonObject? = null, key: String): String? =
|
||||
when (val p = json?.get(key)) {
|
||||
JsonNull, null -> null
|
||||
|
@ -402,9 +446,7 @@ data class RawSubscription(
|
|||
return RawAppRule(
|
||||
activityIds = getStringIArray(jsonObject, "activityIds"),
|
||||
excludeActivityIds = getStringIArray(jsonObject, "excludeActivityIds"),
|
||||
matches = (getStringIArray(
|
||||
jsonObject, "matches"
|
||||
) ?: emptyList()),
|
||||
matches = getStringIArray(jsonObject, "matches"),
|
||||
excludeMatches = getStringIArray(jsonObject, "excludeMatches"),
|
||||
key = getInt(jsonObject, "key"),
|
||||
name = getString(jsonObject, "name"),
|
||||
|
@ -422,6 +464,10 @@ data class RawSubscription(
|
|||
actionMaximumKey = getInt(jsonObject, "actionMaximumKey"),
|
||||
actionCdKey = getInt(jsonObject, "actionCdKey"),
|
||||
order = getInt(jsonObject, "order"),
|
||||
versionCodes = getLongIArray(jsonObject, "versionCodes"),
|
||||
excludeVersionCodes = getLongIArray(jsonObject, "excludeVersionCodes"),
|
||||
versionNames = getStringIArray(jsonObject, "versionNames"),
|
||||
excludeVersionNames = getStringIArray(jsonObject, "excludeVersionNames"),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -459,6 +505,10 @@ data class RawSubscription(
|
|||
actionCdKey = getInt(jsonObject, "actionCdKey"),
|
||||
order = getInt(jsonObject, "order"),
|
||||
scopeKeys = getIntIArray(jsonObject, "scopeKeys"),
|
||||
versionCodes = getLongIArray(jsonObject, "versionCodes"),
|
||||
excludeVersionCodes = getLongIArray(jsonObject, "excludeVersionCodes"),
|
||||
versionNames = getStringIArray(jsonObject, "versionNames"),
|
||||
excludeVersionNames = getStringIArray(jsonObject, "excludeVersionNames"),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -483,6 +533,10 @@ data class RawSubscription(
|
|||
enable = getBoolean(jsonObject, "enable"),
|
||||
activityIds = getStringIArray(jsonObject, "activityIds"),
|
||||
excludeActivityIds = getStringIArray(jsonObject, "excludeActivityIds"),
|
||||
versionCodes = getLongIArray(jsonObject, "versionCodes"),
|
||||
excludeVersionCodes = getLongIArray(jsonObject, "excludeVersionCodes"),
|
||||
versionNames = getStringIArray(jsonObject, "versionNames"),
|
||||
excludeVersionNames = getStringIArray(jsonObject, "excludeVersionNames"),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -542,7 +596,7 @@ data class RawSubscription(
|
|||
jsonToGlobalApp(
|
||||
jsonElement.jsonObject, index
|
||||
)
|
||||
},
|
||||
} ?: emptyList(),
|
||||
rules = jsonObject["rules"]?.jsonArray?.map { jsonElement ->
|
||||
jsonToGlobalRule(jsonElement.jsonObject)
|
||||
} ?: emptyList(),
|
||||
|
@ -559,12 +613,12 @@ data class RawSubscription(
|
|||
updateUrl = getString(rootJson, "updateUrl"),
|
||||
supportUri = getString(rootJson, "supportUri"),
|
||||
checkUpdateUrl = getString(rootJson, "checkUpdateUrl"),
|
||||
apps = rootJson["apps"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
apps = (rootJson["apps"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
jsonToAppRaw(
|
||||
jsonElement.jsonObject, index
|
||||
)
|
||||
} ?: emptyList(),
|
||||
categories = rootJson["categories"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
} ?: emptyList()),
|
||||
categories = (rootJson["categories"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
RawCategory(
|
||||
key = getInt(jsonElement.jsonObject, "key")
|
||||
?: error("miss categories[$index].key"),
|
||||
|
@ -572,10 +626,11 @@ data class RawSubscription(
|
|||
?: error("miss categories[$index].name"),
|
||||
enable = getBoolean(jsonElement.jsonObject, "enable"),
|
||||
)
|
||||
} ?: emptyList(),
|
||||
globalGroups = rootJson["globalGroups"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
} ?: emptyList()),
|
||||
globalGroups = (rootJson["globalGroups"]?.jsonArray?.mapIndexed { index, jsonElement ->
|
||||
jsonToGlobalGroups(jsonElement.jsonObject, index)
|
||||
} ?: emptyList())
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> List<T>.findDuplicatedItem(predicate: (T) -> Any?): T? {
|
||||
|
|
|
@ -16,24 +16,24 @@ sealed class ResolvedRule(
|
|||
) {
|
||||
val key = rule.key
|
||||
val index = group.rules.indexOf(rule)
|
||||
val preKeys = (rule.preKeys ?: emptyList()).toSet()
|
||||
val resetMatch = rule.resetMatch ?: group.resetMatch
|
||||
val matches = rule.matches.map { s -> Selector.parse(s) }
|
||||
val excludeMatches = (rule.excludeMatches ?: emptyList()).map { s -> Selector.parse(s) }
|
||||
private val preKeys = (rule.preKeys ?: emptyList()).toSet()
|
||||
private val resetMatch = rule.resetMatch ?: group.resetMatch
|
||||
private val matches = rule.matches?.map { s -> Selector.parse(s) } ?: emptyList()
|
||||
private val excludeMatches = (rule.excludeMatches ?: emptyList()).map { s -> Selector.parse(s) }
|
||||
val matchDelay = rule.matchDelay ?: group.matchDelay ?: 0L
|
||||
val actionDelay = rule.actionDelay ?: group.actionDelay ?: 0L
|
||||
val matchTime = rule.matchTime ?: group.matchTime
|
||||
val quickFind = rule.quickFind ?: group.quickFind ?: false
|
||||
private val matchTime = rule.matchTime ?: group.matchTime
|
||||
private val quickFind = rule.quickFind ?: group.quickFind ?: false
|
||||
|
||||
val actionCdKey = rule.actionCdKey ?: group.actionCdKey
|
||||
val actionCd = rule.actionCd ?: if (actionCdKey != null) {
|
||||
private val actionCdKey = rule.actionCdKey ?: group.actionCdKey
|
||||
private val actionCd = rule.actionCd ?: if (actionCdKey != null) {
|
||||
group.rules.find { r -> r.key == actionCdKey }?.actionCd
|
||||
} else {
|
||||
null
|
||||
} ?: group.actionCd ?: 1000L
|
||||
|
||||
val actionMaximumKey = rule.actionMaximumKey ?: group.actionMaximumKey
|
||||
val actionMaximum = rule.actionMaximum ?: if (actionMaximumKey != null) {
|
||||
private val actionMaximumKey = rule.actionMaximumKey ?: group.actionMaximumKey
|
||||
private val actionMaximum = rule.actionMaximum ?: if (actionMaximumKey != null) {
|
||||
group.rules.find { r -> r.key == actionMaximumKey }?.actionMaximum
|
||||
} else {
|
||||
null
|
||||
|
@ -41,7 +41,7 @@ sealed class ResolvedRule(
|
|||
|
||||
val order = rule.order ?: group.order ?: 0
|
||||
|
||||
val slowSelectors by lazy {
|
||||
private val slowSelectors by lazy {
|
||||
(matches + excludeMatches).filterNot { s ->
|
||||
((quickFind && s.canQf) || s.isMatchRoot) && !s.connectKeys.contains(
|
||||
"<<"
|
||||
|
@ -83,7 +83,7 @@ sealed class ResolvedRule(
|
|||
}.toSet()
|
||||
}
|
||||
|
||||
var preRules = emptySet<ResolvedRule>()
|
||||
private var preRules = emptySet<ResolvedRule>()
|
||||
val hasNext = group.rules.any { r -> r.preKeys?.any { k -> k == rule.key } == true }
|
||||
|
||||
var actionDelayTriggerTime = 0L
|
||||
|
@ -96,7 +96,7 @@ sealed class ResolvedRule(
|
|||
return false
|
||||
}
|
||||
|
||||
var actionTriggerTime = Value(0L)
|
||||
private var actionTriggerTime = Value(0L)
|
||||
fun trigger() {
|
||||
actionTriggerTime.value = System.currentTimeMillis()
|
||||
lastTriggerTime = actionTriggerTime.value
|
||||
|
@ -110,7 +110,7 @@ sealed class ResolvedRule(
|
|||
|
||||
var matchChangedTime = 0L
|
||||
|
||||
val matchLimitTime = (matchTime ?: 0) + matchDelay
|
||||
private val matchLimitTime = (matchTime ?: 0) + matchDelay
|
||||
|
||||
val resetMatchTypeWhenActivity = when (resetMatch) {
|
||||
"app" -> false
|
||||
|
@ -193,8 +193,7 @@ fun getFixActivityIds(
|
|||
appId: String,
|
||||
activityIds: List<String>?,
|
||||
): List<String> {
|
||||
activityIds ?: return emptyList()
|
||||
return activityIds.map { activityId ->
|
||||
return (activityIds ?: emptyList()).map { activityId ->
|
||||
if (activityId.startsWith('.')) { // .a.b.c -> com.x.y.x.a.b.c
|
||||
appId + activityId
|
||||
} else {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package li.songe.gkd.data
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
|
||||
@Stable
|
||||
data class Tuple3<T0, T1, T2>(
|
||||
val t0: T0,
|
||||
val t1: T1,
|
||||
|
@ -7,13 +10,3 @@ data class Tuple3<T0, T1, T2>(
|
|||
) {
|
||||
override fun toString() = "($t0, $t1, $t2)"
|
||||
}
|
||||
|
||||
data class Tuple4<T0, T1, T2, T3>(
|
||||
val t0: T0,
|
||||
val t1: T1,
|
||||
val t2: T2,
|
||||
val t3: T3,
|
||||
) {
|
||||
override fun toString() = "($t0, $t1, $t2, $t3)"
|
||||
}
|
||||
|
||||
|
|
|
@ -41,8 +41,8 @@ import li.songe.gkd.notif.createNotif
|
|||
import li.songe.gkd.notif.httpChannel
|
||||
import li.songe.gkd.notif.httpNotif
|
||||
import li.songe.gkd.service.GkdAbService
|
||||
import li.songe.gkd.util.Ext.getIpAddressInLocalNetwork
|
||||
import li.songe.gkd.util.SERVER_SCRIPT_URL
|
||||
import li.songe.gkd.util.getIpAddressInLocalNetwork
|
||||
import li.songe.gkd.util.keepNullJson
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.map
|
||||
|
|
|
@ -12,8 +12,8 @@ import li.songe.gkd.data.GlobalRule
|
|||
import li.songe.gkd.data.ResolvedRule
|
||||
import li.songe.gkd.data.SubsConfig
|
||||
import li.songe.gkd.db.DbSet
|
||||
import li.songe.gkd.util.Ext.getDefaultLauncherAppId
|
||||
import li.songe.gkd.util.RuleSummary
|
||||
import li.songe.gkd.util.getDefaultLauncherAppId
|
||||
import li.songe.gkd.util.increaseClickCount
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.recordStoreFlow
|
||||
|
|
|
@ -316,9 +316,13 @@ fun AppItemPage(
|
|||
.apply {
|
||||
set(
|
||||
indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups.filter { g -> g.key != menuGroupRaw.key })
|
||||
appRawVal.copy(
|
||||
groups = appRawVal.groups
|
||||
.filter { g -> g.key != menuGroupRaw.key }
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
updateSubscription(newSubsRaw)
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(
|
||||
|
@ -514,11 +518,11 @@ fun AppItemPage(
|
|||
val newSubsRaw = subsRaw.copy(apps = subsRaw.apps.toMutableList().apply {
|
||||
set(
|
||||
indexOfFirst { a -> a.id == appRawVal.id },
|
||||
appRawVal.copy(groups = appRawVal.groups + tempGroups.mapIndexed { i, g ->
|
||||
appRawVal.copy(groups = (appRawVal.groups + tempGroups.mapIndexed { i, g ->
|
||||
g.copy(
|
||||
key = newKey + i
|
||||
)
|
||||
})
|
||||
}))
|
||||
)
|
||||
})
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
|
|
|
@ -69,13 +69,13 @@ import li.songe.gkd.ui.component.AuthCard
|
|||
import li.songe.gkd.ui.component.SettingItem
|
||||
import li.songe.gkd.ui.component.TextSwitch
|
||||
import li.songe.gkd.ui.destinations.SnapshotPageDestination
|
||||
import li.songe.gkd.util.Ext
|
||||
import li.songe.gkd.util.LocalLauncher
|
||||
import li.songe.gkd.util.LocalNavController
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
import li.songe.gkd.util.authActionFlow
|
||||
import li.songe.gkd.util.canDrawOverlaysAuthAction
|
||||
import li.songe.gkd.util.checkOrRequestNotifPermission
|
||||
import li.songe.gkd.util.getIpAddressInLocalNetwork
|
||||
import li.songe.gkd.util.launchAsFn
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.navigate
|
||||
|
@ -204,7 +204,7 @@ fun DebugPage() {
|
|||
Spacer(modifier = Modifier.width(2.dp))
|
||||
Text(text = "仅本设备可访问")
|
||||
}
|
||||
Ext.getIpAddressInLocalNetwork().forEach { host ->
|
||||
getIpAddressInLocalNetwork().forEach { host ->
|
||||
Text(
|
||||
text = "http://${host}:${store.httpServerPort}",
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
|
@ -242,13 +242,13 @@ fun DebugPage() {
|
|||
HorizontalDivider()
|
||||
|
||||
TextSwitch(
|
||||
name = "自动清除内存订阅",
|
||||
desc = "当HTTP服务关闭时,清除内存订阅",
|
||||
checked = store.autoClearMemorySubs
|
||||
name = "保留内存订阅",
|
||||
desc = "当HTTP服务关闭时,保留内存订阅",
|
||||
checked = !store.autoClearMemorySubs
|
||||
) {
|
||||
updateStorage(
|
||||
storeFlow, store.copy(
|
||||
autoClearMemorySubs = it
|
||||
autoClearMemorySubs = !it
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -175,18 +175,13 @@ fun SubsPage(
|
|||
}
|
||||
IconButton(onClick = { expanded = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.Sort,
|
||||
contentDescription = null
|
||||
imageVector = Icons.AutoMirrored.Filled.Sort, contentDescription = null
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentSize(Alignment.TopStart)
|
||||
modifier = Modifier.wrapContentSize(Alignment.TopStart)
|
||||
) {
|
||||
DropdownMenu(
|
||||
expanded = expanded,
|
||||
onDismissRequest = { expanded = false }
|
||||
) {
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
|
||||
SortTypeOption.allSubObject.forEach { sortOption ->
|
||||
DropdownMenuItem(
|
||||
|
@ -194,11 +189,11 @@ fun SubsPage(
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
RadioButton(selected = sortType == sortOption,
|
||||
RadioButton(
|
||||
selected = sortType == sortOption,
|
||||
onClick = {
|
||||
vm.sortTypeFlow.value = sortOption
|
||||
}
|
||||
)
|
||||
})
|
||||
Text(sortOption.label)
|
||||
}
|
||||
},
|
||||
|
@ -213,11 +208,9 @@ fun SubsPage(
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Checkbox(
|
||||
checked = showUninstallApp,
|
||||
onCheckedChange = {
|
||||
vm.showUninstallAppFlow.value = it
|
||||
})
|
||||
Checkbox(checked = showUninstallApp, onCheckedChange = {
|
||||
vm.showUninstallAppFlow.value = it
|
||||
})
|
||||
Text("显示未安装应用")
|
||||
}
|
||||
},
|
||||
|
@ -243,8 +236,7 @@ fun SubsPage(
|
|||
},
|
||||
) { padding ->
|
||||
LazyColumn(
|
||||
modifier = Modifier.padding(padding),
|
||||
state = listState
|
||||
modifier = Modifier.padding(padding), state = listState
|
||||
) {
|
||||
itemsIndexed(appAndConfigs, { i, a -> i.toString() + a.t0.id }) { _, a ->
|
||||
val (appRaw, subsConfig, enableSize) = a
|
||||
|
@ -440,31 +432,29 @@ fun SubsPage(
|
|||
shape = RoundedCornerShape(16.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
text = "复制", modifier = Modifier
|
||||
.clickable {
|
||||
ClipboardUtils.copyText(
|
||||
json.encodeToJson5String(menuAppRawVal)
|
||||
)
|
||||
toast("复制成功")
|
||||
menuRawApp = null
|
||||
Text(text = "复制", modifier = Modifier
|
||||
.clickable {
|
||||
ClipboardUtils.copyText(
|
||||
json.encodeToJson5String(menuAppRawVal)
|
||||
)
|
||||
toast("复制成功")
|
||||
menuRawApp = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
Text(text = "删除", modifier = Modifier
|
||||
.clickable {
|
||||
// 也许需要二次确认
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id }))
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id)
|
||||
toast("删除成功")
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp))
|
||||
Text(
|
||||
text = "删除", modifier = Modifier
|
||||
.clickable {
|
||||
// 也许需要二次确认
|
||||
vm.viewModelScope.launchTry(Dispatchers.IO) {
|
||||
updateSubscription(subsRaw.copy(apps = subsRaw.apps.filter { a -> a.id != menuAppRawVal.id }))
|
||||
DbSet.subsItemDao.update(subsItemVal.copy(mtime = System.currentTimeMillis()))
|
||||
DbSet.subsConfigDao.delete(subsItemVal.id, menuAppRawVal.id)
|
||||
toast("删除成功")
|
||||
}
|
||||
menuRawApp = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp), color = MaterialTheme.colorScheme.error)
|
||||
menuRawApp = null
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp), color = MaterialTheme.colorScheme.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
package li.songe.gkd.ui.home
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||
import li.songe.gkd.util.ProfileTransitions
|
||||
|
@ -27,6 +32,13 @@ fun HomePage() {
|
|||
val vm = hiltViewModel<HomeVm>()
|
||||
val tab by vm.tabFlow.collectAsState()
|
||||
|
||||
val intent: Intent? = (LocalContext.current as Activity).intent
|
||||
LaunchedEffect(key1 = intent, block = {
|
||||
if (intent != null) {
|
||||
LogUtils.d(intent)
|
||||
}
|
||||
})
|
||||
|
||||
val appListPage = useAppListPage()
|
||||
val subsPage = useSubsManagePage()
|
||||
val controlPage = useControlPage()
|
||||
|
|
|
@ -20,11 +20,9 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import li.songe.gkd.appScope
|
||||
import li.songe.gkd.data.GithubPoliciesAsset
|
||||
import li.songe.gkd.data.RawSubscription
|
||||
import li.songe.gkd.data.RpcError
|
||||
|
@ -35,18 +33,11 @@ import li.songe.gkd.util.FILE_UPLOAD_URL
|
|||
import li.songe.gkd.util.LoadStatus
|
||||
import li.songe.gkd.util.SortTypeOption
|
||||
import li.songe.gkd.util.appInfoCacheFlow
|
||||
import li.songe.gkd.util.authActionFlow
|
||||
import li.songe.gkd.util.checkUpdate
|
||||
import li.songe.gkd.util.clickCountFlow
|
||||
import li.songe.gkd.util.client
|
||||
import li.songe.gkd.util.initFolder
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.logZipDir
|
||||
import li.songe.gkd.util.newVersionApkDir
|
||||
import li.songe.gkd.util.orderedAppInfosFlow
|
||||
import li.songe.gkd.util.ruleSummaryFlow
|
||||
import li.songe.gkd.util.snapshotZipDir
|
||||
import li.songe.gkd.util.storeFlow
|
||||
import li.songe.gkd.util.subsIdToRawFlow
|
||||
import li.songe.gkd.util.subsItemsFlow
|
||||
import li.songe.gkd.util.toast
|
||||
|
@ -58,46 +49,6 @@ import javax.inject.Inject
|
|||
class HomeVm @Inject constructor() : ViewModel() {
|
||||
val tabFlow = MutableStateFlow(controlNav)
|
||||
|
||||
init {
|
||||
appScope.launchTry(Dispatchers.IO) {
|
||||
val localSubsItem = SubsItem(
|
||||
id = -2, order = -2, mtime = System.currentTimeMillis()
|
||||
)
|
||||
if (!DbSet.subsItemDao.query().first().any { s -> s.id == localSubsItem.id }) {
|
||||
DbSet.subsItemDao.insert(localSubsItem)
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launchTry(Dispatchers.IO) {
|
||||
// 每次进入删除缓存
|
||||
listOf(snapshotZipDir, newVersionApkDir, logZipDir).forEach { dir ->
|
||||
if (dir.isDirectory && dir.exists()) {
|
||||
dir.listFiles()?.forEach { file ->
|
||||
if (file.isFile) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launchTry(Dispatchers.IO) {
|
||||
// 在某些机型由于未知原因创建失败
|
||||
// 在此保证每次重新打开APP都能重新检测创建
|
||||
initFolder()
|
||||
}
|
||||
|
||||
if (storeFlow.value.autoCheckAppUpdate) {
|
||||
appScope.launch {
|
||||
try {
|
||||
checkUpdate()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val uploadStatusFlow = MutableStateFlow<LoadStatus<GithubPoliciesAsset>?>(null)
|
||||
var uploadJob: Job? = null
|
||||
|
||||
|
@ -133,11 +84,6 @@ class HomeVm @Inject constructor() : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
authActionFlow.value = null
|
||||
}
|
||||
|
||||
private val latestRecordFlow =
|
||||
DbSet.clickLogDao.queryLatest().stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
||||
val latestRecordDescFlow = combine(
|
||||
|
|
|
@ -50,6 +50,7 @@ import androidx.compose.ui.window.Dialog
|
|||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.blankj.utilcode.util.ClipboardUtils
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import li.songe.gkd.data.SubsItem
|
||||
|
@ -109,7 +110,7 @@ fun useSubsManagePage(): ScaffoldExt {
|
|||
val state = rememberReorderableLazyListState(onMove = { from, to ->
|
||||
orderSubItems.value = orderSubItems.value.toMutableList().apply {
|
||||
add(to.index, removeAt(from.index))
|
||||
}
|
||||
}.toImmutableList()
|
||||
}, onDragEnd = { _, _ ->
|
||||
vm.viewModelScope.launch(Dispatchers.IO) {
|
||||
val changeItems = mutableListOf<SubsItem>()
|
||||
|
|
|
@ -6,6 +6,8 @@ import android.content.Intent
|
|||
import android.content.IntentFilter
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
|
@ -15,10 +17,10 @@ import li.songe.gkd.appScope
|
|||
import li.songe.gkd.data.AppInfo
|
||||
import li.songe.gkd.data.toAppInfo
|
||||
|
||||
val appInfoCacheFlow = MutableStateFlow(mapOf<String, AppInfo>())
|
||||
val appInfoCacheFlow = MutableStateFlow(emptyMap<String, AppInfo>().toImmutableMap())
|
||||
|
||||
val systemAppInfoCacheFlow =
|
||||
appInfoCacheFlow.map(appScope) { c -> c.filter { a -> a.value.isSystem } }
|
||||
appInfoCacheFlow.map(appScope) { c -> c.filter { a -> a.value.isSystem }.toImmutableMap() }
|
||||
|
||||
val systemAppsFlow = systemAppInfoCacheFlow.map(appScope) { c -> c.keys }
|
||||
|
||||
|
@ -35,7 +37,7 @@ val orderedAppInfosFlow = appInfoCacheFlow.map(appScope) { c ->
|
|||
b.name
|
||||
}
|
||||
)
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
||||
private val packageReceiver by lazy {
|
||||
|
@ -95,7 +97,7 @@ fun updateAppInfo(appIds: List<String>) {
|
|||
newMap.remove(appId)
|
||||
}
|
||||
}
|
||||
appInfoCacheFlow.value = newMap
|
||||
appInfoCacheFlow.value = newMap.toImmutableMap()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +114,7 @@ fun initAppState() {
|
|||
appMap[packageInfo.packageName] = info
|
||||
}
|
||||
}
|
||||
appInfoCacheFlow.value = appMap
|
||||
appInfoCacheFlow.value = appMap.toImmutableMap()
|
||||
}
|
||||
}
|
||||
}
|
8
app/src/main/kotlin/li/songe/gkd/util/BitmapExt.kt
Normal file
8
app/src/main/kotlin/li/songe/gkd/util/BitmapExt.kt
Normal file
|
@ -0,0 +1,8 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import android.graphics.Bitmap
|
||||
|
||||
fun Bitmap.isEmptyBitmap(): Boolean {
|
||||
val emptyBitmap = Bitmap.createBitmap(width, height, config)
|
||||
return this.sameAs(emptyBitmap)
|
||||
}
|
|
@ -1,40 +0,0 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import java.net.NetworkInterface
|
||||
|
||||
|
||||
object Ext {
|
||||
|
||||
fun Bitmap.isEmptyBitmap(): Boolean {
|
||||
val emptyBitmap = Bitmap.createBitmap(width, height, config)
|
||||
return this.sameAs(emptyBitmap)
|
||||
}
|
||||
|
||||
fun getIpAddressInLocalNetwork(): Sequence<String> {
|
||||
val networkInterfaces = try {
|
||||
// android.system.ErrnoException: getifaddrs failed: EACCES (Permission denied)
|
||||
NetworkInterface.getNetworkInterfaces().iterator().asSequence()
|
||||
} catch (e: Exception) {
|
||||
toast("获取host失败:" + e.message)
|
||||
emptySequence()
|
||||
}
|
||||
val localAddresses = networkInterfaces.flatMap {
|
||||
it.inetAddresses.asSequence().filter { inetAddress ->
|
||||
inetAddress.isSiteLocalAddress && !(inetAddress.hostAddress?.contains(":")
|
||||
?: false) && inetAddress.hostAddress != "127.0.0.1"
|
||||
}.map { inetAddress -> inetAddress.hostAddress }
|
||||
}
|
||||
return localAddresses
|
||||
}
|
||||
|
||||
fun PackageManager.getDefaultLauncherAppId(): String? {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
val defaultLauncher = this.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
return defaultLauncher?.activityInfo?.packageName
|
||||
}
|
||||
|
||||
}
|
20
app/src/main/kotlin/li/songe/gkd/util/NetworkExt.kt
Normal file
20
app/src/main/kotlin/li/songe/gkd/util/NetworkExt.kt
Normal file
|
@ -0,0 +1,20 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import java.net.NetworkInterface
|
||||
|
||||
fun getIpAddressInLocalNetwork(): List<String> {
|
||||
val networkInterfaces = try {
|
||||
// android.system.ErrnoException: getifaddrs failed: EACCES (Permission denied)
|
||||
NetworkInterface.getNetworkInterfaces().iterator().asSequence()
|
||||
} catch (e: Exception) {
|
||||
toast("获取host失败:" + e.message)
|
||||
emptySequence()
|
||||
}
|
||||
val localAddresses = networkInterfaces.flatMap {
|
||||
it.inetAddresses.asSequence().filter { inetAddress ->
|
||||
inetAddress.isSiteLocalAddress && !(inetAddress.hostAddress?.contains(":")
|
||||
?: false) && inetAddress.hostAddress != "127.0.0.1"
|
||||
}.map { inetAddress -> inetAddress.hostAddress }
|
||||
}
|
||||
return localAddresses.toList()
|
||||
}
|
11
app/src/main/kotlin/li/songe/gkd/util/PackageExt.kt
Normal file
11
app/src/main/kotlin/li/songe/gkd/util/PackageExt.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
|
||||
fun PackageManager.getDefaultLauncherAppId(): String? {
|
||||
val intent = Intent(Intent.ACTION_MAIN)
|
||||
intent.addCategory(Intent.CATEGORY_HOME)
|
||||
val defaultLauncher = this.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
return defaultLauncher?.activityInfo?.packageName
|
||||
}
|
|
@ -16,7 +16,6 @@ import android.media.projection.MediaProjectionManager
|
|||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.blankj.utilcode.util.ScreenUtils
|
||||
import li.songe.gkd.util.Ext.isEmptyBitmap
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.resumeWithException
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
@ -46,7 +45,7 @@ class ScreenshotUtil(private val context: Context, private val screenshotIntent:
|
|||
}
|
||||
|
||||
// TODO android13 上一半概率获取到全透明图片, android12 暂无此问题
|
||||
suspend fun execute() = suspendCoroutine<Bitmap> { block ->
|
||||
suspend fun execute() = suspendCoroutine { block ->
|
||||
imageReader = ImageReader.newInstance(
|
||||
width, height,
|
||||
PixelFormat.RGBA_8888, 2
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package li.songe.gkd.util
|
||||
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.ImmutableMap
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.persistentMapOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableMap
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
|
@ -19,11 +26,12 @@ import li.songe.gkd.data.SubsConfig
|
|||
import li.songe.gkd.db.DbSet
|
||||
|
||||
val subsItemsFlow by lazy {
|
||||
DbSet.subsItemDao.query().stateIn(appScope, SharingStarted.Eagerly, emptyList())
|
||||
DbSet.subsItemDao.query().map { s -> s.toImmutableList() }
|
||||
.stateIn(appScope, SharingStarted.Eagerly, persistentListOf())
|
||||
}
|
||||
|
||||
val subsIdToRawFlow by lazy {
|
||||
MutableStateFlow<Map<Long, RawSubscription>>(emptyMap())
|
||||
MutableStateFlow<ImmutableMap<Long, RawSubscription>>(persistentMapOf())
|
||||
}
|
||||
|
||||
private val updateSubsFileMutex by lazy { Mutex() }
|
||||
|
@ -36,7 +44,7 @@ fun updateSubscription(subscription: RawSubscription) {
|
|||
} else {
|
||||
newMap[subscription.id] = subscription
|
||||
}
|
||||
subsIdToRawFlow.value = newMap
|
||||
subsIdToRawFlow.value = newMap.toImmutableMap()
|
||||
withContext(Dispatchers.IO) {
|
||||
subsFolder.resolve("${subscription.id}.json")
|
||||
.writeText(json.encodeToString(subscription))
|
||||
|
@ -48,7 +56,7 @@ fun updateSubscription(subscription: RawSubscription) {
|
|||
fun deleteSubscription(subsId: Long) {
|
||||
val newMap = subsIdToRawFlow.value.toMutableMap()
|
||||
newMap.remove(subsId)
|
||||
subsIdToRawFlow.value = newMap
|
||||
subsIdToRawFlow.value = newMap.toImmutableMap()
|
||||
}
|
||||
|
||||
fun getGroupRawEnable(
|
||||
|
@ -76,11 +84,11 @@ fun getGroupRawEnable(
|
|||
}
|
||||
|
||||
data class RuleSummary(
|
||||
val globalRules: List<GlobalRule> = emptyList(),
|
||||
val globalGroups: List<RawSubscription.RawGlobalGroup> = emptyList(),
|
||||
val appIdToRules: Map<String, List<AppRule>> = emptyMap(),
|
||||
val appIdToGroups: Map<String, List<RawSubscription.RawAppGroup>> = emptyMap(),
|
||||
val appIdToAllGroups: Map<String, List<Pair<RawSubscription.RawAppGroup, Boolean>>> = emptyMap(),
|
||||
val globalRules: ImmutableList<GlobalRule> = persistentListOf(),
|
||||
val globalGroups: ImmutableList<RawSubscription.RawGlobalGroup> = persistentListOf(),
|
||||
val appIdToRules: ImmutableMap<String, ImmutableList<AppRule>> = persistentMapOf(),
|
||||
val appIdToGroups: ImmutableMap<String, ImmutableList<RawSubscription.RawAppGroup>> = persistentMapOf(),
|
||||
val appIdToAllGroups: ImmutableMap<String, ImmutableList<Pair<RawSubscription.RawAppGroup, Boolean>>> = persistentMapOf(),
|
||||
) {
|
||||
private val appSize = appIdToRules.keys.size
|
||||
private val appGroupSize = appIdToGroups.values.sumOf { s -> s.size }
|
||||
|
@ -145,7 +153,8 @@ val ruleSummaryFlow by lazy {
|
|||
group = groupRaw,
|
||||
rawSubs = rawSubs,
|
||||
subsItem = subsItem,
|
||||
exclude = subGlobalSubsConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude
|
||||
exclude = subGlobalSubsConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude,
|
||||
appInfoCache = appInfoCache,
|
||||
)
|
||||
}
|
||||
subGlobalGroupToRules[groupRaw] = subRules
|
||||
|
@ -190,9 +199,10 @@ val ruleSummaryFlow by lazy {
|
|||
app = appRaw,
|
||||
rawSubs = rawSubs,
|
||||
subsItem = subsItem,
|
||||
exclude = appGroupConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude
|
||||
exclude = appGroupConfigs.find { c -> c.groupKey == groupRaw.key }?.exclude,
|
||||
appInfo = appInfoCache[appRaw.id]
|
||||
)
|
||||
}
|
||||
}.filter { r -> r.enable }
|
||||
subAppGroupToRules[groupRaw] = subRules
|
||||
if (subRules.isNotEmpty()) {
|
||||
val rules = appRules[appRaw.id] ?: mutableListOf()
|
||||
|
@ -212,11 +222,12 @@ val ruleSummaryFlow by lazy {
|
|||
}
|
||||
}
|
||||
RuleSummary(
|
||||
globalRules = globalRules,
|
||||
globalGroups = globalGroups,
|
||||
appIdToRules = appRules,
|
||||
appIdToGroups = appGroups,
|
||||
appIdToAllGroups = appAllGroups
|
||||
globalRules = globalRules.toImmutableList(),
|
||||
globalGroups = globalGroups.toImmutableList(),
|
||||
appIdToRules = appRules.mapValues { e -> e.value.toImmutableList() }.toImmutableMap(),
|
||||
appIdToGroups = appGroups.mapValues { e -> e.value.toImmutableList() }.toImmutableMap(),
|
||||
appIdToAllGroups = appAllGroups.mapValues { e -> e.value.toImmutableList() }
|
||||
.toImmutableMap()
|
||||
)
|
||||
}.stateIn(appScope, SharingStarted.Eagerly, RuleSummary())
|
||||
}
|
||||
|
@ -248,7 +259,7 @@ fun initSubsState() {
|
|||
author = "gkd",
|
||||
)
|
||||
}
|
||||
subsIdToRawFlow.value = newMap
|
||||
subsIdToRawFlow.value = newMap.toImmutableMap()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,7 +17,7 @@ dependencyResolutionManagement {
|
|||
|
||||
// https://youtrack.jetbrains.com/issue/KT-55620
|
||||
// https://stackoverflow.com/questions/69163511
|
||||
repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
|
||||
// repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
|
||||
|
||||
repositories {
|
||||
mavenLocal()
|
||||
|
@ -65,9 +65,9 @@ dependencyResolutionManagement {
|
|||
plugin("kotlin.android", "org.jetbrains.kotlin.android").version(kotlinVersion)
|
||||
|
||||
// compose 编译器的版本, 需要注意它与 compose 的版本没有关联
|
||||
// https://mvnrepository.com/artifact/androidx.compose.compiler/compiler
|
||||
version("compose.compilerVersion", "1.5.8")
|
||||
val composeVersion = "1.6.0"
|
||||
// https://developer.android.com/jetpack/androidx/releases/compose-compiler
|
||||
version("compose.compilerVersion", "1.5.9")
|
||||
val composeVersion = "1.6.1"
|
||||
library("compose.ui", "androidx.compose.ui:ui:$composeVersion")
|
||||
library("compose.preview", "androidx.compose.ui:ui-tooling-preview:$composeVersion")
|
||||
library("compose.tooling", "androidx.compose.ui:ui-tooling:$composeVersion")
|
||||
|
@ -78,13 +78,11 @@ dependencyResolutionManagement {
|
|||
"compose.icons",
|
||||
"androidx.compose.material:material-icons-extended:$composeVersion"
|
||||
)
|
||||
library("compose.material3", "androidx.compose.material3:material3:1.2.0-rc01")
|
||||
library("compose.material3", "androidx.compose.material3:material3:1.2.0")
|
||||
library("compose.activity", "androidx.activity:activity-compose:1.8.2")
|
||||
|
||||
// https://github.com/Tencent/MMKV/blob/master/README_CN.md
|
||||
library("tencent.mmkv", "com.tencent:mmkv:1.3.3")
|
||||
// https://bugly.qq.com/docs/user-guide/instruction-manual-android/
|
||||
library("tencent.bugly", "com.tencent.bugly:crashreport:4.1.9.3")
|
||||
|
||||
// https://github.com/RikkaApps/HiddenApiRefinePlugin
|
||||
val rikkaVersion = "4.4.0"
|
||||
|
@ -140,7 +138,7 @@ dependencyResolutionManagement {
|
|||
|
||||
library("junit", "junit:junit:4.13.2")
|
||||
|
||||
val ktorVersion = "2.3.7"
|
||||
val ktorVersion = "2.3.8"
|
||||
// 请注意,当 client 和 server 版本不一致时, 会报错 socket hang up
|
||||
library("ktor.server.core", "io.ktor:ktor-server-core:$ktorVersion")
|
||||
library("ktor.server.cio", "io.ktor:ktor-server-cio:$ktorVersion")
|
||||
|
@ -167,6 +165,7 @@ dependencyResolutionManagement {
|
|||
)
|
||||
|
||||
// https://github.com/Kotlin/kotlinx.collections.immutable
|
||||
// 仍然存在一些限制 kotlinx.serialization https://github.com/Kotlin/kotlinx.collections.immutable/issues/63
|
||||
library(
|
||||
"kotlinx.collections.immutable",
|
||||
"org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.7"
|
||||
|
|
Loading…
Reference in New Issue
Block a user