mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-20 00:51:32 +00:00
fix: Permission
This commit is contained in:
parent
63e6ec5a27
commit
da69d59382
13 changed files with 221 additions and 131 deletions
|
|
@ -6,6 +6,6 @@ internal sealed interface DeviceAction {
|
|||
|
||||
data object Refresh : DeviceAction
|
||||
|
||||
data class ChangePermission(val permissions: Permissions, val granted: Boolean) : DeviceAction
|
||||
data class ChangePermission(val permission: String, val granted: Boolean) : DeviceAction
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,14 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import io.github.openflocon.domain.device.models.DeviceId
|
||||
import io.github.openflocon.flocondesktop.common.ui.window.FloconWindow
|
||||
import io.github.openflocon.flocondesktop.common.ui.window.createFloconWindowState
|
||||
import io.github.openflocon.flocondesktop.device.models.DeviceUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.previewDeviceUiState
|
||||
import io.github.openflocon.library.designsystem.FloconTheme
|
||||
import io.github.openflocon.library.designsystem.components.FloconCheckbox
|
||||
import io.github.openflocon.library.designsystem.components.FloconHorizontalDivider
|
||||
|
|
@ -63,12 +66,12 @@ private fun Content(
|
|||
) {
|
||||
val pagerState = rememberPagerState { 2 }
|
||||
|
||||
LaunchedEffect(uiState.selectedIndex) {
|
||||
pagerState.animateScrollToPage(uiState.selectedIndex)
|
||||
LaunchedEffect(uiState.contentState.selectedIndex) {
|
||||
pagerState.animateScrollToPage(uiState.contentState.selectedIndex)
|
||||
}
|
||||
|
||||
FloconWindow(
|
||||
title = "Device",
|
||||
title = "Device - ${uiState.infoState.model}",
|
||||
onCloseRequest = onCloseRequest,
|
||||
state = createFloconWindowState()
|
||||
) {
|
||||
|
|
@ -86,18 +89,18 @@ private fun Content(
|
|||
onAction = onAction
|
||||
)
|
||||
FloconScrollableTabRow(
|
||||
selectedTabIndex = uiState.selectedIndex,
|
||||
selectedTabIndex = uiState.contentState.selectedIndex,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
FloconTab(
|
||||
text = "Info",
|
||||
selected = true,
|
||||
selected = uiState.contentState.selectedIndex == 0,
|
||||
onClick = { onAction(DeviceAction.SelectTab(0)) },
|
||||
selectedContentColor = FloconTheme.colorPalette.onSurface
|
||||
)
|
||||
FloconTab(
|
||||
text = "Permission",
|
||||
selected = true,
|
||||
selected = uiState.contentState.selectedIndex == 1,
|
||||
onClick = { onAction(DeviceAction.SelectTab(1)) },
|
||||
selectedContentColor = FloconTheme.colorPalette.onSurface
|
||||
)
|
||||
|
|
@ -112,8 +115,8 @@ private fun Content(
|
|||
HorizontalPager(
|
||||
state = pagerState,
|
||||
userScrollEnabled = false,
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
pageSpacing = 16.dp,
|
||||
contentPadding = PaddingValues(8.dp),
|
||||
pageSpacing = 8.dp,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(it)
|
||||
|
|
@ -142,7 +145,7 @@ private fun Header(
|
|||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "${uiState.model} (${uiState.serialNumber})",
|
||||
text = "${uiState.infoState.model} (${uiState.infoState.serialNumber})",
|
||||
style = FloconTheme.typography.headlineSmall,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
|
|
@ -160,15 +163,16 @@ private fun InfoPage(
|
|||
uiState: DeviceUiState
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
FloconTextValue("Brand", uiState.brand)
|
||||
FloconTextValue("CPU", uiState.cpu)
|
||||
FloconTextValue("Memory", uiState.mem)
|
||||
FloconTextValue("Battery", uiState.battery)
|
||||
FloconTextValue("Serial number", uiState.serialNumber)
|
||||
FloconTextValue("Version - Release", uiState.versionRelease)
|
||||
FloconTextValue("Version - Sdk", uiState.versionSdk)
|
||||
FloconTextValue("Brand", uiState.infoState.brand)
|
||||
// FloconTextValue("CPU", uiState.cpu)
|
||||
// FloconTextValue("Memory", uiState.mem)
|
||||
FloconTextValue("Battery", uiState.infoState.battery)
|
||||
FloconTextValue("Serial number", uiState.infoState.serialNumber)
|
||||
FloconTextValue("Version - Release", uiState.infoState.versionRelease)
|
||||
FloconTextValue("Version - Sdk", uiState.infoState.versionSdk)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -181,13 +185,15 @@ private fun PermissionPage(
|
|||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(
|
||||
items = uiState.permissions,
|
||||
items = uiState.permissionState.list,
|
||||
key = { it.name }
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clip(FloconTheme.shapes.medium)
|
||||
.clickable(onClick = {
|
||||
// onAction(
|
||||
// DeviceAction.ChangePermission(
|
||||
|
|
@ -196,18 +202,17 @@ private fun PermissionPage(
|
|||
// )
|
||||
// )
|
||||
})
|
||||
.padding(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = it.name,
|
||||
style = FloconTheme.typography.labelSmall,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Text(
|
||||
text = it.status
|
||||
)
|
||||
FloconCheckbox(
|
||||
checked = it.granted,
|
||||
uncheckedColor = FloconTheme.colorPalette.secondary,
|
||||
onCheckedChange = {}
|
||||
onCheckedChange = null,
|
||||
uncheckedColor = FloconTheme.colorPalette.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.device
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class DeviceUiState(
|
||||
val selectedIndex: Int,
|
||||
|
||||
val model: String,
|
||||
val brand: String,
|
||||
val versionRelease: String,
|
||||
val versionSdk: String,
|
||||
val serialNumber: String,
|
||||
val battery: String,
|
||||
val cpu: String,
|
||||
val mem: String,
|
||||
|
||||
val permissions: List<PermissionUiState>
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class PermissionUiState(
|
||||
val name: String,
|
||||
val status: String,
|
||||
val granted: Boolean
|
||||
)
|
||||
|
||||
internal fun previewDeviceUiState() = DeviceUiState(
|
||||
selectedIndex = 0,
|
||||
|
||||
model = "",
|
||||
brand = "",
|
||||
versionRelease = "",
|
||||
versionSdk = "",
|
||||
serialNumber = "",
|
||||
battery = "",
|
||||
cpu = "",
|
||||
mem = "",
|
||||
permissions = emptyList()
|
||||
)
|
||||
|
|
@ -6,9 +6,18 @@ import io.github.openflocon.domain.adb.usecase.GetDeviceSerialUseCase
|
|||
import io.github.openflocon.domain.adb.usecase.SendCommandUseCase
|
||||
import io.github.openflocon.domain.common.getOrNull
|
||||
import io.github.openflocon.domain.device.usecase.GetCurrentDeviceIdAndPackageNameUseCase
|
||||
import io.github.openflocon.flocondesktop.device.models.ContentUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.CpuUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.DeviceUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.InfoUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.MemoryUiState
|
||||
import io.github.openflocon.flocondesktop.device.models.PermissionItem
|
||||
import io.github.openflocon.flocondesktop.device.models.PermissionUiState
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
|
@ -19,21 +28,47 @@ internal class DeviceViewModel(
|
|||
val currentDeviceAppsUseCase: GetCurrentDeviceIdAndPackageNameUseCase
|
||||
) : ViewModel() {
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
DeviceUiState(
|
||||
selectedIndex = 0,
|
||||
private val contentState = MutableStateFlow(ContentUiState(selectedIndex = 0))
|
||||
private val infoState = MutableStateFlow(
|
||||
InfoUiState(
|
||||
model = "",
|
||||
brand = "",
|
||||
versionRelease = "",
|
||||
versionSdk = "",
|
||||
serialNumber = "",
|
||||
battery = "",
|
||||
cpu = "",
|
||||
mem = "",
|
||||
permissions = emptyList()
|
||||
battery = ""
|
||||
)
|
||||
)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
private val memoryState = MutableStateFlow(MemoryUiState(emptyList()))
|
||||
private val cpuState = MutableStateFlow(CpuUiState(emptyList()))
|
||||
private val permissionState = MutableStateFlow(PermissionUiState(emptyList()))
|
||||
|
||||
val uiState = combine(
|
||||
contentState,
|
||||
infoState,
|
||||
memoryState,
|
||||
cpuState,
|
||||
permissionState
|
||||
) { content, info, memory, cpu, permission ->
|
||||
DeviceUiState(
|
||||
contentState = content,
|
||||
infoState = info,
|
||||
memoryState = memory,
|
||||
cpuState = cpu,
|
||||
permissionState = permission
|
||||
)
|
||||
}
|
||||
.stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = DeviceUiState(
|
||||
contentState = contentState.value,
|
||||
infoState = infoState.value,
|
||||
memoryState = memoryState.value,
|
||||
cpuState = cpuState.value,
|
||||
permissionState = permissionState.value
|
||||
)
|
||||
)
|
||||
|
||||
private var deviceSerial: String = ""
|
||||
|
||||
|
|
@ -58,32 +93,32 @@ internal class DeviceViewModel(
|
|||
private fun onChangePermission(action: DeviceAction.ChangePermission) {
|
||||
viewModelScope.launch {
|
||||
if (action.granted) {
|
||||
revokePermission(action.permissions)
|
||||
revokePermission(action.permission)
|
||||
} else {
|
||||
grantPermission(action.permissions)
|
||||
grantPermission(action.permission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSelect(action: DeviceAction.SelectTab) {
|
||||
_uiState.update { it.copy(selectedIndex = action.index) }
|
||||
contentState.update { it.copy(selectedIndex = action.index) }
|
||||
}
|
||||
|
||||
private fun onRefresh() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { state ->
|
||||
state.copy(
|
||||
cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")),
|
||||
battery = sendCommand("shell", "dumpsys", "battery"),
|
||||
mem = sendCommand("shell", "dumpsys", "meminfo")
|
||||
)
|
||||
}
|
||||
// _uiState.update { state ->
|
||||
// state.copy(
|
||||
// cpu = main(sendCommand("shell", "dumpsys", "cpuinfo")),
|
||||
// battery = sendCommand("shell", "dumpsys", "battery"),
|
||||
// mem = sendCommand("shell", "dumpsys", "meminfo")
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
private fun deviceInfo() {
|
||||
viewModelScope.launch {
|
||||
_uiState.update { state ->
|
||||
infoState.update { state ->
|
||||
state.copy(
|
||||
model = sendCommand("shell", "getprop", "ro.product.model"),
|
||||
brand = sendCommand("shell", "getprop", "ro.product.brand"),
|
||||
|
|
@ -98,42 +133,37 @@ internal class DeviceViewModel(
|
|||
private fun fetchPermission() {
|
||||
viewModelScope.launch {
|
||||
val packageName = currentDeviceAppsUseCase()?.packageName ?: return@launch
|
||||
val command = sendCommand("shell", "cmd", "appops", "get", packageName)
|
||||
val command = sendCommand("shell", "dumpsys", "package", packageName)
|
||||
val permissions = command.lines()
|
||||
.dropWhile { !it.contains("install permissions:") }
|
||||
.drop(1)
|
||||
.takeWhile { it.contains("granted=") }
|
||||
.map { it.trim() }
|
||||
.filter { it.startsWith(PERMISSION_PREFIX) }
|
||||
.mapNotNull { line ->
|
||||
val list = line.split(":")
|
||||
|
||||
PermissionUiState(
|
||||
name = list.getOrNull(0) ?: return@mapNotNull null,
|
||||
status = list.getOrNull(1) ?: return@mapNotNull null,
|
||||
granted = false
|
||||
PermissionItem(
|
||||
name = list.getOrNull(0)?.removePrefix(PERMISSION_PREFIX) ?: return@mapNotNull null,
|
||||
granted = list.getOrNull(1)?.contains("granted=true") ?: return@mapNotNull null,
|
||||
)
|
||||
}
|
||||
.sortedBy(PermissionUiState::name)
|
||||
.sortedBy(PermissionItem::name)
|
||||
|
||||
// val granted = sendCommand("shell", "dumpsys", "package", packageName, "|", "grep", "permission")
|
||||
// val permissions = Permissions.entries
|
||||
// .map { permissions ->
|
||||
// PermissionUiState(
|
||||
// permissions = permissions,
|
||||
// granted = granted.contains(permissions.value)
|
||||
// )
|
||||
// }
|
||||
|
||||
_uiState.update { it.copy(permissions = permissions) }
|
||||
permissionState.update { it.copy(list = permissions) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun grantPermission(permissions: Permissions) {
|
||||
private suspend fun grantPermission(permission: String) {
|
||||
val packageName = currentDeviceAppsUseCase() ?: return
|
||||
|
||||
sendCommand("shell", "pm", "grant", packageName.packageName, permissions.value)
|
||||
sendCommand("shell", "pm", "grant", packageName.packageName, "${PERMISSION_PREFIX}$permission")
|
||||
}
|
||||
|
||||
private suspend fun revokePermission(permissions: Permissions) {
|
||||
private suspend fun revokePermission(permission: String) {
|
||||
val packageName = currentDeviceAppsUseCase() ?: return
|
||||
|
||||
sendCommand("shell", "pm", "revoke", packageName.packageName, permissions.value)
|
||||
sendCommand("shell", "pm", "revoke", packageName.packageName, "$PERMISSION_PREFIX$permission")
|
||||
}
|
||||
|
||||
private suspend fun sendCommand(vararg args: String): String {
|
||||
|
|
@ -247,4 +277,8 @@ internal class DeviceViewModel(
|
|||
return ""
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PERMISSION_PREFIX = "android.permission."
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
package io.github.openflocon.flocondesktop.device
|
||||
|
||||
enum class Permissions(
|
||||
val label: String,
|
||||
val value: String,
|
||||
) {
|
||||
BACKGROUND_LOCATION(
|
||||
label = "Background location",
|
||||
value = "android.permission.ACCESS_BACKGROUND_LOCATION"
|
||||
),
|
||||
ACCESS_COARSE_LOCATION(
|
||||
label = "Approximate location",
|
||||
value = "android.permission.ACCESS_COARSE_LOCATION"
|
||||
),
|
||||
ACCESS_FINE_LOCATION(
|
||||
label = "Precise location",
|
||||
value = "android.permission.ACCESS_FINE_LOCATION"
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class ContentUiState(
|
||||
val selectedIndex: Int
|
||||
)
|
||||
|
||||
fun previewContentUiState() = ContentUiState(
|
||||
selectedIndex = 0
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class CpuUiState(
|
||||
val list: List<CpuItem>
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class CpuItem(
|
||||
val name: String
|
||||
)
|
||||
|
||||
fun previewCpuUiState() = CpuUiState(
|
||||
list = emptyList()
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class DeviceUiState(
|
||||
val contentState: ContentUiState,
|
||||
val infoState: InfoUiState,
|
||||
val cpuState: CpuUiState,
|
||||
val memoryState: MemoryUiState,
|
||||
val permissionState: PermissionUiState
|
||||
)
|
||||
|
||||
internal fun previewDeviceUiState() = DeviceUiState(
|
||||
contentState = previewContentUiState(),
|
||||
cpuState = previewCpuUiState(),
|
||||
memoryState = previewMemoryUiState(),
|
||||
infoState = previewInfoUiState(),
|
||||
permissionState = previewPermissionUiState()
|
||||
)
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class InfoUiState(
|
||||
val model: String,
|
||||
val brand: String,
|
||||
val versionRelease: String,
|
||||
val versionSdk: String,
|
||||
val serialNumber: String,
|
||||
val battery: String
|
||||
)
|
||||
|
||||
fun previewInfoUiState() = InfoUiState(
|
||||
model = "",
|
||||
brand = "",
|
||||
versionRelease = "",
|
||||
versionSdk = "",
|
||||
serialNumber = "",
|
||||
battery = ""
|
||||
)
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class MemoryUiState(
|
||||
val list: List<MemoryItem>
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class MemoryItem(
|
||||
val name: String
|
||||
)
|
||||
|
||||
fun previewMemoryUiState() = MemoryUiState(
|
||||
list = emptyList()
|
||||
)
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package io.github.openflocon.flocondesktop.device.models
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class PermissionUiState(
|
||||
val list: List<PermissionItem>
|
||||
)
|
||||
|
||||
@Immutable
|
||||
data class PermissionItem(
|
||||
val name: String,
|
||||
val granted: Boolean
|
||||
)
|
||||
|
||||
fun previewPermissionUiState() = PermissionUiState(
|
||||
list = emptyList()
|
||||
)
|
||||
|
|
@ -10,7 +10,7 @@ import io.github.openflocon.library.designsystem.FloconTheme
|
|||
@Composable
|
||||
fun FloconCheckbox(
|
||||
checked: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit,
|
||||
onCheckedChange: ((Boolean) -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
uncheckedColor: Color = FloconTheme.colorPalette.primary
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -16,25 +16,29 @@ import io.github.openflocon.library.designsystem.FloconTheme
|
|||
@Composable
|
||||
fun FloconTextValue(
|
||||
label: String,
|
||||
value: String
|
||||
value: String,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(2.dp)
|
||||
modifier = modifier.padding(2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = label,
|
||||
style = FloconTheme.typography.labelSmall,
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
Text(
|
||||
text = value,
|
||||
style = FloconTheme.typography.labelSmall,
|
||||
SelectionContainer(
|
||||
modifier = Modifier.weight(1f)
|
||||
.clip(FloconTheme.shapes.small)
|
||||
.background(FloconTheme.colorPalette.primary)
|
||||
.padding(4.dp)
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = value,
|
||||
style = FloconTheme.typography.labelSmall
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue