From 128a6cdf606a0632f22e63ee0d8edb4c19a74377 Mon Sep 17 00:00:00 2001 From: lisonge Date: Wed, 18 Sep 2024 22:19:04 +0800 Subject: [PATCH] feat: uri open page --- .../kotlin/li/songe/gkd/debug/HttpService.kt | 265 +++++++++--------- .../main/kotlin/li/songe/gkd/notif/Notif.kt | 5 + .../kotlin/li/songe/gkd/notif/NotifManager.kt | 25 +- .../kotlin/li/songe/gkd/ui/home/HomePage.kt | 25 +- 4 files changed, 171 insertions(+), 149 deletions(-) diff --git a/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt b/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt index 93d20b577..45ced7ec4 100644 --- a/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt +++ b/app/src/main/kotlin/li/songe/gkd/debug/HttpService.kt @@ -1,5 +1,6 @@ package li.songe.gkd.debug +import android.app.Service import android.content.Context import android.content.Intent import com.blankj.utilcode.util.LogUtils @@ -29,7 +30,6 @@ import kotlinx.coroutines.flow.first import kotlinx.serialization.Serializable import li.songe.gkd.app import li.songe.gkd.appScope -import li.songe.gkd.composition.CompositionService import li.songe.gkd.data.AppInfo import li.songe.gkd.data.DeviceInfo import li.songe.gkd.data.GkdAction @@ -57,135 +57,43 @@ import li.songe.gkd.util.updateSubscription import java.io.File -class HttpService : CompositionService({ - val context = this - val scope = CoroutineScope(Dispatchers.IO) +class HttpService : Service() { + private val scope = CoroutineScope(Dispatchers.IO) - val httpSubsItem = SubsItem( - id = LOCAL_HTTP_SUBS_ID, - order = -1, - enableUpdate = false, - ) - - fun createServer(port: Int): CIOApplicationEngine { - return embeddedServer(CIO, port) { - install(KtorCorsPlugin) - install(KtorErrorPlugin) - install(ContentNegotiation) { json(keepNullJson) } - - routing { - get("/") { call.respondText(ContentType.Text.Html) { "" } } - route("/api") { - // Deprecated - get("/device") { call.respond(DeviceInfo.instance) } - - post("/getServerInfo") { call.respond(ServerInfo()) } - - // Deprecated - get("/snapshot") { - val id = call.request.queryParameters["id"]?.toLongOrNull() - ?: throw RpcError("miss id") - val fp = File(SnapshotExt.getSnapshotPath(id)) - if (!fp.exists()) { - throw RpcError("对应快照不存在") - } - call.respondFile(fp) - } - post("/getSnapshot") { - val data = call.receive() - val fp = File(SnapshotExt.getSnapshotPath(data.id)) - if (!fp.exists()) { - throw RpcError("对应快照不存在") - } - call.respond(fp) - } - - // Deprecated - get("/screenshot") { - val id = call.request.queryParameters["id"]?.toLongOrNull() - ?: throw RpcError("miss id") - val fp = File(SnapshotExt.getScreenshotPath(id)) - if (!fp.exists()) { - throw RpcError("对应截图不存在") - } - call.respondFile(fp) - } - post("/getScreenshot") { - val data = call.receive() - val fp = File(SnapshotExt.getScreenshotPath(data.id)) - if (!fp.exists()) { - throw RpcError("对应截图不存在") - } - call.respondFile(fp) - } - - // Deprecated - get("/captureSnapshot") { - call.respond(captureSnapshot()) - } - post("/captureSnapshot") { - call.respond(captureSnapshot()) - } - - // Deprecated - get("/snapshots") { - call.respond(DbSet.snapshotDao.query().first()) - } - post("/getSnapshots") { - call.respond(DbSet.snapshotDao.query().first()) - } - - post("/updateSubscription") { - val subscription = - RawSubscription.parse(call.receiveText(), json5 = false) - .copy( - id = LOCAL_HTTP_SUBS_ID, - name = "内存订阅", - version = 0, - author = "@gkd-kit/inspect" - ) - updateSubscription(subscription) - DbSet.subsItemDao.insert((subsItemsFlow.value.find { s -> s.id == httpSubsItem.id } - ?: httpSubsItem).copy(mtime = System.currentTimeMillis())) - call.respond(RpcOk()) - } - post("/execSelector") { - if (!GkdAbService.isRunning.value) { - throw RpcError("无障碍没有运行") - } - val gkdAction = call.receive() - call.respond(GkdAbService.execAction(gkdAction)) - } + private var server: CIOApplicationEngine? = null + override fun onCreate() { + super.onCreate() + isRunning.value = true + localNetworkIpsFlow.value = getIpAddressInLocalNetwork() + val httpServerPortFlow = storeFlow.map(scope) { s -> s.httpServerPort } + scope.launchTry(Dispatchers.IO) { + httpServerPortFlow.collect { port -> + server?.stop() + server = try { + createServer(port).apply { start() } + } catch (e: Exception) { + LogUtils.d("HTTP服务启动失败", e) + null } + if (server == null) { + toast("HTTP服务启动失败,您可以尝试切换端口后重新启动") + stopSelf() + return@collect + } + createNotif( + this@HttpService, + httpChannel.id, + httpNotif.copy(text = "HTTP服务-端口$port") + ) } } } - var server: CIOApplicationEngine? = null - scope.launchTry(Dispatchers.IO) { - storeFlow.map(scope) { s -> s.httpServerPort }.collect { port -> - server?.stop() - server = try { - createServer(port).apply { start() } - } catch (e: Exception) { - LogUtils.d("HTTP服务启动失败", e) - null - } - if (server == null) { - toast("HTTP服务启动失败,您可以尝试切换端口后重新启动") - stopSelf() - return@collect - } - createNotif( - context, httpChannel.id, httpNotif.copy(text = "HTTP服务正在运行-端口$port") - ) - LogUtils.d(*getIpAddressInLocalNetwork().map { host -> "http://${host}:${port}" } - .toList().toTypedArray()) - } - } - + override fun onDestroy() { + super.onDestroy() + isRunning.value = false + localNetworkIpsFlow.value = emptyList() - onDestroy { scope.launchTry(Dispatchers.IO) { server?.stop() if (storeFlow.value.autoClearMemorySubs) { @@ -196,13 +104,6 @@ class HttpService : CompositionService({ } } - isRunning.value = true - localNetworkIpsFlow.value = getIpAddressInLocalNetwork() - onDestroy { - isRunning.value = false - localNetworkIpsFlow.value = emptyList() - } -}) { companion object { val isRunning = MutableStateFlow(false) val localNetworkIpsFlow = MutableStateFlow(emptyList()) @@ -213,8 +114,9 @@ class HttpService : CompositionService({ fun start(context: Context = app) { context.startForegroundService(Intent(context, HttpService::class.java)) } - } + + override fun onBind(intent: Intent?) = null } @Serializable @@ -242,4 +144,105 @@ fun clearHttpSubs() { deleteSubscription(LOCAL_HTTP_SUBS_ID) } } +} + +private val httpSubsItem by lazy { + SubsItem( + id = LOCAL_HTTP_SUBS_ID, + order = -1, + enableUpdate = false, + ) +} + +private fun createServer(port: Int): CIOApplicationEngine { + return embeddedServer(CIO, port) { + install(KtorCorsPlugin) + install(KtorErrorPlugin) + install(ContentNegotiation) { json(keepNullJson) } + routing { + get("/") { call.respondText(ContentType.Text.Html) { "" } } + route("/api") { + // Deprecated + get("/device") { call.respond(DeviceInfo.instance) } + + post("/getServerInfo") { call.respond(ServerInfo()) } + + // Deprecated + get("/snapshot") { + val id = call.request.queryParameters["id"]?.toLongOrNull() + ?: throw RpcError("miss id") + val fp = File(SnapshotExt.getSnapshotPath(id)) + if (!fp.exists()) { + throw RpcError("对应快照不存在") + } + call.respondFile(fp) + } + post("/getSnapshot") { + val data = call.receive() + val fp = File(SnapshotExt.getSnapshotPath(data.id)) + if (!fp.exists()) { + throw RpcError("对应快照不存在") + } + call.respond(fp) + } + + // Deprecated + get("/screenshot") { + val id = call.request.queryParameters["id"]?.toLongOrNull() + ?: throw RpcError("miss id") + val fp = File(SnapshotExt.getScreenshotPath(id)) + if (!fp.exists()) { + throw RpcError("对应截图不存在") + } + call.respondFile(fp) + } + post("/getScreenshot") { + val data = call.receive() + val fp = File(SnapshotExt.getScreenshotPath(data.id)) + if (!fp.exists()) { + throw RpcError("对应截图不存在") + } + call.respondFile(fp) + } + + // Deprecated + get("/captureSnapshot") { + call.respond(captureSnapshot()) + } + post("/captureSnapshot") { + call.respond(captureSnapshot()) + } + + // Deprecated + get("/snapshots") { + call.respond(DbSet.snapshotDao.query().first()) + } + post("/getSnapshots") { + call.respond(DbSet.snapshotDao.query().first()) + } + + post("/updateSubscription") { + val subscription = + RawSubscription.parse(call.receiveText(), json5 = false) + .copy( + id = LOCAL_HTTP_SUBS_ID, + name = "内存订阅", + version = 0, + author = "@gkd-kit/inspect" + ) + updateSubscription(subscription) + DbSet.subsItemDao.insert((subsItemsFlow.value.find { s -> s.id == httpSubsItem.id } + ?: httpSubsItem).copy(mtime = System.currentTimeMillis())) + call.respond(RpcOk()) + } + post("/execSelector") { + if (!GkdAbService.isRunning.value) { + throw RpcError("无障碍没有运行") + } + val gkdAction = call.receive() + call.respond(GkdAbService.execAction(gkdAction)) + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/notif/Notif.kt b/app/src/main/kotlin/li/songe/gkd/notif/Notif.kt index 50e0ab99a..51a48d5b1 100644 --- a/app/src/main/kotlin/li/songe/gkd/notif/Notif.kt +++ b/app/src/main/kotlin/li/songe/gkd/notif/Notif.kt @@ -3,6 +3,7 @@ package li.songe.gkd.notif import li.songe.gkd.app import li.songe.gkd.util.SafeR + data class Notif( val id: Int, val smallIcon: Int = SafeR.ic_status, @@ -10,6 +11,7 @@ data class Notif( val text: String, val ongoing: Boolean, val autoCancel: Boolean, + val uri: String? = null, ) val abNotif by lazy { @@ -27,6 +29,7 @@ val screenshotNotif by lazy { text = "截屏服务正在运行", ongoing = true, autoCancel = false, + uri = "gkd://page/1", ) } @@ -36,6 +39,7 @@ val floatingNotif by lazy { text = "悬浮窗按钮正在显示", ongoing = true, autoCancel = false, + uri = "gkd://page/1", ) } @@ -45,5 +49,6 @@ val httpNotif by lazy { text = "HTTP服务正在运行", ongoing = true, autoCancel = false, + uri = "gkd://page/1", ) } \ No newline at end of file diff --git a/app/src/main/kotlin/li/songe/gkd/notif/NotifManager.kt b/app/src/main/kotlin/li/songe/gkd/notif/NotifManager.kt index bf29dfd7d..fc59e58a6 100644 --- a/app/src/main/kotlin/li/songe/gkd/notif/NotifManager.kt +++ b/app/src/main/kotlin/li/songe/gkd/notif/NotifManager.kt @@ -7,6 +7,7 @@ import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo +import android.net.Uri import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat @@ -21,20 +22,24 @@ fun createChannel(context: Context, notifChannel: NotifChannel) { } fun createNotif(context: Service, channelId: String, notif: Notif) { - val intent = Intent(context, MainActivity::class.java).apply { - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - } val pendingIntent = PendingIntent.getActivity( - context, notif.id, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + context, + 0, + Intent(context, MainActivity::class.java).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + notif.uri?.let { data = Uri.parse(it) } + }, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) - - val builder = NotificationCompat.Builder(context, channelId) + val notification = NotificationCompat.Builder(context, channelId) .setSmallIcon(notif.smallIcon) - .setContentTitle(notif.title).setContentText(notif.text).setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_DEFAULT).setOngoing(notif.ongoing) + .setContentTitle(notif.title) + .setContentText(notif.text) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setOngoing(notif.ongoing) .setAutoCancel(notif.autoCancel) - - val notification = builder.build() + .build() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { context.startForeground( notif.id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST diff --git a/app/src/main/kotlin/li/songe/gkd/ui/home/HomePage.kt b/app/src/main/kotlin/li/songe/gkd/ui/home/HomePage.kt index ca35312c4..a399c71b8 100644 --- a/app/src/main/kotlin/li/songe/gkd/ui/home/HomePage.kt +++ b/app/src/main/kotlin/li/songe/gkd/ui/home/HomePage.kt @@ -17,11 +17,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.blankj.utilcode.util.LogUtils import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph +import com.ramcosta.composedestinations.generated.destinations.AdvancedPageDestination +import com.ramcosta.composedestinations.utils.toDestinationsNavigator import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import li.songe.gkd.MainActivity import li.songe.gkd.OpenFileActivity -import li.songe.gkd.OpenSchemeActivity import li.songe.gkd.data.importData +import li.songe.gkd.util.LocalNavController import li.songe.gkd.util.ProfileTransitions import li.songe.gkd.util.launchTry import li.songe.gkd.util.toast @@ -35,6 +38,7 @@ data class BottomNavItem( @Composable fun HomePage() { val context = LocalContext.current as MainActivity + val navController = LocalNavController.current val vm = viewModel() val tab by vm.tabFlow.collectAsState() @@ -47,12 +51,11 @@ fun HomePage() { val currentPage = pages.find { p -> p.navItem.label == tab.label } ?: controlPage - val intent = context.intent - LaunchedEffect(key1 = intent, block = { - intent ?: return@LaunchedEffect + LaunchedEffect(key1 = null, block = { + val intent = context.intent ?: return@LaunchedEffect context.intent = null LogUtils.d(intent) - val uri = intent.data ?: return@LaunchedEffect + val uri = intent.data?.normalizeScheme() ?: return@LaunchedEffect val source = intent.getStringExtra("source") if (source == OpenFileActivity::class.qualifiedName) { vm.viewModelScope.launchTry(Dispatchers.IO) { @@ -60,8 +63,13 @@ fun HomePage() { vm.tabFlow.value = subsPage.navItem importData(uri) } - } else if (source == OpenSchemeActivity::class.qualifiedName) { - LogUtils.d(uri) + } else if (uri.scheme == "gkd" && uri.host == "page") { + delay(300) + when (uri.path) { + "/1" -> { + navController.toDestinationsNavigator().navigate(AdvancedPageDestination) + } + } } }) @@ -72,7 +80,8 @@ fun HomePage() { bottomBar = { NavigationBar { pages.forEach { page -> - NavigationBarItem(selected = tab.label == page.navItem.label, + NavigationBarItem( + selected = tab.label == page.navItem.label, modifier = Modifier, onClick = { vm.tabFlow.value = page.navItem