mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 11:42:22 +08:00
This commit is contained in:
parent
017422cc03
commit
001bb5683d
|
@ -97,7 +97,9 @@ class MainActivity : CompositionActivity({
|
||||||
initFolder()
|
initFolder()
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePermissionState()
|
appScope.launch(Dispatchers.IO) {
|
||||||
|
updatePermissionState()
|
||||||
|
}
|
||||||
|
|
||||||
// 进程崩溃后重新打开应用, 由于存在缓存导致服务状态可能不正确, 在此保证每次界面切换都能重新刷新状态
|
// 进程崩溃后重新打开应用, 由于存在缓存导致服务状态可能不正确, 在此保证每次界面切换都能重新刷新状态
|
||||||
appScope.launch(Dispatchers.IO) {
|
appScope.launch(Dispatchers.IO) {
|
||||||
|
|
|
@ -12,20 +12,22 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.updateAndGet
|
import kotlinx.coroutines.flow.updateAndGet
|
||||||
import li.songe.gkd.app
|
import li.songe.gkd.app
|
||||||
import li.songe.gkd.appScope
|
import li.songe.gkd.appScope
|
||||||
|
import li.songe.gkd.shizuku.newActivityTaskManager
|
||||||
|
import li.songe.gkd.shizuku.safeGetTasks
|
||||||
import li.songe.gkd.shizuku.shizukuIsSafeOK
|
import li.songe.gkd.shizuku.shizukuIsSafeOK
|
||||||
import li.songe.gkd.util.initOrResetAppInfoCache
|
import li.songe.gkd.util.initOrResetAppInfoCache
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
|
|
||||||
class PermissionState(
|
class PermissionState(
|
||||||
val check: () -> Boolean,
|
val check: suspend () -> Boolean,
|
||||||
val request: (suspend (context: Activity) -> PermissionResult)? = null,
|
val request: (suspend (context: Activity) -> PermissionResult)? = null,
|
||||||
/**
|
/**
|
||||||
* show it when user doNotAskAgain
|
* show it when user doNotAskAgain
|
||||||
*/
|
*/
|
||||||
val reason: AuthReason? = null,
|
val reason: AuthReason? = null,
|
||||||
) {
|
) {
|
||||||
val stateFlow = MutableStateFlow(check())
|
val stateFlow = MutableStateFlow(false)
|
||||||
fun updateAndGet(): Boolean {
|
suspend fun updateAndGet(): Boolean {
|
||||||
return stateFlow.updateAndGet { check() }
|
return stateFlow.updateAndGet { check() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,21 +122,32 @@ val canSaveToAlbumState by lazy {
|
||||||
|
|
||||||
val shizukuOkState by lazy {
|
val shizukuOkState by lazy {
|
||||||
PermissionState(
|
PermissionState(
|
||||||
check = { shizukuIsSafeOK() },
|
check = {
|
||||||
|
shizukuIsSafeOK() && (try {
|
||||||
|
// 打开 shizuku 点击右上角停止, 此时 shizukuIsSafeOK() == true, 因此需要二次检查状态
|
||||||
|
newActivityTaskManager()?.safeGetTasks(log = false)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
false
|
||||||
|
})
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePermissionState() {
|
private val checkLoading = MutableStateFlow(false)
|
||||||
|
suspend fun updatePermissionState() {
|
||||||
|
if (checkLoading.value) return
|
||||||
|
checkLoading.value = true
|
||||||
arrayOf(
|
arrayOf(
|
||||||
notificationState,
|
notificationState,
|
||||||
canDrawOverlaysState,
|
canDrawOverlaysState,
|
||||||
canSaveToAlbumState,
|
canSaveToAlbumState,
|
||||||
shizukuOkState
|
shizukuOkState
|
||||||
).forEach { it.updateAndGet() }
|
).forEach { it.updateAndGet() }
|
||||||
|
|
||||||
if (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet()) {
|
if (canQueryPkgState.stateFlow.value != canQueryPkgState.updateAndGet()) {
|
||||||
appScope.launchTry {
|
appScope.launchTry {
|
||||||
initOrResetAppInfoCache()
|
initOrResetAppInfoCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkLoading.value = false
|
||||||
}
|
}
|
|
@ -66,7 +66,7 @@ fun newActivityTaskManager(): IActivityTaskManager? {
|
||||||
*/
|
*/
|
||||||
private var getTasksFcType: Int? = null
|
private var getTasksFcType: Int? = null
|
||||||
|
|
||||||
fun IActivityTaskManager.safeGetTasks(): List<ActivityManager.RunningTaskInfo>? {
|
fun IActivityTaskManager.safeGetTasks(log: Boolean = true): List<ActivityManager.RunningTaskInfo>? {
|
||||||
if (getTasksFcType == null) {
|
if (getTasksFcType == null) {
|
||||||
val fcs = this::class.declaredMemberFunctions
|
val fcs = this::class.declaredMemberFunctions
|
||||||
val parameters = fcs.find { d -> d.name == "getTasks" }?.parameters
|
val parameters = fcs.find { d -> d.name == "getTasks" }?.parameters
|
||||||
|
@ -95,7 +95,9 @@ fun IActivityTaskManager.safeGetTasks(): List<ActivityManager.RunningTaskInfo>?
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtils.d(e)
|
if (log) {
|
||||||
|
LogUtils.d(e)
|
||||||
|
}
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,6 +57,7 @@ import com.ramcosta.composedestinations.annotation.RootNavGraph
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import li.songe.gkd.MainActivity
|
import li.songe.gkd.MainActivity
|
||||||
|
import li.songe.gkd.app
|
||||||
import li.songe.gkd.appScope
|
import li.songe.gkd.appScope
|
||||||
import li.songe.gkd.debug.FloatingService
|
import li.songe.gkd.debug.FloatingService
|
||||||
import li.songe.gkd.debug.HttpService
|
import li.songe.gkd.debug.HttpService
|
||||||
|
@ -70,18 +71,23 @@ import li.songe.gkd.shizuku.newActivityTaskManager
|
||||||
import li.songe.gkd.shizuku.newUserService
|
import li.songe.gkd.shizuku.newUserService
|
||||||
import li.songe.gkd.shizuku.safeGetTasks
|
import li.songe.gkd.shizuku.safeGetTasks
|
||||||
import li.songe.gkd.ui.component.AuthCard
|
import li.songe.gkd.ui.component.AuthCard
|
||||||
|
import li.songe.gkd.ui.component.DialogApiInjection
|
||||||
import li.songe.gkd.ui.component.SettingItem
|
import li.songe.gkd.ui.component.SettingItem
|
||||||
import li.songe.gkd.ui.component.TextSwitch
|
import li.songe.gkd.ui.component.TextSwitch
|
||||||
|
import li.songe.gkd.ui.component.build
|
||||||
|
import li.songe.gkd.ui.component.useDialog
|
||||||
import li.songe.gkd.ui.destinations.SnapshotPageDestination
|
import li.songe.gkd.ui.destinations.SnapshotPageDestination
|
||||||
import li.songe.gkd.ui.style.itemPadding
|
import li.songe.gkd.ui.style.itemPadding
|
||||||
import li.songe.gkd.ui.style.titleItemPadding
|
import li.songe.gkd.ui.style.titleItemPadding
|
||||||
import li.songe.gkd.util.LocalLauncher
|
import li.songe.gkd.util.LocalLauncher
|
||||||
import li.songe.gkd.util.LocalNavController
|
import li.songe.gkd.util.LocalNavController
|
||||||
import li.songe.gkd.util.ProfileTransitions
|
import li.songe.gkd.util.ProfileTransitions
|
||||||
|
import li.songe.gkd.util.appInfoCacheFlow
|
||||||
import li.songe.gkd.util.json
|
import li.songe.gkd.util.json
|
||||||
import li.songe.gkd.util.launchAsFn
|
import li.songe.gkd.util.launchAsFn
|
||||||
import li.songe.gkd.util.launchTry
|
import li.songe.gkd.util.launchTry
|
||||||
import li.songe.gkd.util.navigate
|
import li.songe.gkd.util.navigate
|
||||||
|
import li.songe.gkd.util.openApp
|
||||||
import li.songe.gkd.util.openUri
|
import li.songe.gkd.util.openUri
|
||||||
import li.songe.gkd.util.storeFlow
|
import li.songe.gkd.util.storeFlow
|
||||||
import li.songe.gkd.util.toast
|
import li.songe.gkd.util.toast
|
||||||
|
@ -93,6 +99,7 @@ import rikka.shizuku.Shizuku
|
||||||
fun AdvancedPage() {
|
fun AdvancedPage() {
|
||||||
val context = LocalContext.current as MainActivity
|
val context = LocalContext.current as MainActivity
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val dialog = useDialog()
|
||||||
val launcher = LocalLauncher.current
|
val launcher = LocalLauncher.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val store by storeFlow.collectAsState()
|
val store by storeFlow.collectAsState()
|
||||||
|
@ -117,7 +124,8 @@ fun AdvancedPage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}, title = { Text(text = "高级模式") }, actions = {})
|
}, title = { Text(text = "高级模式") }, actions = {})
|
||||||
}) { contentPadding ->
|
}
|
||||||
|
) { contentPadding ->
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
@ -133,13 +141,13 @@ fun AdvancedPage() {
|
||||||
val shizukuOk by shizukuOkState.stateFlow.collectAsState()
|
val shizukuOk by shizukuOkState.stateFlow.collectAsState()
|
||||||
if (!shizukuOk) {
|
if (!shizukuOk) {
|
||||||
AuthCard(title = "Shizuku授权",
|
AuthCard(title = "Shizuku授权",
|
||||||
desc = "高级模式:准确识别界面ID,强制模拟点击",
|
desc = "高级模式:准确区别界面ID,强制模拟点击",
|
||||||
onAuthClick = {
|
onAuthClick = {
|
||||||
try {
|
try {
|
||||||
Shizuku.requestPermission(Activity.RESULT_OK)
|
Shizuku.requestPermission(Activity.RESULT_OK)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
LogUtils.d("Shizuku授权错误", e)
|
LogUtils.d("Shizuku授权错误", e.message)
|
||||||
toast("Shizuku可能没有运行")
|
showShizukuErrorDialog(dialog)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
ShizukuFragment(false)
|
ShizukuFragment(false)
|
||||||
|
@ -473,4 +481,39 @@ private fun ShizukuFragment(enabled: Boolean = true) {
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showShizukuErrorDialog(dialog: DialogApiInjection) {
|
||||||
|
val appId = "moe.shizuku.privileged.api"
|
||||||
|
val installed = appInfoCacheFlow.value.contains(appId)
|
||||||
|
dialog.build(
|
||||||
|
title = "授权错误",
|
||||||
|
text = if (installed) {
|
||||||
|
"Shizuku 授权失败, 请检查是否运行"
|
||||||
|
} else {
|
||||||
|
"Shizuku 未安装, 请先下载后安装"
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
if (installed) {
|
||||||
|
TextButton(onClick = {
|
||||||
|
dialog.dismiss()
|
||||||
|
app.openApp(appId)
|
||||||
|
}) {
|
||||||
|
Text(text = "打开 Shizuku")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TextButton(onClick = {
|
||||||
|
dialog.dismiss()
|
||||||
|
app.openUri("https://shizuku.rikka.app/")
|
||||||
|
}) {
|
||||||
|
Text(text = "去下载")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = dialog.dismiss) {
|
||||||
|
Text(text = "我知道了")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
72
app/src/main/kotlin/li/songe/gkd/ui/component/Dialog.kt
Normal file
72
app/src/main/kotlin/li/songe/gkd/ui/component/Dialog.kt
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package li.songe.gkd.ui.component
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
|
||||||
|
data class DialogOptions internal constructor(
|
||||||
|
val onDismissRequest: (() -> Unit)? = null,
|
||||||
|
val confirmButton: @Composable () -> Unit,
|
||||||
|
val dismissButton: @Composable (() -> Unit)? = null,
|
||||||
|
val icon: @Composable (() -> Unit)? = null,
|
||||||
|
val title: @Composable (() -> Unit)? = null,
|
||||||
|
val text: @Composable (() -> Unit)? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DialogApiInjection(
|
||||||
|
val create: (options: DialogOptions) -> Unit,
|
||||||
|
val dismiss: () -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun DialogApiInjection.build(
|
||||||
|
title: String,
|
||||||
|
text: String,
|
||||||
|
confirmButton: @Composable () -> Unit,
|
||||||
|
dismissButton: @Composable (() -> Unit)? = null,
|
||||||
|
icon: ImageVector? = null,
|
||||||
|
) {
|
||||||
|
return create(
|
||||||
|
DialogOptions(
|
||||||
|
confirmButton = confirmButton,
|
||||||
|
dismissButton = dismissButton,
|
||||||
|
icon = icon?.let {
|
||||||
|
{ Image(imageVector = icon, contentDescription = null) }
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = title)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = text)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun useDialog(): DialogApiInjection {
|
||||||
|
var options by remember { mutableStateOf<DialogOptions?>(null) }
|
||||||
|
val apiInjection = remember {
|
||||||
|
DialogApiInjection(
|
||||||
|
create = { options = it },
|
||||||
|
dismiss = { options = null }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
options?.let {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = it.onDismissRequest ?: apiInjection.dismiss,
|
||||||
|
confirmButton = it.confirmButton,
|
||||||
|
dismissButton = it.dismissButton,
|
||||||
|
icon = it.icon,
|
||||||
|
title = it.title,
|
||||||
|
text = it.text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return apiInjection
|
||||||
|
}
|
|
@ -48,4 +48,14 @@ fun Context.openUri(uri: String) {
|
||||||
val intent = Intent(Intent.ACTION_VIEW, u)
|
val intent = Intent(Intent.ACTION_VIEW, u)
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
tryStartActivity(intent)
|
tryStartActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.openApp(appId: String) {
|
||||||
|
val intent = packageManager.getLaunchIntentForPackage(appId)
|
||||||
|
if (intent != null) {
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
tryStartActivity(intent)
|
||||||
|
} else {
|
||||||
|
toast("请检查此应用是否安装")
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user