From 0f924ecb8284fe166a8f82564a30c3e8635562ec Mon Sep 17 00:00:00 2001 From: darken Date: Sat, 29 Jan 2022 18:00:13 +0100 Subject: [PATCH] Track first time a device is seen and display it. --- .../overview/cards/pods/DualApplePodsCardVH.kt | 6 +++++- .../java/eu/darken/capod/pods/core/PodDevice.kt | 2 ++ .../capod/pods/core/PodDeviceExtensions.kt | 9 +++++++++ .../capod/pods/core/apple/ApplePodsFactory.kt | 3 +++ .../capod/pods/core/apple/UnknownAppleDevice.kt | 1 + .../capod/pods/core/apple/airpods/AirPodsGen1.kt | 2 ++ .../capod/pods/core/apple/airpods/AirPodsGen2.kt | 2 ++ .../capod/pods/core/apple/airpods/AirPodsGen3.kt | 2 ++ .../capod/pods/core/apple/airpods/AirPodsMax.kt | 2 ++ .../capod/pods/core/apple/airpods/AirPodsPro.kt | 2 ++ .../capod/pods/core/apple/beats/BeatsFlex.kt | 2 ++ .../capod/pods/core/apple/beats/BeatsSolo3.kt | 2 ++ .../capod/pods/core/apple/beats/BeatsStudio3.kt | 2 ++ .../darken/capod/pods/core/apple/beats/BeatsX.kt | 2 ++ .../capod/pods/core/apple/beats/PowerBeats3.kt | 2 ++ .../capod/pods/core/apple/beats/PowerBeatsPro.kt | 2 ++ .../res/layout/overview_pods_apple_dual_item.xml | 16 ++++++++++++++-- app/src/main/res/values/strings.xml | 2 ++ 18 files changed, 58 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/pods/DualApplePodsCardVH.kt b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/pods/DualApplePodsCardVH.kt index d28826c0..96409253 100644 --- a/app/src/main/java/eu/darken/capod/main/ui/overview/cards/pods/DualApplePodsCardVH.kt +++ b/app/src/main/java/eu/darken/capod/main/ui/overview/cards/pods/DualApplePodsCardVH.kt @@ -2,6 +2,7 @@ package eu.darken.capod.main.ui.overview.cards.pods import android.graphics.Typeface import android.view.ViewGroup +import androidx.core.view.isGone import androidx.core.view.isInvisible import eu.darken.capod.R import eu.darken.capod.common.lists.binding @@ -10,6 +11,7 @@ import eu.darken.capod.pods.core.* 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.LidState +import java.time.Duration import java.time.Instant class DualApplePodsCardVH(parent: ViewGroup) : @@ -36,7 +38,9 @@ class DualApplePodsCardVH(parent: ViewGroup) : } deviceIcon.setImageResource(device.iconRes) - lastSeen.text = device.lastSeenFormatted(item.now) + lastSeen.text = context.getString(R.string.last_seen_x, device.lastSeenFormatted(item.now)) + firstSeen.text = context.getString(R.string.first_seen_x, device.firstSeenFormatted(item.now)) + firstSeen.isGone = Duration.between(device.firstSeenAt, device.lastSeenAt).toMinutes() < 1 reception.text = device.getSignalQuality(context).let { if (item.isMainPod) "$it\n(${getString(R.string.pods_yours)})" else it diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt index 394aab8b..15a033af 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodDevice.kt @@ -22,6 +22,8 @@ interface PodDevice { val lastSeenAt: Instant + val firstSeenAt: Instant + val scanResult: BleScanResult val rssi: Int diff --git a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt index bdcf0186..bc69a792 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/PodDeviceExtensions.kt @@ -51,4 +51,13 @@ fun PodDevice.lastSeenFormatted(now: Instant): String { RelativeDateTimeFormatter.Direction.LAST, RelativeDateTimeFormatter.RelativeUnit.SECONDS ) +} + +fun PodDevice.firstSeenFormatted(now: Instant): String { + val duration = Duration.between(firstSeenAt, now) + return lastSeenFormatter.format( + duration.toMinutes().toDouble(), + RelativeDateTimeFormatter.Direction.LAST, + RelativeDateTimeFormatter.RelativeUnit.MINUTES + ) } \ No newline at end of file diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt index 6009220d..43c23d94 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/ApplePodsFactory.kt @@ -9,6 +9,7 @@ import eu.darken.capod.common.upperNibble import eu.darken.capod.pods.core.PodDevice import eu.darken.capod.pods.core.apple.protocol.ProximityPairing import java.time.Duration +import java.time.Instant abstract class ApplePodsFactory(private val tag: String) { @@ -33,6 +34,7 @@ abstract class ApplePodsFactory(private val tag: String) { data class KnownDevice( val id: PodDevice.Id, + val firstSeenAt: Instant, val history: List ) { val lastMessage: ProximityPairing.Message @@ -111,6 +113,7 @@ abstract class ApplePodsFactory(private val tag: String) { log(tag) { "searchHistory1: Creating new history for $device" } KnownDevice( id = device.identifier, + firstSeenAt = device.firstSeenAt, history = listOf(device) ) } diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt index c56b218f..584fcc3c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/UnknownAppleDevice.kt @@ -12,6 +12,7 @@ import javax.inject.Inject data class UnknownAppleDevice( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = 0f, diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt index 0b1347f2..d5e04357 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen1.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class AirPodsGen1 constructor( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -47,6 +48,7 @@ data class AirPodsGen1 constructor( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, cachedBatteryPercentage = result.getLatestCaseBattery(), rssiAverage = result.averageRssi(basic.rssi), diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt index 7930518d..ebeb7d10 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen2.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class AirPodsGen2 constructor( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -47,6 +48,7 @@ data class AirPodsGen2 constructor( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, cachedBatteryPercentage = result.getLatestCaseBattery(), rssiAverage = result.averageRssi(basic.rssi), diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt index d1311642..4c4adb4c 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsGen3.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class AirPodsGen3 constructor( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -46,6 +47,7 @@ data class AirPodsGen3 constructor( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, cachedBatteryPercentage = result.getLatestCaseBattery(), rssiAverage = result.averageRssi(basic.rssi), diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt index 7c80cd81..ebb3e0be 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsMax.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class AirPodsMax( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -39,6 +40,7 @@ data class AirPodsMax( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt index f806e378..b5e745d5 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/airpods/AirPodsPro.kt @@ -14,6 +14,7 @@ import javax.inject.Inject data class AirPodsPro( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -49,6 +50,7 @@ data class AirPodsPro( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, cachedBatteryPercentage = result.getLatestCaseBattery(), rssiAverage = result.averageRssi(basic.rssi), diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt index 28d8072a..7da40595 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsFlex.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class BeatsFlex( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -39,6 +40,7 @@ data class BeatsFlex( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt index 058dba44..c33eb396 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsSolo3.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class BeatsSolo3( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -38,6 +39,7 @@ data class BeatsSolo3( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt index a44341ec..a37b5bfc 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsStudio3.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class BeatsStudio3( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -36,6 +37,7 @@ data class BeatsStudio3( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt index fda2ab87..bfbb065d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/BeatsX.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class BeatsX( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -39,6 +40,7 @@ data class BeatsX( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt index 05669c7b..09aa307d 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeats3.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class PowerBeats3( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -38,6 +39,7 @@ data class PowerBeats3( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, rssiAverage = result.averageRssi(basic.rssi), ) diff --git a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt index a0a5937f..16a83b3f 100644 --- a/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt +++ b/app/src/main/java/eu/darken/capod/pods/core/apple/beats/PowerBeatsPro.kt @@ -13,6 +13,7 @@ import javax.inject.Inject data class PowerBeatsPro( override val identifier: PodDevice.Id = PodDevice.Id(), override val lastSeenAt: Instant = Instant.now(), + override val firstSeenAt: Instant = Instant.now(), override val scanResult: BleScanResult, override val proximityMessage: ProximityPairing.Message, override val confidence: Float = PodDevice.BASE_CONFIDENCE, @@ -48,6 +49,7 @@ data class PowerBeatsPro( return basic.copy( identifier = result.id, + firstSeenAt = result.firstSeenAt, confidence = result.confidence, cachedBatteryPercentage = result.getLatestCaseBattery(), rssiAverage = result.averageRssi(basic.rssi), diff --git a/app/src/main/res/layout/overview_pods_apple_dual_item.xml b/app/src/main/res/layout/overview_pods_apple_dual_item.xml index f599d028..61da3e12 100644 --- a/app/src/main/res/layout/overview_pods_apple_dual_item.xml +++ b/app/src/main/res/layout/overview_pods_apple_dual_item.xml @@ -45,12 +45,24 @@ style="@style/TextAppearance.MaterialComponents.Caption" android:layout_width="0dp" android:layout_height="wrap_content" + app:layout_constraintBottom_toTopOf="@id/first_seen" + app:layout_constraintEnd_toEndOf="@id/name" + app:layout_constraintStart_toStartOf="@id/name" + app:layout_constraintTop_toBottomOf="@id/name" + app:layout_goneMarginBottom="8dp" + tools:text="Last seen: 3s ago" /> + + + app:layout_constraintTop_toBottomOf="@id/last_seen" + tools:text="First seen: 3s ago" /> Wearing both pods is not required, wearing a single pod is sufficient to trigger reactions. System Alert Window Allow CAPod to draw over other apps to make the feature \"Show PopUp\" possible. + Last seen: %s + First seen: %s \ No newline at end of file