mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-16 11:16:12 -04:00
Improved device cards
This commit is contained in:
@@ -26,7 +26,7 @@ class BleScanner @Inject constructor(
|
|||||||
// TODO check Bluetooth enabled
|
// TODO check Bluetooth enabled
|
||||||
fun scan(
|
fun scan(
|
||||||
filter: Set<ScanFilter> = ProximityPairing.getBleScanFilter(),
|
filter: Set<ScanFilter> = ProximityPairing.getBleScanFilter(),
|
||||||
mode: Int = ScanSettings.SCAN_MODE_LOW_LATENCY,
|
mode: Int = ScanSettings.SCAN_MODE_LOW_POWER,
|
||||||
delay: Long = 1,
|
delay: Long = 1,
|
||||||
): Flow<List<ScanResult>> = callbackFlow {
|
): Flow<List<ScanResult>> = callbackFlow {
|
||||||
val scanner = bluetoothManager.adapter.bluetoothLeScanner
|
val scanner = bluetoothManager.adapter.bluetoothLeScanner
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package eu.darken.capod.common.debug.autoreport
|
package eu.darken.capod.common.debug.autoreport
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceDataStore
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import eu.darken.androidstarter.common.preferences.Settings
|
||||||
|
import eu.darken.capod.common.preferences.PreferenceStoreMapper
|
||||||
import eu.darken.capod.common.preferences.createFlowPreference
|
import eu.darken.capod.common.preferences.createFlowPreference
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
@@ -9,12 +13,26 @@ import javax.inject.Singleton
|
|||||||
@Singleton
|
@Singleton
|
||||||
class DebugSettings @Inject constructor(
|
class DebugSettings @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
) {
|
) : Settings() {
|
||||||
|
|
||||||
private val prefs by lazy {
|
override val preferences: SharedPreferences = context.getSharedPreferences("settings_debug", Context.MODE_PRIVATE)
|
||||||
context.getSharedPreferences("settings_debug", Context.MODE_PRIVATE)
|
|
||||||
|
val isAutoReportEnabled = preferences.createFlowPreference("debug.bugreport.automatic.enabled", true)
|
||||||
|
|
||||||
|
val isDebugModeEnabled = preferences.createFlowPreference("debug.mode.enabled", false)
|
||||||
|
|
||||||
|
override val preferenceDataStore: PreferenceDataStore = object : PreferenceStoreMapper() {
|
||||||
|
override fun getBoolean(key: String, defValue: Boolean): Boolean = when (key) {
|
||||||
|
isAutoReportEnabled.key -> isAutoReportEnabled.value
|
||||||
|
isDebugModeEnabled.key -> isDebugModeEnabled.value
|
||||||
|
else -> super.getBoolean(key, defValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putBoolean(key: String, value: Boolean) = when (key) {
|
||||||
|
isAutoReportEnabled.key -> isAutoReportEnabled.update { value }
|
||||||
|
isDebugModeEnabled.key -> isDebugModeEnabled.update { value }
|
||||||
|
else -> super.putBoolean(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val isAutoReportEnabled = prefs.createFlowPreference("bugreport.automatic.enabled", true)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -11,4 +11,16 @@ interface BindableVH<ItemT, ViewBindingT : ViewBinding> {
|
|||||||
fun bind(item: ItemT, payloads: MutableList<Any> = mutableListOf()) = with(viewBinding.value) {
|
fun bind(item: ItemT, payloads: MutableList<Any> = mutableListOf()) = with(viewBinding.value) {
|
||||||
onBindData(item, payloads)
|
onBindData(item, payloads)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
inline fun <reified ItemT, ViewBindingT : ViewBinding> BindableVH<ItemT, ViewBindingT>.binding(
|
||||||
|
payload: Boolean = true,
|
||||||
|
crossinline block: ViewBindingT.(ItemT) -> Unit,
|
||||||
|
): ViewBindingT.(item: ItemT, payloads: List<Any>) -> Unit = { item: ItemT, payloads: List<Any> ->
|
||||||
|
val newestItem = when (payload) {
|
||||||
|
true -> payloads.filterIsInstance<ItemT>().lastOrNull() ?: item
|
||||||
|
false -> item
|
||||||
|
}
|
||||||
|
block(newestItem)
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
|
|
||||||
class FlowPreference<T> constructor(
|
class FlowPreference<T> constructor(
|
||||||
private val preferences: SharedPreferences,
|
private val preferences: SharedPreferences,
|
||||||
private val key: String,
|
val key: String,
|
||||||
private val reader: SharedPreferences.(key: String) -> T,
|
private val reader: SharedPreferences.(key: String) -> T,
|
||||||
private val writer: SharedPreferences.Editor.(key: String, value: T) -> Unit
|
private val writer: SharedPreferences.Editor.(key: String, value: T) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -17,22 +17,20 @@ class GeneralSettings @Inject constructor(
|
|||||||
private val debugSettings: DebugSettings,
|
private val debugSettings: DebugSettings,
|
||||||
) : Settings() {
|
) : Settings() {
|
||||||
|
|
||||||
|
override val preferences: SharedPreferences = context.getSharedPreferences("settings_general", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
override val preferenceDataStore: PreferenceDataStore = object : PreferenceStoreMapper() {
|
override val preferenceDataStore: PreferenceDataStore = object : PreferenceStoreMapper() {
|
||||||
override fun getBoolean(key: String, defValue: Boolean): Boolean = when (key) {
|
override fun getBoolean(key: String, defValue: Boolean): Boolean = when (key) {
|
||||||
PK_BUGTRACKING_ENABLED -> debugSettings.isAutoReportEnabled.value
|
else -> debugSettings.preferenceDataStore.getBoolean(key, defValue)
|
||||||
else -> super.getBoolean(key, defValue)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun putBoolean(key: String, value: Boolean) = when (key) {
|
override fun putBoolean(key: String, value: Boolean) = when (key) {
|
||||||
PK_BUGTRACKING_ENABLED -> debugSettings.isAutoReportEnabled.update { value }
|
else -> debugSettings.preferenceDataStore.putBoolean(key, value)
|
||||||
else -> super.putBoolean(key, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val preferences: SharedPreferences = context.getSharedPreferences("settings_general", Context.MODE_PRIVATE)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
internal val TAG = logTag("Core", "Settings")
|
internal val TAG = logTag("Core", "Settings")
|
||||||
private const val PK_BUGTRACKING_ENABLED = "core.bugtracking.enabled"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,9 @@ 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.DataBinderMod
|
||||||
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
|
import eu.darken.capod.common.lists.modular.mods.TypedVHCreatorMod
|
||||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.BasicSingleApplePodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.SingleApplePodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -26,6 +28,8 @@ class OverviewAdapter @Inject constructor() :
|
|||||||
modules.add(DataBinderMod(data))
|
modules.add(DataBinderMod(data))
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is PermissionCardVH.Item }) { PermissionCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is PermissionCardVH.Item }) { PermissionCardVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is DualApplePodsCardVH.Item }) { DualApplePodsCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is DualApplePodsCardVH.Item }) { DualApplePodsCardVH(it) })
|
||||||
|
modules.add(TypedVHCreatorMod({ data[it] is SingleApplePodsCardVH.Item }) { SingleApplePodsCardVH(it) })
|
||||||
|
modules.add(TypedVHCreatorMod({ data[it] is BasicSingleApplePodsCardVH.Item }) { BasicSingleApplePodsCardVH(it) })
|
||||||
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
modules.add(TypedVHCreatorMod({ data[it] is UnknownPodDeviceCardVH.Item }) { UnknownPodDeviceCardVH(it) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.lifecycle.SavedStateHandle
|
|||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import eu.darken.capod.common.coroutine.DispatcherProvider
|
import eu.darken.capod.common.coroutine.DispatcherProvider
|
||||||
|
import eu.darken.capod.common.debug.autoreport.DebugSettings
|
||||||
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
import eu.darken.capod.common.debug.recording.core.RecorderModule
|
||||||
import eu.darken.capod.common.livedata.SingleLiveEvent
|
import eu.darken.capod.common.livedata.SingleLiveEvent
|
||||||
import eu.darken.capod.common.navigation.navVia
|
import eu.darken.capod.common.navigation.navVia
|
||||||
@@ -13,14 +14,20 @@ import eu.darken.capod.common.permissions.Permission
|
|||||||
import eu.darken.capod.common.permissions.isGrantedOrNotRequired
|
import eu.darken.capod.common.permissions.isGrantedOrNotRequired
|
||||||
import eu.darken.capod.common.uix.ViewModel3
|
import eu.darken.capod.common.uix.ViewModel3
|
||||||
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
import eu.darken.capod.main.ui.overview.cards.PermissionCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.BasicSingleApplePodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.DualApplePodsCardVH
|
||||||
|
import eu.darken.capod.main.ui.overview.cards.pods.SingleApplePodsCardVH
|
||||||
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
import eu.darken.capod.main.ui.overview.cards.pods.UnknownPodDeviceCardVH
|
||||||
import eu.darken.capod.monitor.core.PodMonitor
|
import eu.darken.capod.monitor.core.PodMonitor
|
||||||
import eu.darken.capod.monitor.core.worker.MonitorControl
|
import eu.darken.capod.monitor.core.worker.MonitorControl
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
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.DualApplePods
|
||||||
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -32,12 +39,19 @@ class OverviewFragmentVM @Inject constructor(
|
|||||||
private val recorderModule: RecorderModule,
|
private val recorderModule: RecorderModule,
|
||||||
private val monitorControl: MonitorControl,
|
private val monitorControl: MonitorControl,
|
||||||
private val podMonitor: PodMonitor,
|
private val podMonitor: PodMonitor,
|
||||||
|
private val debugSettings: DebugSettings,
|
||||||
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
) : ViewModel3(dispatcherProvider = dispatcherProvider) {
|
||||||
|
|
||||||
private val enabledState: Flow<Boolean> = flow {
|
private val enabledState: Flow<Boolean> = flow {
|
||||||
emit(false)
|
emit(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val updateTicker = channelFlow<Unit> {
|
||||||
|
while (isActive) {
|
||||||
|
trySend(Unit)
|
||||||
|
delay(1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
|
private val permissionCheckTrigger = MutableStateFlow(UUID.randomUUID())
|
||||||
private val requiredPermissions: Flow<List<Permission>> = permissionCheckTrigger
|
private val requiredPermissions: Flow<List<Permission>> = permissionCheckTrigger
|
||||||
.map {
|
.map {
|
||||||
@@ -64,10 +78,12 @@ class OverviewFragmentVM @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val listItems: LiveData<List<OverviewAdapter.Item>> = combine(
|
val listItems: LiveData<List<OverviewAdapter.Item>> = combine(
|
||||||
|
updateTicker,
|
||||||
enabledState,
|
enabledState,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
pods
|
pods,
|
||||||
) { state, permissions, pods ->
|
debugSettings.isDebugModeEnabled.flow
|
||||||
|
) { tick, state, permissions, pods, isDebugMode ->
|
||||||
val items = mutableListOf<OverviewAdapter.Item>()
|
val items = mutableListOf<OverviewAdapter.Item>()
|
||||||
|
|
||||||
permissions
|
permissions
|
||||||
@@ -82,7 +98,9 @@ class OverviewFragmentVM @Inject constructor(
|
|||||||
pods
|
pods
|
||||||
.map {
|
.map {
|
||||||
when (it) {
|
when (it) {
|
||||||
is DualApplePods -> DualApplePodsCardVH.Item(it)
|
is DualApplePods -> DualApplePodsCardVH.Item(it, showDebug = isDebugMode)
|
||||||
|
is SingleApplePods -> SingleApplePodsCardVH.Item(it, showDebug = isDebugMode)
|
||||||
|
is BasicSingleApplePods -> BasicSingleApplePodsCardVH.Item(it, showDebug = isDebugMode)
|
||||||
else -> UnknownPodDeviceCardVH.Item(it)
|
else -> UnknownPodDeviceCardVH.Item(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+60
@@ -0,0 +1,60 @@
|
|||||||
|
package eu.darken.capod.main.ui.overview.cards.pods
|
||||||
|
|
||||||
|
import android.icu.text.RelativeDateTimeFormatter
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.lists.binding
|
||||||
|
import eu.darken.capod.databinding.OverviewPodsAppleSingleBasicItemBinding
|
||||||
|
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||||
|
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class BasicSingleApplePodsCardVH(parent: ViewGroup) :
|
||||||
|
PodDeviceVH<BasicSingleApplePodsCardVH.Item, OverviewPodsAppleSingleBasicItemBinding>(
|
||||||
|
R.layout.overview_pods_apple_single_basic_item,
|
||||||
|
parent
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val viewBinding = lazy { OverviewPodsAppleSingleBasicItemBinding.bind(itemView) }
|
||||||
|
|
||||||
|
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
||||||
|
|
||||||
|
override val onBindData = binding(payload = true) { item: Item ->
|
||||||
|
val device = item.device
|
||||||
|
|
||||||
|
name.text = device.getLabel(context)
|
||||||
|
deviceIcon.setImageResource(device.iconRes)
|
||||||
|
|
||||||
|
val duration = Duration.between(device.lastSeenAt, Instant.now())
|
||||||
|
lastSeen.text = lastSeenFormatter.format(
|
||||||
|
duration.seconds.toDouble(),
|
||||||
|
RelativeDateTimeFormatter.Direction.LAST,
|
||||||
|
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
|
reception.text = device.getSignalQuality(context)
|
||||||
|
|
||||||
|
headphones.apply {
|
||||||
|
val sb = StringBuilder(context.getString(R.string.pods_single_headphones_label))
|
||||||
|
sb.append("\n").append(device.getBatteryLevelHeadset(context))
|
||||||
|
text = sb
|
||||||
|
}
|
||||||
|
|
||||||
|
status.apply {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (item.showDebug) {
|
||||||
|
sb.append("--- Debug ---")
|
||||||
|
sb.append("\n").append(device.rawDataHex)
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
|
isGone = !item.showDebug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Item(
|
||||||
|
override val device: BasicSingleApplePods,
|
||||||
|
override val showDebug: Boolean,
|
||||||
|
) : PodDeviceVH.Item
|
||||||
|
}
|
||||||
+21
-23
@@ -3,15 +3,15 @@ package eu.darken.capod.main.ui.overview.cards.pods
|
|||||||
import android.icu.text.RelativeDateTimeFormatter
|
import android.icu.text.RelativeDateTimeFormatter
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.BuildConfigWrap
|
import eu.darken.capod.common.lists.binding
|
||||||
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
|
import eu.darken.capod.databinding.OverviewPodsAppleDualItemBinding
|
||||||
import eu.darken.capod.pods.core.DualPods
|
import eu.darken.capod.pods.core.DualPodDevice.Pod
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods.DeviceColor
|
import eu.darken.capod.pods.core.apple.DualApplePods.DeviceColor
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
import eu.darken.capod.pods.core.apple.DualApplePods.LidState
|
||||||
import eu.darken.capod.pods.core.getBatteryCase
|
import eu.darken.capod.pods.core.getBatteryLevelCase
|
||||||
import eu.darken.capod.pods.core.getBatteryLeftPod
|
import eu.darken.capod.pods.core.getBatteryLevelLeftPod
|
||||||
import eu.darken.capod.pods.core.getBatteryRightPod
|
import eu.darken.capod.pods.core.getBatteryLevelRightPod
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@@ -21,19 +21,13 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
|||||||
parent
|
parent
|
||||||
) {
|
) {
|
||||||
|
|
||||||
override val viewBinding = lazy {
|
override val viewBinding = lazy { OverviewPodsAppleDualItemBinding.bind(itemView) }
|
||||||
OverviewPodsAppleDualItemBinding.bind(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
||||||
|
|
||||||
override val onBindData: OverviewPodsAppleDualItemBinding.(
|
override val onBindData = binding(payload = true) { item: Item ->
|
||||||
item: Item,
|
|
||||||
payloads: List<Any>
|
|
||||||
) -> Unit = { item, _ ->
|
|
||||||
val device = item.device
|
val device = item.device
|
||||||
|
|
||||||
|
|
||||||
name.apply {
|
name.apply {
|
||||||
val sb = StringBuilder(device.getLabel(context))
|
val sb = StringBuilder(device.getLabel(context))
|
||||||
if (device.deviceColor != DeviceColor.UNKNOWN) {
|
if (device.deviceColor != DeviceColor.UNKNOWN) {
|
||||||
@@ -54,7 +48,7 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
|||||||
|
|
||||||
podLeft.apply {
|
podLeft.apply {
|
||||||
val sb = StringBuilder(context.getString(R.string.pods_dual_left_label))
|
val sb = StringBuilder(context.getString(R.string.pods_dual_left_label))
|
||||||
sb.append("\n").append(device.getBatteryLeftPod(context))
|
sb.append("\n").append(device.getBatteryLevelLeftPod(context))
|
||||||
when {
|
when {
|
||||||
device.isLeftPodCharging -> sb.append("\n").append("Charging")
|
device.isLeftPodCharging -> sb.append("\n").append("Charging")
|
||||||
device.isLeftPodInEar -> sb.append("\n").append("In ear")
|
device.isLeftPodInEar -> sb.append("\n").append("In ear")
|
||||||
@@ -65,7 +59,7 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
|||||||
|
|
||||||
podRight.apply {
|
podRight.apply {
|
||||||
val sb = StringBuilder("Right pod")
|
val sb = StringBuilder("Right pod")
|
||||||
sb.append("\n").append(device.getBatteryRightPod(context))
|
sb.append("\n").append(device.getBatteryLevelRightPod(context))
|
||||||
when {
|
when {
|
||||||
device.isRightPodCharging -> sb.append("\n").append("Charging")
|
device.isRightPodCharging -> sb.append("\n").append("Charging")
|
||||||
device.isRightPodInEar -> sb.append("\n").append("In ear")
|
device.isRightPodInEar -> sb.append("\n").append("In ear")
|
||||||
@@ -75,13 +69,13 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
when (device.microPhonePod) {
|
when (device.microPhonePod) {
|
||||||
DualPods.Pod.LEFT -> podLeft.append("\n(Microphone)")
|
Pod.LEFT -> podLeft.append("\n(Microphone)")
|
||||||
DualPods.Pod.RIGHT -> podRight.append("\n(Microphone)")
|
Pod.RIGHT -> podRight.append("\n(Microphone)")
|
||||||
}
|
}
|
||||||
|
|
||||||
podCase.apply {
|
podCase.apply {
|
||||||
val sb = StringBuilder("Case")
|
val sb = StringBuilder("Case")
|
||||||
sb.append("\n").append(device.getBatteryCase(context))
|
sb.append("\n").append(device.getBatteryLevelCase(context))
|
||||||
if (device.isCaseCharging) sb.append("\n").append("Charging")
|
if (device.isCaseCharging) sb.append("\n").append("Charging")
|
||||||
when (device.caseLidState) {
|
when (device.caseLidState) {
|
||||||
LidState.OPEN -> sb.append("\n").append(context.getString(R.string.pods_case_status_open_label))
|
LidState.OPEN -> sb.append("\n").append(context.getString(R.string.pods_case_status_open_label))
|
||||||
@@ -93,14 +87,18 @@ class DualApplePodsCardVH(parent: ViewGroup) :
|
|||||||
text = sb
|
text = sb
|
||||||
}
|
}
|
||||||
|
|
||||||
status.text = getString(R.string.pods_status_x_label, device.getConnectionStateLabel(context))
|
status.apply {
|
||||||
if (BuildConfigWrap.DEBUG) {
|
val sb = StringBuilder(getString(R.string.pods_status_x_label, device.getConnectionStateLabel(context)))
|
||||||
status.append("\n")
|
if (item.showDebug) {
|
||||||
status.append(device.identifier.toString())
|
sb.append("\n\n").append("---Debug---")
|
||||||
|
sb.append("\n").append(device.rawDataHex)
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Item(
|
data class Item(
|
||||||
override val device: DualApplePods
|
override val device: DualApplePods,
|
||||||
|
override val showDebug: Boolean,
|
||||||
) : PodDeviceVH.Item
|
) : PodDeviceVH.Item
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,8 @@ abstract class PodDeviceVH<D : PodDeviceVH.Item, B : ViewBinding>(
|
|||||||
|
|
||||||
val device: PodDevice
|
val device: PodDevice
|
||||||
|
|
||||||
|
val showDebug: Boolean
|
||||||
|
|
||||||
override val stableId: Long get() = device.identifier.hashCode().toLong()
|
override val stableId: Long get() = device.identifier.hashCode().toLong()
|
||||||
|
|
||||||
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)?
|
override val payloadProvider: ((DifferItem, DifferItem) -> DifferItem?)?
|
||||||
|
|||||||
+65
@@ -0,0 +1,65 @@
|
|||||||
|
package eu.darken.capod.main.ui.overview.cards.pods
|
||||||
|
|
||||||
|
import android.icu.text.RelativeDateTimeFormatter
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.isGone
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.lists.binding
|
||||||
|
import eu.darken.capod.databinding.OverviewPodsAppleSingleItemBinding
|
||||||
|
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||||
|
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
||||||
|
import java.time.Duration
|
||||||
|
import java.time.Instant
|
||||||
|
|
||||||
|
class SingleApplePodsCardVH(parent: ViewGroup) :
|
||||||
|
PodDeviceVH<SingleApplePodsCardVH.Item, OverviewPodsAppleSingleItemBinding>(
|
||||||
|
R.layout.overview_pods_apple_single_item,
|
||||||
|
parent
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val viewBinding = lazy { OverviewPodsAppleSingleItemBinding.bind(itemView) }
|
||||||
|
|
||||||
|
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
||||||
|
|
||||||
|
override val onBindData = binding(payload = true) { item: Item ->
|
||||||
|
val device = item.device
|
||||||
|
|
||||||
|
name.text = device.getLabel(context)
|
||||||
|
deviceIcon.setImageResource(device.iconRes)
|
||||||
|
|
||||||
|
val duration = Duration.between(device.lastSeenAt, Instant.now())
|
||||||
|
lastSeen.text = lastSeenFormatter.format(
|
||||||
|
duration.seconds.toDouble(),
|
||||||
|
RelativeDateTimeFormatter.Direction.LAST,
|
||||||
|
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
|
reception.text = device.getSignalQuality(context)
|
||||||
|
|
||||||
|
headphones.apply {
|
||||||
|
val sb = StringBuilder(context.getString(R.string.pods_single_headphones_label))
|
||||||
|
sb.append("\n").append(device.getBatteryLevelHeadset(context))
|
||||||
|
when {
|
||||||
|
device.isHeadsetBeingCharged -> sb.append("\n").append("Charging")
|
||||||
|
device.isHeadsetBeingWorn -> sb.append("\n").append("In ear")
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
|
}
|
||||||
|
|
||||||
|
status.apply {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (item.showDebug) {
|
||||||
|
sb.append("--- Debug ---")
|
||||||
|
sb.append("\n").append(device.rawDataHex)
|
||||||
|
}
|
||||||
|
text = sb
|
||||||
|
isGone = !item.showDebug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Item(
|
||||||
|
override val device: SingleApplePods,
|
||||||
|
override val showDebug: Boolean,
|
||||||
|
) : PodDeviceVH.Item
|
||||||
|
}
|
||||||
+6
-5
@@ -3,6 +3,7 @@ package eu.darken.capod.main.ui.overview.cards.pods
|
|||||||
import android.icu.text.RelativeDateTimeFormatter
|
import android.icu.text.RelativeDateTimeFormatter
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
|
import eu.darken.capod.common.lists.binding
|
||||||
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
|
import eu.darken.capod.databinding.OverviewPodsUnknownItemBinding
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
@@ -20,10 +21,7 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
|
|||||||
|
|
||||||
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
private val lastSeenFormatter = RelativeDateTimeFormatter.getInstance()
|
||||||
|
|
||||||
override val onBindData: OverviewPodsUnknownItemBinding.(
|
override val onBindData = binding(payload = true) { item ->
|
||||||
item: Item,
|
|
||||||
payloads: List<Any>
|
|
||||||
) -> Unit = { item, _ ->
|
|
||||||
val device = item.device
|
val device = item.device
|
||||||
name.text = device.getLabel(context)
|
name.text = device.getLabel(context)
|
||||||
|
|
||||||
@@ -33,9 +31,12 @@ class UnknownPodDeviceCardVH(parent: ViewGroup) :
|
|||||||
RelativeDateTimeFormatter.Direction.LAST,
|
RelativeDateTimeFormatter.Direction.LAST,
|
||||||
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
RelativeDateTimeFormatter.RelativeUnit.SECONDS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rawdata.text = device.rawDataHex
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Item(
|
data class Item(
|
||||||
override val device: PodDevice
|
override val device: PodDevice,
|
||||||
|
override val showDebug: Boolean = false
|
||||||
) : PodDeviceVH.Item
|
) : PodDeviceVH.Item
|
||||||
}
|
}
|
||||||
@@ -31,9 +31,39 @@ class PodMonitor @Inject constructor(
|
|||||||
}
|
}
|
||||||
.onStart { emptyList<PodDevice>() }
|
.onStart { emptyList<PodDevice>() }
|
||||||
.map { scanResults ->
|
.map { scanResults ->
|
||||||
scanResults
|
val pods = scanResults
|
||||||
.sortedByDescending { it.rssi }
|
.sortedByDescending { it.rssi }
|
||||||
.mapNotNull { podFactory.createPod(it) }
|
.mapNotNull { podFactory.createPod(it) }
|
||||||
|
|
||||||
|
// if (BuildConfigWrap.DEBUG && scanResults.isNotEmpty()) {
|
||||||
|
// val fake1 = AirPodsMax(
|
||||||
|
// identifier = UUID.fromString("63436171-5433-4506-8bf5-faf6c35ffa8a"),
|
||||||
|
// scanResult = scanResults.first(),
|
||||||
|
// proximityMessage = ProximityPairing.Decoder().decode(
|
||||||
|
// message = ContinuityProtocol.Message(
|
||||||
|
// type = 0x07.toUByte(),
|
||||||
|
// length = 25,
|
||||||
|
// data = arrayOf<Byte>(1, 10, 32, 98, 4, -128, 1, 15, 64, 13, 112, 80, 22, -14, 64, -125, 22, -65, 16, 22, 52, -101, 116, -124, -24).toByteArray().toUByteArray()
|
||||||
|
// )
|
||||||
|
// )!!
|
||||||
|
// )
|
||||||
|
// val fake2 = BeatsFlex(
|
||||||
|
// identifier = UUID.fromString("e563098d-ea24-4136-a169-5a59344317c3"),
|
||||||
|
// scanResult = scanResults.first(),
|
||||||
|
// proximityMessage = ProximityPairing.Decoder().decode(
|
||||||
|
// message = ContinuityProtocol.Message(
|
||||||
|
// type = 0x07.toUByte(),
|
||||||
|
// length = 25,
|
||||||
|
// data = arrayOf<Byte>(1, 16, 32, 10, -12, -113, 0, 1, 0, -60, 113, -97, -100, -17, -94, -29, -70, 102, -2, 29, 69, -97, -55, 47, -96).toByteArray().toUByteArray()
|
||||||
|
// )
|
||||||
|
// )!!
|
||||||
|
// )
|
||||||
|
// pods.plus(fake1).plus(fake2)
|
||||||
|
// } else {
|
||||||
|
// pods
|
||||||
|
// }
|
||||||
|
|
||||||
|
pods
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ import eu.darken.capod.common.notifications.PendingIntentCompat
|
|||||||
import eu.darken.capod.main.ui.MainActivity
|
import eu.darken.capod.main.ui.MainActivity
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.getBatteryCase
|
import eu.darken.capod.pods.core.getBatteryLevelCase
|
||||||
import eu.darken.capod.pods.core.getBatteryLeftPod
|
import eu.darken.capod.pods.core.getBatteryLevelLeftPod
|
||||||
import eu.darken.capod.pods.core.getBatteryRightPod
|
import eu.darken.capod.pods.core.getBatteryLevelRightPod
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
@@ -70,11 +70,11 @@ class MonitorNotifications @Inject constructor(
|
|||||||
val infoText = when (device) {
|
val infoText = when (device) {
|
||||||
is DualApplePods -> {
|
is DualApplePods -> {
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
sb.append("L: ${device.getBatteryLeftPod(context)}")
|
sb.append("L: ${device.getBatteryLevelLeftPod(context)}")
|
||||||
sb.append(" | ")
|
sb.append(" | ")
|
||||||
sb.append("C: ${device.getBatteryCase(context)}")
|
sb.append("C: ${device.getBatteryLevelCase(context)}")
|
||||||
sb.append(" | ")
|
sb.append(" | ")
|
||||||
sb.append("R: ${device.getBatteryRightPod(context)}")
|
sb.append("R: ${device.getBatteryLevelRightPod(context)}")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
"Unknown device"
|
"Unknown device"
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
interface DualPods : PodDevice {
|
interface DualPodDevice : PodDevice {
|
||||||
|
|
||||||
val microPhonePod: Pod
|
|
||||||
|
|
||||||
enum class Pod {
|
enum class Pod {
|
||||||
LEFT,
|
LEFT,
|
||||||
@@ -4,14 +4,14 @@ import android.content.Context
|
|||||||
import eu.darken.capod.R
|
import eu.darken.capod.R
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
fun DualPods.getBatteryLeftPod(context: Context): String =
|
fun DualPodDevice.getBatteryLevelLeftPod(context: Context): String =
|
||||||
batteryLeftPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryLeftPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
fun DualPods.getBatteryRightPod(context: Context): String =
|
fun DualPodDevice.getBatteryLevelRightPod(context: Context): String =
|
||||||
batteryRightPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryRightPodPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|
||||||
fun DualPods.getBatteryCase(context: Context): String =
|
fun DualPodDevice.getBatteryLevelCase(context: Context): String =
|
||||||
batteryCasePercent?.let { "${(it * 100).roundToInt()}%" }
|
batteryCasePercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
?: context.getString(R.string.general_value_not_available_label)
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
|
|||||||
@@ -12,13 +12,18 @@ interface PodDevice {
|
|||||||
|
|
||||||
val identifier: UUID
|
val identifier: UUID
|
||||||
|
|
||||||
val scanResult: ScanResult
|
|
||||||
|
|
||||||
val lastSeenAt: Instant
|
val lastSeenAt: Instant
|
||||||
|
|
||||||
|
val scanResult: ScanResult
|
||||||
|
|
||||||
val rssi: Int
|
val rssi: Int
|
||||||
get() = scanResult.rssi
|
get() = scanResult.rssi
|
||||||
|
|
||||||
|
val rawData: ByteArray
|
||||||
|
|
||||||
|
val rawDataHex: String
|
||||||
|
get() = rawData.joinToString(separator = " ") { String.format("%02X", it) }
|
||||||
|
|
||||||
fun getSignalQuality(context: Context): String {
|
fun getSignalQuality(context: Context): String {
|
||||||
val percentage = (100 - abs(rssi))
|
val percentage = (100 - abs(rssi))
|
||||||
return "~$percentage%"
|
return "~$percentage%"
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
interface SinglePodDevice : PodDevice {
|
||||||
|
|
||||||
|
val batteryHeadsetPercent: Float?
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package eu.darken.capod.pods.core
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import eu.darken.capod.R
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
fun SinglePodDevice.getBatteryLevelHeadset(context: Context): String =
|
||||||
|
batteryHeadsetPercent?.let { "${(it * 100).roundToInt()}%" }
|
||||||
|
?: context.getString(R.string.general_value_not_available_label)
|
||||||
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple
|
|||||||
|
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
|
import eu.darken.capod.pods.core.apple.protocol.ContinuityProtocol
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
|
|
||||||
interface ApplePods : PodDevice {
|
interface ApplePods : PodDevice {
|
||||||
@@ -10,6 +11,9 @@ interface ApplePods : PodDevice {
|
|||||||
|
|
||||||
val proximityMessage: ProximityPairing.Message
|
val proximityMessage: ProximityPairing.Message
|
||||||
|
|
||||||
|
override val rawData: ByteArray
|
||||||
|
get() = scanResult.scanRecord!!.getManufacturerSpecificData(ContinuityProtocol.APPLE_COMPANY_IDENTIFIER)!!
|
||||||
|
|
||||||
// We start counting at the airpods prefix byte
|
// We start counting at the airpods prefix byte
|
||||||
val rawPrefix: UByte
|
val rawPrefix: UByte
|
||||||
get() = proximityMessage.data[0]
|
get() = proximityMessage.data[0]
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ package eu.darken.capod.pods.core.apple
|
|||||||
|
|
||||||
import eu.darken.capod.common.debug.logging.log
|
import eu.darken.capod.common.debug.logging.log
|
||||||
import eu.darken.capod.common.lowerNibble
|
import eu.darken.capod.common.lowerNibble
|
||||||
|
import eu.darken.capod.pods.core.SinglePodDevice
|
||||||
|
|
||||||
interface BasicSingleApplePods : ApplePods {
|
interface BasicSingleApplePods : ApplePods, SinglePodDevice {
|
||||||
|
|
||||||
val batteryHeadsetPercent: Float?
|
override val batteryHeadsetPercent: Float?
|
||||||
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
||||||
15 -> null
|
15 -> null
|
||||||
else -> if (value > 10) {
|
else -> if (value > 10) {
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import eu.darken.capod.common.debug.logging.log
|
|||||||
import eu.darken.capod.common.isBitSet
|
import eu.darken.capod.common.isBitSet
|
||||||
import eu.darken.capod.common.lowerNibble
|
import eu.darken.capod.common.lowerNibble
|
||||||
import eu.darken.capod.common.upperNibble
|
import eu.darken.capod.common.upperNibble
|
||||||
import eu.darken.capod.pods.core.DualPods
|
import eu.darken.capod.pods.core.DualPodDevice
|
||||||
import eu.darken.capod.pods.core.DualPods.Pod
|
import eu.darken.capod.pods.core.DualPodDevice.Pod
|
||||||
|
|
||||||
interface DualApplePods : ApplePods, DualPods {
|
interface DualApplePods : ApplePods, DualPodDevice {
|
||||||
|
|
||||||
override val microPhonePod: Pod
|
val microPhonePod: Pod
|
||||||
get() = when (rawStatus.isBitSet(5)) {
|
get() = when (rawStatus.isBitSet(5)) {
|
||||||
true -> Pod.LEFT
|
true -> Pod.LEFT
|
||||||
false -> Pod.RIGHT
|
false -> Pod.RIGHT
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package eu.darken.capod.pods.core.apple.airpods
|
|||||||
|
|
||||||
import android.bluetooth.le.ScanResult
|
import android.bluetooth.le.ScanResult
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import eu.darken.capod.R
|
||||||
import eu.darken.capod.common.debug.logging.logTag
|
import eu.darken.capod.common.debug.logging.logTag
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
import eu.darken.capod.pods.core.apple.protocol.ProximityPairing
|
||||||
@@ -19,5 +20,8 @@ data class AirPodsPro constructor(
|
|||||||
return "AirPods Pro"
|
return "AirPods Pro"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override val iconRes: Int
|
||||||
|
get() = R.drawable.ic_device_airpods_gen2
|
||||||
|
|
||||||
override val tag: String = logTag("Pod", "Apple", "AirPods", "Pro")
|
override val tag: String = logTag("Pod", "Apple", "AirPods", "Pro")
|
||||||
}
|
}
|
||||||
@@ -33,31 +33,13 @@
|
|||||||
android:layout_marginStart="4dp"
|
android:layout_marginStart="4dp"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
app:layout_constraintBottom_toTopOf="@id/barrier_top"
|
app:layout_constraintBottom_toTopOf="@+id/last_seen"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reception"
|
app:layout_constraintEnd_toStartOf="@id/reception"
|
||||||
app:layout_constraintStart_toEndOf="@id/device_icon"
|
app:layout_constraintStart_toEndOf="@id/device_icon"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintVertical_bias="0.0"
|
|
||||||
tools:text="Apple AirPods Pro" />
|
tools:text="Apple AirPods Pro" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
android:id="@+id/reception"
|
|
||||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:gravity="center"
|
|
||||||
app:drawableRightCompat="@drawable/ic_baseline_signal_cellular_alt_24"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toEndOf="@id/name"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
tools:text="Yours (RSSI -61)" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/last_seen"
|
android:id="@+id/last_seen"
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
@@ -70,6 +52,36 @@
|
|||||||
app:layout_constraintTop_toBottomOf="@id/name"
|
app:layout_constraintTop_toBottomOf="@id/name"
|
||||||
tools:text="3s ago" />
|
tools:text="3s ago" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/reception"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reception_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/name"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/name"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reception_icon"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/reception"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/reception"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/reception"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_signal_cellular_alt_24"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Barrier
|
<androidx.constraintlayout.widget.Barrier
|
||||||
android:id="@+id/barrier_top"
|
android:id="@+id/barrier_top"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -137,12 +149,9 @@
|
|||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="16dp"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
app:drawableLeftCompat="@drawable/ic_baseline_apps_24"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<?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"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
tools:context=".main.ui.MainActivity">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/card_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/device_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/name"
|
||||||
|
app:srcCompat="@drawable/ic_device_generic_headphones" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/last_seen"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reception"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/device_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Power Beats 3" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/last_seen"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/barrier_top"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/name"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/name"
|
||||||
|
tools:text="3s ago" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/reception"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reception_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/name"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/name"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reception_icon"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/reception"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/reception"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/reception"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_signal_cellular_alt_24"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier_top"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="top"
|
||||||
|
app:constraint_referenced_ids="headphones"
|
||||||
|
tools:layout_editor_absoluteY="60dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/headphones"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
app:drawableTopCompat="@drawable/ic_device_generic_headphones"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_goneMarginBottom="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/barrier_top"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
tools:text="Headphones\n50%\nCharging" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier_bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="headphones" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/barrier_bottom"
|
||||||
|
tools:text="Music Active, Case Closed" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?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"
|
||||||
|
android:layout_marginVertical="8dp"
|
||||||
|
tools:context=".main.ui.MainActivity">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/card_content"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/device_icon"
|
||||||
|
android:layout_width="24dp"
|
||||||
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/name"
|
||||||
|
app:srcCompat="@drawable/ic_device_generic_headphones" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/name"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/last_seen"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reception"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/device_icon"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Apple AirPods Max" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/last_seen"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/barrier_top"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/name"
|
||||||
|
app:layout_constraintStart_toStartOf="@id/name"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/name"
|
||||||
|
tools:text="3s ago" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/reception"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/reception_icon"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/name"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/name"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reception_icon"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/reception"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/reception"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/reception"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_signal_cellular_alt_24"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier_top"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="top"
|
||||||
|
app:constraint_referenced_ids="headphones"
|
||||||
|
tools:layout_editor_absoluteY="60dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/headphones"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
app:drawableTopCompat="@drawable/ic_device_generic_headphones"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/barrier_bottom"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_goneMarginBottom="16dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/barrier_top"
|
||||||
|
app:layout_constraintVertical_bias="0.0"
|
||||||
|
tools:text="Headphones\n50%\nCharging" />
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier_bottom"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="bottom"
|
||||||
|
app:constraint_referenced_ids="headphones" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
android:id="@+id/status"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/barrier_bottom"
|
||||||
|
tools:text="Music Active, Case Closed" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
@@ -18,23 +18,22 @@
|
|||||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:gravity="center_vertical"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/last_seen"
|
||||||
app:layout_constraintEnd_toStartOf="@id/reception"
|
app:layout_constraintEnd_toStartOf="@id/reception"
|
||||||
app:layout_constraintHorizontal_bias="0.49"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:text="Unknown Device" />
|
tools:text="Apple AirPods Max" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/last_seen"
|
android:id="@+id/last_seen"
|
||||||
style="@style/TextAppearance.MaterialComponents.Caption"
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="4dp"
|
android:layout_marginBottom="8dp"
|
||||||
app:layout_constraintBottom_toTopOf="@id/rawdata_label"
|
app:layout_constraintBottom_toTopOf="@id/rawdata_label"
|
||||||
app:layout_constraintEnd_toEndOf="@id/name"
|
app:layout_constraintEnd_toEndOf="@id/name"
|
||||||
app:layout_constraintStart_toStartOf="@id/name"
|
app:layout_constraintStart_toStartOf="@id/name"
|
||||||
@@ -43,17 +42,33 @@
|
|||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/reception"
|
android:id="@+id/reception"
|
||||||
style="@style/TextAppearance.MaterialComponents.Body2"
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="16dp"
|
android:gravity="center"
|
||||||
android:layout_marginEnd="16dp"
|
app:layout_constraintBottom_toBottomOf="@id/name"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@id/reception_icon"
|
||||||
app:layout_constraintStart_toEndOf="@id/name"
|
app:layout_constraintStart_toEndOf="@id/name"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="@+id/name"
|
||||||
tools:text="RSSI -100 dbM" />
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/reception_icon"
|
||||||
|
style="@style/TextAppearance.MaterialComponents.Caption"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@+id/reception"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/reception"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/reception"
|
||||||
|
app:srcCompat="@drawable/ic_baseline_signal_cellular_alt_24"
|
||||||
|
tools:text="Yours (RSSI -61)" />
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/rawdata_label"
|
android:id="@+id/rawdata_label"
|
||||||
@@ -72,7 +87,7 @@
|
|||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
<com.google.android.material.textview.MaterialTextView
|
||||||
android:id="@+id/rawdata"
|
android:id="@+id/rawdata"
|
||||||
style="@style/TextAppearance.MaterialComponents.Body1"
|
style="@style/TextAppearance.MaterialComponents.Body2"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginHorizontal="16dp"
|
android:layout_marginHorizontal="16dp"
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<string name="pods_connection_state_call_label">Call</string>
|
<string name="pods_connection_state_call_label">Call</string>
|
||||||
<string name="pods_connection_state_ringing_label">Ringing</string>
|
<string name="pods_connection_state_ringing_label">Ringing</string>
|
||||||
<string name="pods_connection_state_hanging_up_label">Hanging up</string>
|
<string name="pods_connection_state_hanging_up_label">Hanging up</string>
|
||||||
<string name="pods_status_x_label">Status %s</string>
|
<string name="pods_status_x_label">Status: %s</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="settings_label">Settings</string>
|
<string name="settings_label">Settings</string>
|
||||||
@@ -82,4 +82,7 @@
|
|||||||
<string name="settings_debug_autoreports_label">Automatic bug reports</string>
|
<string name="settings_debug_autoreports_label">Automatic bug reports</string>
|
||||||
<string name="settings_debug_autoreports_description">Automatically reports issues, e.g. details on an app crash so I can figure out how to fix it.</string>
|
<string name="settings_debug_autoreports_description">Automatically reports issues, e.g. details on an app crash so I can figure out how to fix it.</string>
|
||||||
<string name="device_unknown_label">Unknown device</string>
|
<string name="device_unknown_label">Unknown device</string>
|
||||||
|
<string name="pods_single_headphones_label">Headphones</string>
|
||||||
|
<string name="settings_debug_mode_label">Debug mode</string>
|
||||||
|
<string name="settings_debug_mode_description">Show additional information to troubleshoot issues.</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -5,10 +5,16 @@
|
|||||||
|
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:icon="@drawable/ic_spider_thread_onsurface"
|
android:icon="@drawable/ic_spider_thread_onsurface"
|
||||||
android:key="core.bugtracking.enabled"
|
android:key="debug.bugreport.automatic.enabled"
|
||||||
android:summary="@string/settings_debug_autoreports_description"
|
android:summary="@string/settings_debug_autoreports_description"
|
||||||
android:title="@string/settings_debug_autoreports_label" />
|
android:title="@string/settings_debug_autoreports_label" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:icon="@drawable/ic_baseline_bug_report_24"
|
||||||
|
android:key="debug.mode.enabled"
|
||||||
|
android:summary="@string/settings_debug_mode_description"
|
||||||
|
android:title="@string/settings_debug_mode_label" />
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core.apple
|
package eu.darken.capod.pods.core.apple
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.DualPods
|
import eu.darken.capod.pods.core.DualPodDevice
|
||||||
import eu.darken.capod.pods.core.PodDevice
|
import eu.darken.capod.pods.core.PodDevice
|
||||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
|
import eu.darken.capod.pods.core.apple.airpods.AirPodsGen1
|
||||||
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
import eu.darken.capod.pods.core.apple.airpods.AirPodsPro
|
||||||
@@ -16,12 +16,12 @@ class AirPodsFactoryTest : BaseAirPodsTest() {
|
|||||||
create<DualApplePods>("07 19 01 0E 20 >2B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualApplePods>("07 19 01 0E 20 >2B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00101011
|
// 00101011
|
||||||
// --^-----
|
// --^-----
|
||||||
microPhonePod shouldBe DualPods.Pod.LEFT
|
microPhonePod shouldBe DualPodDevice.Pod.LEFT
|
||||||
}
|
}
|
||||||
create<DualApplePods>("07 19 01 0E 20 >0B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
create<DualApplePods>("07 19 01 0E 20 >0B< AA B5 31 00 00 E0 0C A7 8A 60 4B D3 7D F4 60 4F 2C 73 E9 A7 F4") {
|
||||||
// 00001011
|
// 00001011
|
||||||
// --^-----
|
// --^-----
|
||||||
microPhonePod shouldBe DualPods.Pod.RIGHT
|
microPhonePod shouldBe DualPodDevice.Pod.RIGHT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core.apple.airpods
|
package eu.darken.capod.pods.core.apple.airpods
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.DualPods
|
import eu.darken.capod.pods.core.DualPodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
@@ -22,7 +22,7 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
rawDeviceColor shouldBe 0x00.toUByte()
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
rawSuffix shouldBe 0x00.toUByte()
|
rawSuffix shouldBe 0x00.toUByte()
|
||||||
|
|
||||||
microPhonePod shouldBe DualPods.Pod.RIGHT
|
microPhonePod shouldBe DualPodDevice.Pod.RIGHT
|
||||||
|
|
||||||
batteryLeftPodPercent shouldBe 1.0f
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
batteryRightPodPercent shouldBe 1.0f
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
@@ -39,7 +39,7 @@ class AirPodsProTest : BaseAirPodsTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test AirPods from my downstairs neighbour`() = runBlockingTest {
|
fun `test AirPods from my downstairs neighbour`() = runBlockingTest {
|
||||||
create<AirPodsPro>("07 19 01 0E 20 00 F3 8F 02 00 04 79 C6 3F F9 C3 15 D9 11 A1 3C B1 58 66 B9 8B 67") {
|
create<AirPodsPro>("07 19 01 0E 20 00 F3 8F 02 00 04 79 C6 3F F9 C3 15 D9 11 A1 3C B1 58 66 B9 8B 67") {
|
||||||
microPhonePod shouldBe DualPods.Pod.RIGHT
|
microPhonePod shouldBe DualPodDevice.Pod.RIGHT
|
||||||
|
|
||||||
batteryLeftPodPercent shouldBe null
|
batteryLeftPodPercent shouldBe null
|
||||||
batteryRightPodPercent shouldBe 0.3f
|
batteryRightPodPercent shouldBe 0.3f
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package eu.darken.capod.pods.core.apple.beats
|
package eu.darken.capod.pods.core.apple.beats
|
||||||
|
|
||||||
import eu.darken.capod.pods.core.DualPods
|
import eu.darken.capod.pods.core.DualPodDevice
|
||||||
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
import eu.darken.capod.pods.core.apple.BaseAirPodsTest
|
||||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
@@ -22,7 +22,7 @@ class PowerBeatsProTest : BaseAirPodsTest() {
|
|||||||
rawDeviceColor shouldBe 0x00.toUByte()
|
rawDeviceColor shouldBe 0x00.toUByte()
|
||||||
rawSuffix shouldBe 0x00.toUByte()
|
rawSuffix shouldBe 0x00.toUByte()
|
||||||
|
|
||||||
microPhonePod shouldBe DualPods.Pod.RIGHT
|
microPhonePod shouldBe DualPodDevice.Pod.RIGHT
|
||||||
|
|
||||||
batteryLeftPodPercent shouldBe 1.0f
|
batteryLeftPodPercent shouldBe 1.0f
|
||||||
batteryRightPodPercent shouldBe 1.0f
|
batteryRightPodPercent shouldBe 1.0f
|
||||||
|
|||||||
Reference in New Issue
Block a user