feat: uri open page
Some checks are pending
Build-Apk / build (push) Waiting to run

This commit is contained in:
lisonge 2024-09-18 22:19:04 +08:00
parent de140488aa
commit 128a6cdf60
4 changed files with 171 additions and 149 deletions

View File

@ -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,22 +57,108 @@ 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(
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")
)
}
}
}
override fun onDestroy() {
super.onDestroy()
isRunning.value = false
localNetworkIpsFlow.value = emptyList()
scope.launchTry(Dispatchers.IO) {
server?.stop()
if (storeFlow.value.autoClearMemorySubs) {
deleteSubscription(LOCAL_HTTP_SUBS_ID)
}
delay(3000)
scope.cancel()
}
}
companion object {
val isRunning = MutableStateFlow(false)
val localNetworkIpsFlow = MutableStateFlow(emptyList<String>())
fun stop(context: Context = app) {
context.stopService(Intent(context, HttpService::class.java))
}
fun start(context: Context = app) {
context.startForegroundService(Intent(context, HttpService::class.java))
}
}
override fun onBind(intent: Intent?) = null
}
@Serializable
data class RpcOk(
val message: String? = null,
)
@Serializable
data class ReqId(
val id: Long,
)
@Serializable
data class ServerInfo(
val device: DeviceInfo = DeviceInfo.instance,
val gkdAppInfo: AppInfo = selfAppInfo
)
fun clearHttpSubs() {
// 如果 app 被直接在任务列表划掉, HTTP订阅会没有清除, 所以在后续的第一次启动时清除
if (HttpService.isRunning.value) return
appScope.launchTry(Dispatchers.IO) {
delay(1000)
if (storeFlow.value.autoClearMemorySubs) {
deleteSubscription(LOCAL_HTTP_SUBS_ID)
}
}
}
private val httpSubsItem by lazy {
SubsItem(
id = LOCAL_HTTP_SUBS_ID,
order = -1,
enableUpdate = false,
)
}
fun createServer(port: Int): CIOApplicationEngine {
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) { "<script type='module' src='$SERVER_SCRIPT_URL'></script>" } }
route("/api") {
@ -159,87 +245,4 @@ class HttpService : CompositionService({
}
}
}
}
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())
}
}
onDestroy {
scope.launchTry(Dispatchers.IO) {
server?.stop()
if (storeFlow.value.autoClearMemorySubs) {
deleteSubscription(LOCAL_HTTP_SUBS_ID)
}
delay(3000)
scope.cancel()
}
}
isRunning.value = true
localNetworkIpsFlow.value = getIpAddressInLocalNetwork()
onDestroy {
isRunning.value = false
localNetworkIpsFlow.value = emptyList()
}
}) {
companion object {
val isRunning = MutableStateFlow(false)
val localNetworkIpsFlow = MutableStateFlow(emptyList<String>())
fun stop(context: Context = app) {
context.stopService(Intent(context, HttpService::class.java))
}
fun start(context: Context = app) {
context.startForegroundService(Intent(context, HttpService::class.java))
}
}
}
@Serializable
data class RpcOk(
val message: String? = null,
)
@Serializable
data class ReqId(
val id: Long,
)
@Serializable
data class ServerInfo(
val device: DeviceInfo = DeviceInfo.instance,
val gkdAppInfo: AppInfo = selfAppInfo
)
fun clearHttpSubs() {
// 如果 app 被直接在任务列表划掉, HTTP订阅会没有清除, 所以在后续的第一次启动时清除
if (HttpService.isRunning.value) return
appScope.launchTry(Dispatchers.IO) {
delay(1000)
if (storeFlow.value.autoClearMemorySubs) {
deleteSubscription(LOCAL_HTTP_SUBS_ID)
}
}
}

View File

@ -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",
)
}

View File

@ -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

View File

@ -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<HomeVm>()
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