mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Fix permission refreshing and add card if bluetooth is disabled.
This commit is contained in:
@@ -1,20 +1,43 @@
|
||||
package eu.darken.capod.main.core
|
||||
|
||||
import android.content.Context
|
||||
import dagger.Reusable
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
import eu.darken.capod.common.permissions.isRequired
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import java.util.*
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Reusable
|
||||
@Singleton
|
||||
class PermissionTool @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val generalSettings: GeneralSettings,
|
||||
) {
|
||||
private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
|
||||
|
||||
suspend fun missingPermissions(): Set<Permission> = Permission.values()
|
||||
.filter { it != Permission.IGNORE_BATTERY_OPTIMIZATION || generalSettings.monitorMode.value == MonitorMode.ALWAYS }
|
||||
.filter { it.isRequired(context) }
|
||||
.toSet()
|
||||
fun recheck() {
|
||||
log(TAG) { "recheck()" }
|
||||
permissionCheckTrigger.value = UUID.randomUUID()
|
||||
}
|
||||
|
||||
val missingPermissions: Flow<Set<Permission>> = combine(
|
||||
permissionCheckTrigger,
|
||||
generalSettings.monitorMode.flow,
|
||||
) { _, monitorMode ->
|
||||
Permission.values()
|
||||
.filter { it != Permission.IGNORE_BATTERY_OPTIMIZATION || monitorMode == MonitorMode.ALWAYS }
|
||||
.filter { it.isRequired(context) }
|
||||
.toSet()
|
||||
}
|
||||
.onEach { log(TAG) { "Missing permission: $it" } }
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("PermissionTool")
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import eu.darken.capod.common.lists.differ.setupDiffer
|
||||
import eu.darken.capod.common.lists.modular.ModularAdapter
|
||||
import eu.darken.capod.common.lists.modular.mods.DataBinderMod
|
||||
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
|
||||
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
||||
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
||||
import eu.darken.capod.main.ui.overview.cards.NoPairedDeviceCardVH
|
||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||
@@ -35,6 +36,7 @@ class OverviewAdapter @Inject constructor() :
|
||||
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is NoPairedDeviceCardVH.Item }) { NoPairedDeviceCardVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is MissingMainDeviceVH.Item }) { MissingMainDeviceVH(it) })
|
||||
modules.add(TypedVHCreatorMod({ data[it] is BluetoothDisabledVH.Item }) { BluetoothDisabledVH(it) })
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = data.size
|
||||
|
||||
@@ -70,8 +70,10 @@ class OverviewFragment : Fragment3(R.layout.main_fragment) {
|
||||
}
|
||||
}
|
||||
|
||||
vm.listItems.observe2(ui) {
|
||||
adapter.update(it)
|
||||
vm.listItems.observe2(ui) { adapter.update(it) }
|
||||
|
||||
vm.workerAutolaunch.observe2 {
|
||||
// While UI is active, subscribe to the autolaunch routine
|
||||
}
|
||||
|
||||
vm.requestPermissionEvent.observe2(ui) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import eu.darken.capod.common.bluetooth.BluetoothManager2
|
||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.flow.combine
|
||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||
import eu.darken.capod.common.navigation.navVia
|
||||
import eu.darken.capod.common.permissions.Permission
|
||||
@@ -16,6 +17,7 @@ import eu.darken.capod.common.upgrade.UpgradeRepo
|
||||
import eu.darken.capod.main.core.GeneralSettings
|
||||
import eu.darken.capod.main.core.MonitorMode
|
||||
import eu.darken.capod.main.core.PermissionTool
|
||||
import eu.darken.capod.main.ui.overview.cards.BluetoothDisabledVH
|
||||
import eu.darken.capod.main.ui.overview.cards.MissingMainDeviceVH
|
||||
import eu.darken.capod.main.ui.overview.cards.NoPairedDeviceCardVH
|
||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||
@@ -26,7 +28,6 @@ import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.isActive
|
||||
@@ -57,9 +58,7 @@ class OverviewFragmentVM @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
|
||||
private val requiredPermissions: Flow<Collection<Permission>> = permissionCheckTrigger
|
||||
.map { permissionTool.missingPermissions() }
|
||||
val workerAutolaunch: LiveData<Unit> = permissionTool.missingPermissions
|
||||
.onEach {
|
||||
if (it.isNotEmpty()) {
|
||||
log(TAG) { "Missing permissions: $it" }
|
||||
@@ -76,26 +75,24 @@ class OverviewFragmentVM @Inject constructor(
|
||||
monitorControl.startMonitor()
|
||||
}
|
||||
}
|
||||
.map { Unit }
|
||||
.asLiveData2()
|
||||
|
||||
val requestPermissionEvent = SingleLiveEvent<Permission>()
|
||||
|
||||
private val pods: Flow<List<PodDevice>> = requiredPermissions
|
||||
private val pods: Flow<List<PodDevice>> = permissionTool.missingPermissions
|
||||
.flatMapLatest { permissions ->
|
||||
if (permissions.isEmpty()) {
|
||||
generalSettings.showAll.flow
|
||||
.flatMapLatest { showAll ->
|
||||
if (showAll) {
|
||||
podMonitor.devices
|
||||
} else {
|
||||
podMonitor.mainDevice.map { mainDevice ->
|
||||
mainDevice?.let { listOf(it) } ?: emptyList()
|
||||
}
|
||||
}
|
||||
if (permissions.isNotEmpty()) {
|
||||
return@flatMapLatest flowOf(emptyList())
|
||||
}
|
||||
|
||||
generalSettings.showAll.flow.flatMapLatest { showAll ->
|
||||
if (showAll) {
|
||||
podMonitor.devices
|
||||
} else {
|
||||
podMonitor.mainDevice.map { mainDevice ->
|
||||
mainDevice?.let { listOf(it) } ?: emptyList()
|
||||
}
|
||||
} else {
|
||||
channelFlow {
|
||||
send(emptyList())
|
||||
awaitClose()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,11 +100,12 @@ class OverviewFragmentVM @Inject constructor(
|
||||
|
||||
val listItems: LiveData<List<OverviewAdapter.Item>> = combine(
|
||||
updateTicker,
|
||||
requiredPermissions,
|
||||
permissionTool.missingPermissions,
|
||||
pods,
|
||||
debugSettings.isDebugModeEnabled.flow,
|
||||
generalSettings.showAll.flow,
|
||||
) { tick, permissions, pods, isDebugMode, showAll ->
|
||||
bluetoothManager.isBluetoothEnabled,
|
||||
) { _, permissions, pods, isDebugMode, showAll, isBluetoothEnabled ->
|
||||
val items = mutableListOf<OverviewAdapter.Item>()
|
||||
|
||||
val mainPod = podMonitor.mainDevice.first()
|
||||
@@ -144,20 +142,24 @@ class OverviewFragmentVM @Inject constructor(
|
||||
.run { items.addAll(this) }
|
||||
|
||||
permissions
|
||||
.map {
|
||||
.map { perm ->
|
||||
PermissionCardVH.Item(
|
||||
permission = it,
|
||||
permission = perm,
|
||||
onRequest = { requestPermissionEvent.postValue(it) }
|
||||
)
|
||||
}
|
||||
.run { items.addAll(this) }
|
||||
|
||||
if (!showAll && items.none { it is PodDeviceVH.Item } && permissions.isEmpty()) {
|
||||
NoPairedDeviceCardVH.Item {
|
||||
generalSettings.showAll.value = true
|
||||
}.run { items.add(this) }
|
||||
} else if (showAll && mainPod == null && permissions.isEmpty()) {
|
||||
items.add(0, MissingMainDeviceVH.Item)
|
||||
if (permissions.isEmpty()) {
|
||||
if (!isBluetoothEnabled) {
|
||||
items.add(0, BluetoothDisabledVH.Item)
|
||||
} else if (!showAll && items.none { it is PodDeviceVH.Item }) {
|
||||
NoPairedDeviceCardVH.Item {
|
||||
generalSettings.showAll.value = true
|
||||
}.run { items.add(this) }
|
||||
} else if (showAll && mainPod == null) {
|
||||
items.add(0, MissingMainDeviceVH.Item)
|
||||
}
|
||||
}
|
||||
|
||||
items
|
||||
@@ -166,7 +168,7 @@ class OverviewFragmentVM @Inject constructor(
|
||||
.asLiveData2()
|
||||
|
||||
fun onPermissionResult(granted: Boolean) {
|
||||
if (granted) permissionCheckTrigger.value = UUID.randomUUID()
|
||||
if (granted) permissionTool.recheck()
|
||||
}
|
||||
|
||||
fun goToSettings() = launch {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package eu.darken.capod.main.ui.overview.cards
|
||||
|
||||
import android.view.ViewGroup
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.lists.binding
|
||||
import eu.darken.capod.common.lists.differ.DifferItem
|
||||
import eu.darken.capod.databinding.OverviewBluetoothDisabledItemBinding
|
||||
import eu.darken.capod.main.ui.overview.OverviewAdapter
|
||||
|
||||
class BluetoothDisabledVH(parent: ViewGroup) :
|
||||
OverviewAdapter.BaseVH<BluetoothDisabledVH.Item, OverviewBluetoothDisabledItemBinding>(
|
||||
R.layout.overview_bluetooth_disabled_item,
|
||||
parent
|
||||
) {
|
||||
|
||||
override val viewBinding = lazy {
|
||||
OverviewBluetoothDisabledItemBinding.bind(itemView)
|
||||
}
|
||||
|
||||
override val onBindData: OverviewBluetoothDisabledItemBinding.(
|
||||
item: Item,
|
||||
payloads: List<Any>
|
||||
) -> Unit = binding(payload = true) { item ->
|
||||
|
||||
}
|
||||
|
||||
object Item : OverviewAdapter.Item {
|
||||
override val stableId: Long = Item::class.hashCode().toLong()
|
||||
|
||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)
|
||||
get() = { old, new -> if (new::class.isInstance(old)) new else null }
|
||||
}
|
||||
}
|
||||
@@ -32,17 +32,16 @@ class PodMonitor @Inject constructor(
|
||||
private val deviceCache = mutableMapOf<PodDevice.Id, PodDevice>()
|
||||
private val cacheLock = Mutex()
|
||||
|
||||
val devices: Flow<List<PodDevice>> = generalSettings.scannerMode.flow
|
||||
.flatMapLatest { scannerMode ->
|
||||
bluetoothManager.isBluetoothEnabled.flatMapLatest { isBluetoothEnabled ->
|
||||
if (isBluetoothEnabled) {
|
||||
bleScanner.scan(scannerMode = scannerMode)
|
||||
} else {
|
||||
log(TAG, WARN) { "Bluetooth is current disabled" }
|
||||
emptyFlow()
|
||||
}
|
||||
val devices: Flow<List<PodDevice>> = bluetoothManager.isBluetoothEnabled
|
||||
.flatMapLatest { isBluetoothEnabled ->
|
||||
if (isBluetoothEnabled) {
|
||||
generalSettings.scannerMode.flow
|
||||
} else {
|
||||
log(TAG, WARN) { "Bluetooth is currently disabled" }
|
||||
emptyFlow()
|
||||
}
|
||||
}
|
||||
.flatMapLatest { bleScanner.scan(scannerMode = it) }
|
||||
.map { result ->
|
||||
// For each address we only want the newest result, upstream may batch data
|
||||
result.groupBy { it.address }
|
||||
|
||||
@@ -76,7 +76,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private suspend fun doDoWork() {
|
||||
val permissionsMissingOnStart = permissionTool.missingPermissions()
|
||||
val permissionsMissingOnStart = permissionTool.missingPermissions.first()
|
||||
if (permissionsMissingOnStart.isNotEmpty()) {
|
||||
log(TAG, WARN) { "Aborting, missing permissions: $permissionsMissingOnStart" }
|
||||
return
|
||||
@@ -99,7 +99,7 @@ class MonitorWorker @AssistedInject constructor(
|
||||
|
||||
generalSettings.monitorMode.flow
|
||||
.flatMapLatest { monitorMode ->
|
||||
val missingPermsFlow = permissionTool.missingPermissions()
|
||||
val missingPermsFlow = permissionTool.missingPermissions.first()
|
||||
if (missingPermsFlow.isNotEmpty()) {
|
||||
log(TAG, WARN) { "Aborting, permissions are missing for $monitorMode: $missingPermsFlow" }
|
||||
workerScope.coroutineContext.cancelChildren()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card"
|
||||
style="@style/MyCardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:context=".main.ui.MainActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/bluetooth_label"
|
||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/overview_bluetooth_disabled_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/bluetooth_description"
|
||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/overview_bluetooth_disabled_description"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/bluetooth_label" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -146,4 +146,6 @@
|
||||
<string name="settings_popup_caseopen_label">Show popup</string>
|
||||
<string name="settings_popup_caseopen_description">Show a popup when the device case is opened (experimental).</string>
|
||||
<string name="notification_channel_reaction_popup_label">Popup device reactions</string>
|
||||
<string name="overview_bluetooth_disabled_label">Bluetooth is disabled</string>
|
||||
<string name="overview_bluetooth_disabled_description">Bluetooth is disabled, enable it ;).</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user