mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-16 03:32:38 +08:00
This commit is contained in:
parent
de140488aa
commit
128a6cdf60
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user