mirror of
https://github.com/gkd-kit/gkd.git
synced 2024-11-15 19:22:26 +08:00
feat: float button tile
This commit is contained in:
parent
71af6d340d
commit
77f008dd42
|
@ -121,6 +121,7 @@ android {
|
|||
"capture_snapshot" to "捕获快照",
|
||||
"import_data" to "导入数据",
|
||||
"http_server" to "HTTP服务",
|
||||
"float_button" to "悬浮按钮",
|
||||
).forEach {
|
||||
resValue("string", it.first, it.second + "-debug")
|
||||
}
|
||||
|
|
|
@ -186,6 +186,16 @@
|
|||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service
|
||||
android:name=".debug.FloatingTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/ic_radio_button"
|
||||
android:label="@string/float_button"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".debug.SnapshotActionService"
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package li.songe.gkd.debug
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.ViewConfiguration
|
||||
import androidx.compose.foundation.layout.size
|
||||
|
@ -19,6 +18,8 @@ import li.songe.gkd.appScope
|
|||
import li.songe.gkd.data.Tuple3
|
||||
import li.songe.gkd.notif.floatingNotif
|
||||
import li.songe.gkd.notif.notifyService
|
||||
import li.songe.gkd.permission.canDrawOverlaysState
|
||||
import li.songe.gkd.permission.notificationState
|
||||
import li.songe.gkd.util.launchTry
|
||||
import kotlin.math.sqrt
|
||||
|
||||
|
@ -88,8 +89,14 @@ class FloatingService : ExpandableBubbleService() {
|
|||
|
||||
companion object {
|
||||
val isRunning = MutableStateFlow(false)
|
||||
fun stop(context: Context = app) {
|
||||
context.stopService(Intent(context, FloatingService::class.java))
|
||||
|
||||
fun start() {
|
||||
if (!notificationState.checkOrToast()) return
|
||||
if (!canDrawOverlaysState.checkOrToast()) return
|
||||
app.startForegroundService(Intent(app, FloatingService::class.java))
|
||||
}
|
||||
fun stop() {
|
||||
app.stopService(Intent(app, FloatingService::class.java))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package li.songe.gkd.debug
|
||||
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import kotlinx.coroutines.MainScope
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import li.songe.gkd.util.OnChangeListen
|
||||
import li.songe.gkd.util.OnDestroy
|
||||
import li.songe.gkd.util.OnTileClick
|
||||
|
||||
class FloatingTileService : TileService(), OnDestroy, OnChangeListen, OnTileClick {
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
onStartListened()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
onTileClicked()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
super.onStopListening()
|
||||
onStopListened()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
onDestroyed()
|
||||
}
|
||||
|
||||
val scope = MainScope().also { scope ->
|
||||
onDestroyed { scope.cancel() }
|
||||
}
|
||||
private val listeningFlow = MutableStateFlow(false).also { listeningFlow ->
|
||||
onStartListened { listeningFlow.value = true }
|
||||
onStopListened { listeningFlow.value = false }
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
combine(
|
||||
FloatingService.isRunning,
|
||||
listeningFlow
|
||||
) { v1, v2 -> v1 to v2 }.collect { (running, listening) ->
|
||||
if (listening) {
|
||||
qsTile.state = if (running) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
onTileClicked {
|
||||
if (FloatingService.isRunning.value) {
|
||||
FloatingService.stop()
|
||||
} else {
|
||||
FloatingService.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -41,6 +41,7 @@ import li.songe.gkd.db.DbSet
|
|||
import li.songe.gkd.debug.SnapshotExt.captureSnapshot
|
||||
import li.songe.gkd.notif.httpNotif
|
||||
import li.songe.gkd.notif.notifyService
|
||||
import li.songe.gkd.permission.notificationState
|
||||
import li.songe.gkd.service.A11yService
|
||||
import li.songe.gkd.util.LOCAL_HTTP_SUBS_ID
|
||||
import li.songe.gkd.util.SERVER_SCRIPT_URL
|
||||
|
@ -106,6 +107,7 @@ class HttpService : Service() {
|
|||
}
|
||||
|
||||
fun start() {
|
||||
if (!notificationState.checkOrToast()) return
|
||||
app.startForegroundService(Intent(app, HttpService::class.java))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,46 +7,57 @@ import kotlinx.coroutines.cancel
|
|||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import li.songe.gkd.util.OnChangeListen
|
||||
import li.songe.gkd.util.OnDestroy
|
||||
import li.songe.gkd.util.OnTileClick
|
||||
|
||||
class HttpTileService : TileService() {
|
||||
val scope = MainScope()
|
||||
private val listeningFlow = MutableStateFlow(false)
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
scope.launch {
|
||||
combine(
|
||||
HttpService.isRunning,
|
||||
listeningFlow
|
||||
) { v1, v2 -> v1 to v2 }.collect { (httpRunning, listening) ->
|
||||
if (listening) {
|
||||
qsTile.state = if (httpRunning) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HttpTileService : TileService(), OnDestroy, OnChangeListen, OnTileClick {
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
listeningFlow.value = true
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
super.onStopListening()
|
||||
listeningFlow.value = false
|
||||
onStartListened()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
if (HttpService.isRunning.value) {
|
||||
HttpService.stop()
|
||||
} else {
|
||||
HttpService.start()
|
||||
}
|
||||
onTileClicked()
|
||||
}
|
||||
|
||||
override fun onStopListening() {
|
||||
super.onStopListening()
|
||||
onStopListened()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
scope.cancel()
|
||||
onDestroyed()
|
||||
}
|
||||
|
||||
val scope = MainScope().also { scope ->
|
||||
onDestroyed { scope.cancel() }
|
||||
}
|
||||
private val listeningFlow = MutableStateFlow(false).also { listeningFlow ->
|
||||
onStartListened { listeningFlow.value = true }
|
||||
onStopListened { listeningFlow.value = false }
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
combine(
|
||||
HttpService.isRunning,
|
||||
listeningFlow
|
||||
) { v1, v2 -> v1 to v2 }.collect { (running, listening) ->
|
||||
if (listening) {
|
||||
qsTile.state = if (running) Tile.STATE_ACTIVE else Tile.STATE_INACTIVE
|
||||
qsTile.updateTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
onTileClicked {
|
||||
if (HttpService.isRunning.value) {
|
||||
HttpService.stop()
|
||||
} else {
|
||||
HttpService.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ import li.songe.gkd.appScope
|
|||
import li.songe.gkd.shizuku.shizukuCheckGranted
|
||||
import li.songe.gkd.util.initOrResetAppInfoCache
|
||||
import li.songe.gkd.util.launchTry
|
||||
import li.songe.gkd.util.toast
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
|
@ -31,6 +32,14 @@ class PermissionState(
|
|||
fun updateAndGet(): Boolean {
|
||||
return stateFlow.updateAndGet { check() }
|
||||
}
|
||||
|
||||
fun checkOrToast(): Boolean {
|
||||
updateAndGet()
|
||||
if (!stateFlow.value) {
|
||||
reason?.text?.let { toast(it) }
|
||||
}
|
||||
return stateFlow.value
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSelfPermission(permission: String): Boolean {
|
||||
|
|
|
@ -59,6 +59,7 @@ class ManageService : Service() {
|
|||
val isRunning = MutableStateFlow(false)
|
||||
|
||||
fun start() {
|
||||
if (!notificationState.checkOrToast()) return
|
||||
app.startForegroundService(Intent(app, ManageService::class.java))
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package li.songe.gkd.ui
|
|||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.projection.MediaProjectionManager
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.clickable
|
||||
|
@ -53,7 +52,6 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.blankj.utilcode.util.LogUtils
|
||||
|
@ -449,10 +447,9 @@ fun AdvancedPage() {
|
|||
if (it) {
|
||||
requiredPermission(context, notificationState)
|
||||
requiredPermission(context, canDrawOverlaysState)
|
||||
val intent = Intent(context, FloatingService::class.java)
|
||||
ContextCompat.startForegroundService(context, intent)
|
||||
FloatingService.start()
|
||||
} else {
|
||||
FloatingService.stop(context)
|
||||
FloatingService.stop()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -64,3 +64,31 @@ interface OnA11yConnected : CanOnCallback {
|
|||
getCallbacks<() -> Unit>(8).forEach { it() }
|
||||
}
|
||||
}
|
||||
|
||||
interface OnChangeListen : CanOnCallback {
|
||||
fun onStartListened(f: () -> Unit) {
|
||||
getCallbacks<() -> Unit>(10).add(f)
|
||||
}
|
||||
|
||||
fun onStartListened() {
|
||||
getCallbacks<() -> Unit>(10).forEach { it() }
|
||||
}
|
||||
|
||||
fun onStopListened(f: () -> Unit) {
|
||||
getCallbacks<() -> Unit>(12).add(f)
|
||||
}
|
||||
|
||||
fun onStopListened() {
|
||||
getCallbacks<() -> Unit>(12).forEach { it() }
|
||||
}
|
||||
}
|
||||
|
||||
interface OnTileClick {
|
||||
fun onTileClicked(f: () -> Unit) {
|
||||
getCallbacks<() -> Unit>(14).add(f)
|
||||
}
|
||||
|
||||
fun onTileClicked() {
|
||||
getCallbacks<() -> Unit>(14).forEach { it() }
|
||||
}
|
||||
}
|
9
app/src/main/res/drawable/ic_radio_button.xml
Normal file
9
app/src/main/res/drawable/ic_radio_button.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="#fff"
|
||||
android:pathData="M480,680q83,0 141.5,-58.5T680,480q0,-83 -58.5,-141.5T480,280q-83,0 -141.5,58.5T280,480q0,83 58.5,141.5T480,680ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z" />
|
||||
</vector>
|
|
@ -5,4 +5,5 @@
|
|||
<string name="import_data">导入数据</string>
|
||||
<string name="capture_snapshot">捕获快照</string>
|
||||
<string name="http_server">HTTP服务</string>
|
||||
<string name="float_button">悬浮按钮</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in New Issue
Block a user