diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b2de15ee..5dde0c43 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -130,6 +130,7 @@ android { //noinspection WrongGradleMethod tasks.withType { useJUnitPlatform() + maxHeapSize = "4g" } } } diff --git a/app/src/debug/java/eu/darken/capod/screenshots/ScreenshotContent.kt b/app/src/debug/java/eu/darken/capod/screenshots/ScreenshotContent.kt index ff5e3d76..efbae5c3 100644 --- a/app/src/debug/java/eu/darken/capod/screenshots/ScreenshotContent.kt +++ b/app/src/debug/java/eu/darken/capod/screenshots/ScreenshotContent.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import eu.darken.capod.common.bluetooth.BluetoothDevice2 import eu.darken.capod.common.compose.PreviewWrapper import eu.darken.capod.common.compose.preview.MOCK_NOW import eu.darken.capod.common.compose.preview.MockPodDataProvider @@ -37,16 +38,25 @@ import eu.darken.capod.reaction.ui.popup.PopUpContent as PopUpCard internal const val DS = "spec:width=1080px,height=2400px,dpi=428" @Composable -internal fun DashboardContent() = PreviewWrapper { +internal fun DashboardContent(showAap: Boolean = false) = PreviewWrapper { + val devices = if (showAap) { + listOf( + MockPodDataProvider.dualPodMonitoredWithAap(), + MockPodDataProvider.singlePodMonitoredWithAap(), + MockPodDataProvider.unknownMonitored(), + ) + } else { + listOf( + MockPodDataProvider.dualPodMonitoredMixed(), + MockPodDataProvider.singlePodMonitored(), + MockPodDataProvider.unknownMonitored(), + ) + } OverviewScreen( state = OverviewViewModel.State( now = MOCK_NOW, permissions = emptySet(), - devices = listOf( - MockPodDataProvider.dualPodMonitoredMixed(), - MockPodDataProvider.singlePodMonitored(), - MockPodDataProvider.unknownMonitored(), - ), + devices = devices, isDebugMode = false, isBluetoothEnabled = true, profiles = listOf( @@ -83,19 +93,38 @@ internal fun DeviceProfilesContent() = PreviewWrapper { @Composable internal fun AddProfileContent() = PreviewWrapper { + val pairedAirPods = BluetoothDevice2( + address = "AA:BB:CC:DD:EE:FF", + name = "AirPods Pro", + seenFirstAt = MOCK_NOW, + ) + val bondedItems = listOf( + DeviceProfileCreationViewModel.BondedDeviceItem( + device = pairedAirPods, + claimedByProfile = null, + ), + DeviceProfileCreationViewModel.BondedDeviceItem( + device = BluetoothDevice2( + address = "11:22:33:44:55:66", + name = "Living Room TV", + seenFirstAt = MOCK_NOW, + ), + claimedByProfile = null, + ), + ) DeviceProfileCreationScreen( state = DeviceProfileCreationViewModel.State( isEditMode = false, - name = "", + name = "My AirPods Pro", nameError = null, - selectedModel = null, + selectedModel = PodModel.AIRPODS_PRO2, availableModels = PodModel.entries.filter { it != PodModel.UNKNOWN }, identityKey = null, encryptionKey = null, - selectedDevice = null, - bondedDeviceItems = emptyList(), + selectedDevice = pairedAirPods, + bondedDeviceItems = bondedItems, minimumSignalQuality = 0.15f, - canSave = false, + canSave = true, ), onBack = {}, onSave = {}, @@ -212,7 +241,7 @@ private fun PreviewDashboardLight() = DashboardContent() @Preview(name = "2 - Dashboard Dark", locale = "en", device = DS, uiMode = Configuration.UI_MODE_NIGHT_YES, showSystemUi = true) @Composable -private fun PreviewDashboardDark() = DashboardContent() +private fun PreviewDashboardDark() = DashboardContent(showAap = true) @Preview(name = "3 - Case Pop-up", device = DS, showSystemUi = true) @Composable diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt index 4aa9c394..1a01b65a 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothDevice2.kt @@ -4,12 +4,8 @@ import android.bluetooth.BluetoothDevice import java.time.Instant data class BluetoothDevice2( - internal val internal: BluetoothDevice, + val address: BluetoothAddress, + val name: String?, val seenFirstAt: Instant, -) { - val address: BluetoothAddress - get() = internal.address - - val name: String? - get() = internal.name -} + internal val internal: BluetoothDevice? = null, +) diff --git a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt index 4828aed6..42839740 100644 --- a/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt +++ b/app/src/main/java/eu/darken/capod/common/bluetooth/BluetoothManager2.kt @@ -255,6 +255,8 @@ class BluetoothManager2 @Inject constructor( } .map { device -> BluetoothDevice2( + address = device.address, + name = device.name, internal = device, seenFirstAt = seenDevicesLock.withLock { seenDevicesCache[device.address] ?: run { @@ -304,6 +306,8 @@ class BluetoothManager2 @Inject constructor( val wrappedDevices = rawDevices.map { device -> BluetoothDevice2( + address = device.address, + name = device.name, internal = device, seenFirstAt = seenDevicesLock.withLock { seenDevicesCache[device.address] ?: run { @@ -408,9 +412,10 @@ class BluetoothManager2 @Inject constructor( @android.annotation.SuppressLint("MissingPermission") fun setDeviceAlias(device: BluetoothDevice2, alias: String): Boolean { return try { + val target = device.internal ?: return false val method = BluetoothDevice::class.java.getDeclaredMethod("setAlias", String::class.java) .apply { isAccessible = true } - val result = method.invoke(device.internal, alias) as? Boolean ?: false + val result = method.invoke(target, alias) as? Boolean ?: false log(TAG) { "setDeviceAlias(${device.address}, $alias) -> $result" } result } catch (e: Exception) { @@ -424,11 +429,12 @@ class BluetoothManager2 @Inject constructor( try { log(TAG) { "Nudging Android connection to $device" } + val target = device.internal ?: return@map false val connectMethod = BluetoothHeadset::class.java.getDeclaredMethod( "connect", BluetoothDevice::class.java ).apply { isAccessible = true } - val accepted = connectMethod.invoke(bluetoothProfile.proxy, device.internal) as? Boolean ?: false + val accepted = connectMethod.invoke(bluetoothProfile.proxy, target) as? Boolean ?: false log(TAG) { "Nudged connection to $device — accepted=$accepted" } accepted } catch (e: Exception) { diff --git a/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt b/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt index aba80453..f2b5025f 100644 --- a/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt +++ b/app/src/main/java/eu/darken/capod/common/compose/preview/MockPodDataProvider.kt @@ -180,18 +180,21 @@ object MockPodDataProvider { fun dualPodMonitoredMixed(): PodDevice = PodDevice( profileId = "preview-dual-mixed", + label = "My AirPods Pro", ble = airPodsProMixed(), aap = null, ) fun dualPodMonitoredWithKeys(): PodDevice = PodDevice( profileId = "preview-dual-keys", + label = "My AirPods Pro", ble = airPodsProWithKeys(), aap = null, ) fun dualPodMonitoredWithAap(): PodDevice = PodDevice( profileId = "preview-dual-aap", + label = "My AirPods Pro", ble = airPodsProWithKeys(), aap = AapPodState( connectionState = AapPodState.ConnectionState.READY, @@ -227,12 +230,14 @@ object MockPodDataProvider { fun singlePodMonitored(): PodDevice = PodDevice( profileId = "preview-single", + label = "AirPods Max", ble = airPodsMax(), aap = null, ) fun singlePodMonitoredWithAap(): PodDevice = PodDevice( profileId = "preview-single-aap", + label = "AirPods Max", ble = airPodsMaxCharging(), aap = AapPodState( connectionState = AapPodState.ConnectionState.READY, diff --git a/app/src/main/java/eu/darken/capod/monitor/core/aap/AapAutoConnect.kt b/app/src/main/java/eu/darken/capod/monitor/core/aap/AapAutoConnect.kt index ee5fa6ec..be203176 100644 --- a/app/src/main/java/eu/darken/capod/monitor/core/aap/AapAutoConnect.kt +++ b/app/src/main/java/eu/darken/capod/monitor/core/aap/AapAutoConnect.kt @@ -76,7 +76,7 @@ class AapAutoConnect @Inject constructor( log(TAG) { "AAP connecting to $address (${profile.label})" } try { - aapManager.connect(address, bonded.internal, profile.model) + aapManager.connect(address, bonded.internal!!, profile.model) log(TAG) { "AAP connected to $address" } } catch (e: Exception) { log(TAG, WARN) { "AAP initial connect failed for $address: ${e.message}" } @@ -99,7 +99,7 @@ class AapAutoConnect @Inject constructor( try { log(TAG) { "AAP initial retry ${attempt + 1} for $address after ${delayMs}ms" } - aapManager.connect(address, bonded.internal, profile.model) + aapManager.connect(address, bonded.internal!!, profile.model) log(TAG) { "AAP connected to $address on retry ${attempt + 1}" } break } catch (retryException: Exception) { @@ -159,7 +159,7 @@ class AapAutoConnect @Inject constructor( try { log(TAG) { "AAP reconnect attempt ${attempt + 1} for $address in ${delayMs}ms" } - aapManager.connect(address, bonded.internal, profile.model) + aapManager.connect(address, bonded.internal!!, profile.model) log(TAG) { "AAP reconnected to $address" } break } catch (e: Exception) { @@ -211,7 +211,7 @@ class AapAutoConnect @Inject constructor( val bonded = bluetoothManager.bondedDevices().first() .firstOrNull { it.address == address } if (bonded != null) { - aapManager.connect(address, bonded.internal, detectedModel) + aapManager.connect(address, bonded.internal!!, detectedModel) log(TAG) { "AAP reconnected $address with corrected model $detectedModel" } } } catch (e: Exception) { diff --git a/app/src/screenshotTest/kotlin/eu/darken/capod/screenshots/PlayStoreScreenshots.kt b/app/src/screenshotTest/kotlin/eu/darken/capod/screenshots/PlayStoreScreenshots.kt index ede3012e..6fccda40 100644 --- a/app/src/screenshotTest/kotlin/eu/darken/capod/screenshots/PlayStoreScreenshots.kt +++ b/app/src/screenshotTest/kotlin/eu/darken/capod/screenshots/PlayStoreScreenshots.kt @@ -12,7 +12,7 @@ fun DashboardLight() = DashboardContent() @PreviewTest @PlayStoreLocalesDark @Composable -fun DashboardDark() = DashboardContent() +fun DashboardDark() = DashboardContent(showAap = true) @PreviewTest @PlayStoreLocales diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/ar/images/phoneScreenshots/1_dashboard_light.png index 0b456072..767953af 100644 Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/ar/images/phoneScreenshots/2_dashboard_dark.png index ceedc700..86281e49 100644 Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/ar/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/ar/images/phoneScreenshots/6_add_profile.png index d546eba1..57489d55 100644 Binary files a/fastlane/metadata/android/ar/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/ar/images/phoneScreenshots/6_add_profile.png differ diff --git a/fastlane/metadata/android/de-DE/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/de-DE/images/phoneScreenshots/1_dashboard_light.png index 40bff726..d34e9e59 100644 Binary files a/fastlane/metadata/android/de-DE/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/de-DE/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/de-DE/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/de-DE/images/phoneScreenshots/2_dashboard_dark.png index 85639fc1..c4c6dd9f 100644 Binary files a/fastlane/metadata/android/de-DE/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/de-DE/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/de-DE/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/de-DE/images/phoneScreenshots/6_add_profile.png index f34042f4..a054372f 100644 Binary files a/fastlane/metadata/android/de-DE/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/de-DE/images/phoneScreenshots/6_add_profile.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_dashboard_light.png index 8c18d3f0..e2a683c0 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_dashboard_dark.png index 381a5422..ed340991 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_add_profile.png index 82b58ccc..56f00253 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_add_profile.png differ diff --git a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/1_dashboard_light.png index 8c910b9a..912219b5 100644 Binary files a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/2_dashboard_dark.png index 8612587e..ee870afd 100644 Binary files a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/6_add_profile.png index 4b3773ee..cd6d947c 100644 Binary files a/fastlane/metadata/android/ja-JP/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/ja-JP/images/phoneScreenshots/6_add_profile.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1_dashboard_light.png index 8c18d3f0..e2a683c0 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2_dashboard_dark.png index 381a5422..ed340991 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6_add_profile.png index 82b58ccc..56f00253 100644 Binary files a/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/pt-BR/images/phoneScreenshots/6_add_profile.png differ diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_dashboard_light.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_dashboard_light.png index 1ba56377..f39aa9ae 100644 Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_dashboard_light.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/1_dashboard_light.png differ diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_dashboard_dark.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_dashboard_dark.png index 85114190..513ed3a2 100644 Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_dashboard_dark.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/2_dashboard_dark.png differ diff --git a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6_add_profile.png b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6_add_profile.png index 82b58ccc..83d7e64f 100644 Binary files a/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6_add_profile.png and b/fastlane/metadata/android/zh-CN/images/phoneScreenshots/6_add_profile.png differ