Merge pull request #5 from msomu/main
Fix Settings UI reactivity, resolve compiler warnings, update .gitignore
This commit is contained in:
2
android/.gitignore
vendored
2
android/.gitignore
vendored
@@ -7,6 +7,8 @@
|
|||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
|
/.idea/misc.xml
|
||||||
|
/.idea/junie.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
|
|||||||
10
android/.idea/misc.xml
generated
10
android/.idea/misc.xml
generated
@@ -1,10 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="temurin-21" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectType">
|
|
||||||
<option name="id" value="Android" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
@@ -64,6 +64,7 @@ dependencies {
|
|||||||
|
|
||||||
// Lifecycle service
|
// Lifecycle service
|
||||||
implementation(libs.lifecycle.service)
|
implementation(libs.lifecycle.service)
|
||||||
|
implementation(libs.lifecycle.runtime.compose)
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
implementation(libs.navigation.compose)
|
implementation(libs.navigation.compose)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.thisux.droidclaw.capture
|
package com.thisux.droidclaw.capture
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.PixelFormat
|
import android.graphics.PixelFormat
|
||||||
|
import androidx.core.graphics.createBitmap
|
||||||
|
import androidx.core.graphics.get
|
||||||
import android.hardware.display.DisplayManager
|
import android.hardware.display.DisplayManager
|
||||||
import android.hardware.display.VirtualDisplay
|
import android.hardware.display.VirtualDisplay
|
||||||
import android.media.ImageReader
|
import android.media.ImageReader
|
||||||
@@ -19,16 +22,19 @@ class ScreenCaptureManager(private val context: Context) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "ScreenCapture"
|
private const val TAG = "ScreenCapture"
|
||||||
const val REQUEST_CODE = 1001
|
|
||||||
val isAvailable = MutableStateFlow(false)
|
val isAvailable = MutableStateFlow(false)
|
||||||
|
|
||||||
// Stores MediaProjection consent for use by ConnectionService
|
// Stores MediaProjection consent for use by ConnectionService
|
||||||
var consentResultCode: Int? = null
|
var consentResultCode: Int? = null
|
||||||
var consentData: Intent? = null
|
var consentData: Intent? = null
|
||||||
|
|
||||||
|
// Expose consent as state so UI can react immediately
|
||||||
|
val hasConsentState = MutableStateFlow(false)
|
||||||
|
|
||||||
fun storeConsent(resultCode: Int, data: Intent?) {
|
fun storeConsent(resultCode: Int, data: Intent?) {
|
||||||
consentResultCode = resultCode
|
consentResultCode = resultCode
|
||||||
consentData = data
|
consentData = data
|
||||||
|
hasConsentState.value = (resultCode == Activity.RESULT_OK && data != null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasConsent(): Boolean = consentResultCode != null && consentData != null
|
fun hasConsent(): Boolean = consentResultCode != null && consentData != null
|
||||||
@@ -86,7 +92,7 @@ class ScreenCaptureManager(private val context: Context) {
|
|||||||
val rowStride = planes[0].rowStride
|
val rowStride = planes[0].rowStride
|
||||||
val rowPadding = rowStride - pixelStride * image.width
|
val rowPadding = rowStride - pixelStride * image.width
|
||||||
|
|
||||||
val bitmap = Bitmap.createBitmap(
|
val bitmap = createBitmap(
|
||||||
image.width + rowPadding / pixelStride,
|
image.width + rowPadding / pixelStride,
|
||||||
image.height,
|
image.height,
|
||||||
Bitmap.Config.ARGB_8888
|
Bitmap.Config.ARGB_8888
|
||||||
@@ -119,7 +125,7 @@ class ScreenCaptureManager(private val context: Context) {
|
|||||||
bitmap.width - 1 to bitmap.height - 1,
|
bitmap.width - 1 to bitmap.height - 1,
|
||||||
bitmap.width / 2 to bitmap.height / 2
|
bitmap.width / 2 to bitmap.height / 2
|
||||||
)
|
)
|
||||||
return points.all { (x, y) -> bitmap.getPixel(x, y) == android.graphics.Color.BLACK }
|
return points.all { (x, y) -> bitmap[x, y] == android.graphics.Color.BLACK }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import androidx.compose.material3.OutlinedButton
|
|||||||
import androidx.compose.material3.OutlinedTextField
|
import androidx.compose.material3.OutlinedTextField
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -36,8 +37,11 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LifecycleEventObserver
|
||||||
import com.thisux.droidclaw.DroidClawApp
|
import com.thisux.droidclaw.DroidClawApp
|
||||||
import com.thisux.droidclaw.accessibility.DroidClawAccessibilityService
|
import com.thisux.droidclaw.accessibility.DroidClawAccessibilityService
|
||||||
import com.thisux.droidclaw.capture.ScreenCaptureManager
|
import com.thisux.droidclaw.capture.ScreenCaptureManager
|
||||||
@@ -58,8 +62,21 @@ fun SettingsScreen() {
|
|||||||
|
|
||||||
val isAccessibilityEnabled by DroidClawAccessibilityService.isRunning.collectAsState()
|
val isAccessibilityEnabled by DroidClawAccessibilityService.isRunning.collectAsState()
|
||||||
val isCaptureAvailable by ScreenCaptureManager.isAvailable.collectAsState()
|
val isCaptureAvailable by ScreenCaptureManager.isAvailable.collectAsState()
|
||||||
val hasCaptureConsent = isCaptureAvailable || ScreenCaptureManager.hasConsent()
|
val hasConsent by ScreenCaptureManager.hasConsentState.collectAsState()
|
||||||
val isBatteryExempt = remember { BatteryOptimization.isIgnoringBatteryOptimizations(context) }
|
val hasCaptureConsent = isCaptureAvailable || hasConsent
|
||||||
|
|
||||||
|
var isBatteryExempt by remember { mutableStateOf(BatteryOptimization.isIgnoringBatteryOptimizations(context)) }
|
||||||
|
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
|
DisposableEffect(lifecycleOwner) {
|
||||||
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
|
if (event == Lifecycle.Event.ON_RESUME) {
|
||||||
|
isBatteryExempt = BatteryOptimization.isIgnoringBatteryOptimizations(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycleOwner.lifecycle.addObserver(observer)
|
||||||
|
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
||||||
|
}
|
||||||
|
|
||||||
val projectionLauncher = rememberLauncherForActivityResult(
|
val projectionLauncher = rememberLauncherForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-
|
|||||||
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
||||||
datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
|
||||||
lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycleService" }
|
lifecycle-service = { group = "androidx.lifecycle", name = "lifecycle-service", version.ref = "lifecycleService" }
|
||||||
|
lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycleService" }
|
||||||
navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" }
|
||||||
compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeIconsExtended" }
|
compose-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeIconsExtended" }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user