mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-15 02:36:12 -04:00
Improve notifications
This commit is contained in:
@@ -92,7 +92,7 @@ class BleScanner @Inject constructor(
|
||||
// AirPods Max
|
||||
BleScanResult(
|
||||
address = "7E:E5:C7:65:D2:B5",
|
||||
rssi = -57,
|
||||
rssi = -30,
|
||||
generatedAtNanos = SystemClockWrap.elapsedRealtimeNanos + 300,
|
||||
manufacturerSpecificData = mapOf(76 to "07 19 01 0A 20 02 05 80 04 0F 44 A7 60 9B F8 3C FD B1 D8 1C 61 EA 82 60 A3 2C 4E".hexToByteArray())
|
||||
).run { fakeDevices.add(this) }
|
||||
|
||||
@@ -26,6 +26,7 @@ import javax.inject.Inject
|
||||
class MonitorNotifications @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManager,
|
||||
private val notificationViewFactory: NotificationViewFactory
|
||||
) {
|
||||
|
||||
private val builder: NotificationCompat.Builder
|
||||
@@ -45,33 +46,32 @@ class MonitorNotifications @Inject constructor(
|
||||
PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
|
||||
.setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
.setContentIntent(openPi)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setSmallIcon(R.drawable.ic_device_generic_earbuds)
|
||||
.setOngoing(true)
|
||||
.setContentTitle(context.getString(R.string.app_name))
|
||||
builder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID).apply {
|
||||
setChannelId(NOTIFICATION_CHANNEL_ID)
|
||||
setContentIntent(openPi)
|
||||
priority = NotificationCompat.PRIORITY_LOW
|
||||
setSmallIcon(R.drawable.ic_device_generic_earbuds)
|
||||
setOngoing(true)
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
}
|
||||
}
|
||||
|
||||
fun getBuilder(device: PodDevice?): NotificationCompat.Builder {
|
||||
if (device == null) {
|
||||
builder.setContentTitle(context.getString(R.string.pods_none_label_short))
|
||||
builder.setSubText(context.getString(R.string.app_name))
|
||||
builder.setSmallIcon(R.drawable.ic_device_generic_earbuds)
|
||||
return builder
|
||||
return builder.apply {
|
||||
setContentTitle(context.getString(R.string.pods_none_label_short))
|
||||
setSubText(context.getString(R.string.app_name))
|
||||
setSmallIcon(R.drawable.ic_device_generic_earbuds)
|
||||
}
|
||||
}
|
||||
|
||||
builder.setSmallIcon(device.iconRes)
|
||||
builder.setSubText(null)
|
||||
builder.setContentTitle(device.getStatusShort(context))
|
||||
builder.setStyle(
|
||||
NotificationCompat.BigTextStyle()
|
||||
.setBigContentTitle(device.getLabel(context))
|
||||
.bigText(device.getStatusLong(context).joinToString("\n"))
|
||||
)
|
||||
log(TAG, VERBOSE) { "updatingNotification(): $device" }
|
||||
return builder
|
||||
return builder.apply {
|
||||
setSmallIcon(device.iconRes)
|
||||
setSubText(null)
|
||||
setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
||||
setCustomContentView(notificationViewFactory.createContentView(device))
|
||||
log(TAG, VERBOSE) { "updatingNotification(): $device" }
|
||||
}
|
||||
}
|
||||
|
||||
fun getNotification(podDevice: PodDevice?): Notification = getBuilder(podDevice).build()
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package eu.darken.capod.monitor.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.apple.BasicSingleApplePods
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class NotificationViewFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
fun createContentView(device: PodDevice): RemoteViews = when (device) {
|
||||
is DualApplePods -> createDualApplePods(device)
|
||||
is SingleApplePods -> createSingleApplePods(device)
|
||||
is BasicSingleApplePods -> createSingleBasicApplePods(device)
|
||||
else -> createUnknownDevice(device)
|
||||
}
|
||||
|
||||
private fun createDualApplePods(device: DualApplePods): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.monitor_notification_dual_pods_small
|
||||
).apply {
|
||||
device.getBatteryLevelLeftPod(context)
|
||||
.let { if (device.isLeftPodCharging) "$it⚡" else it }
|
||||
.run { setTextViewText(R.id.pod_left, this) }
|
||||
|
||||
device.getBatteryLevelCase(context)
|
||||
.let { if (device.isCaseCharging) "$it⚡" else it }
|
||||
.run { setTextViewText(R.id.pod_case, this) }
|
||||
|
||||
device.getBatteryLevelRightPod(context)
|
||||
.let { if (device.isRightPodCharging) "$it⚡" else it }
|
||||
.run { setTextViewText(R.id.pod_right, this) }
|
||||
}
|
||||
|
||||
private fun createSingleApplePods(device: SingleApplePods): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.monitor_notification_single_pods_small
|
||||
).apply {
|
||||
setTextViewText(R.id.headphones_label, device.getLabel(context))
|
||||
|
||||
device.getBatteryLevelHeadset(context)
|
||||
.let { if (!device.isHeadsetBeingCharged) "$it⚡" else it }
|
||||
.run { setTextViewText(R.id.headphones, this) }
|
||||
}
|
||||
|
||||
private fun createSingleBasicApplePods(device: BasicSingleApplePods): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.monitor_notification_single_pods_small
|
||||
).apply {
|
||||
setTextViewText(R.id.headphones_label, device.getLabel(context))
|
||||
|
||||
device.getBatteryLevelHeadset(context)
|
||||
.run { setTextViewText(R.id.headphones, this) }
|
||||
}
|
||||
|
||||
private fun createUnknownDevice(device: PodDevice): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.monitor_notification_dual_pods_small
|
||||
).apply {
|
||||
setTextViewText(R.id.device, device.getLabel(context))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,10 +31,6 @@ interface PodDevice {
|
||||
|
||||
fun getLabel(context: Context): String
|
||||
|
||||
fun getStatusShort(context: Context): String
|
||||
|
||||
fun getStatusLong(context: Context): List<String>
|
||||
|
||||
@get:DrawableRes
|
||||
val iconRes: Int
|
||||
get() = R.drawable.ic_device_generic_earbuds
|
||||
|
||||
@@ -1,23 +1,11 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.pods.core.HasSinglePod
|
||||
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
||||
|
||||
interface BasicSingleApplePods : ApplePods, HasSinglePod {
|
||||
|
||||
override fun getStatusShort(context: Context): String = context.getString(
|
||||
R.string.pods_single_basic_status_short,
|
||||
getBatteryLevelHeadset(context),
|
||||
)
|
||||
|
||||
override fun getStatusLong(context: Context): List<String> {
|
||||
return listOf("${context.getString(R.string.pods_single_headphones_label)}: ${getBatteryLevelHeadset(context)}")
|
||||
}
|
||||
|
||||
override val batteryHeadsetPercent: Float?
|
||||
get() = when (val value = rawPodsBattery.lowerNibble.toInt()) {
|
||||
15 -> null
|
||||
|
||||
@@ -7,48 +7,13 @@ import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.lowerNibble
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.*
|
||||
import eu.darken.capod.pods.core.HasCase
|
||||
import eu.darken.capod.pods.core.HasDualPods
|
||||
import eu.darken.capod.pods.core.HasDualPods.Pod
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
|
||||
interface DualApplePods : ApplePods, HasDualPods, HasEarDetection, HasCase {
|
||||
|
||||
override fun getStatusShort(context: Context): String = context.getString(
|
||||
R.string.pods_dual_case_status_short,
|
||||
getBatteryLevelLeftPod(context).let {
|
||||
if (isLeftPodCharging) "$it*" else it
|
||||
},
|
||||
getBatteryLevelCase(context).let {
|
||||
if (isCaseCharging) "$it*" else it
|
||||
},
|
||||
getBatteryLevelRightPod(context).let {
|
||||
if (isRightPodCharging) "$it*" else it
|
||||
},
|
||||
)
|
||||
|
||||
override fun getStatusLong(context: Context): List<String> {
|
||||
val list = mutableListOf<String>()
|
||||
|
||||
StringBuilder("${context.getString(R.string.pods_dual_left_label)}: ${getBatteryLevelLeftPod(context)}").apply {
|
||||
if (isLeftPodCharging) append(", ${context.getString(R.string.pods_charging_label)}")
|
||||
if (isLeftPodInEar) append(", ${context.getString(R.string.pods_inear_label)}")
|
||||
list.add(this.toString())
|
||||
}
|
||||
|
||||
StringBuilder("${context.getString(R.string.pods_dual_right_label)}: ${getBatteryLevelRightPod(context)}").apply {
|
||||
if (isRightPodCharging) append(", ${context.getString(R.string.pods_charging_label)}")
|
||||
if (isRightPodInEar) append(", ${context.getString(R.string.pods_inear_label)}")
|
||||
if (microPhonePod == Pod.RIGHT) append(", ${context.getString(R.string.pods_microphone_label)}")
|
||||
list.add(this.toString())
|
||||
}
|
||||
|
||||
StringBuilder("${context.getString(R.string.pods_case_label)}: ${getBatteryLevelCase(context)}").apply {
|
||||
if (isCaseCharging) append(", ${context.getString(R.string.pods_charging_label)}")
|
||||
list.add(this.toString())
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
val microPhonePod: Pod
|
||||
get() = when (rawStatus.isBitSet(5)) {
|
||||
true -> Pod.LEFT
|
||||
|
||||
@@ -1,24 +1,12 @@
|
||||
package eu.darken.capod.pods.core.apple
|
||||
|
||||
import android.content.Context
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.isBitSet
|
||||
import eu.darken.capod.common.upperNibble
|
||||
import eu.darken.capod.pods.core.HasEarDetection
|
||||
import eu.darken.capod.pods.core.HasSinglePod
|
||||
import eu.darken.capod.pods.core.getBatteryLevelHeadset
|
||||
|
||||
interface SingleApplePods : BasicSingleApplePods, HasEarDetection, HasSinglePod {
|
||||
|
||||
override fun getStatusShort(context: Context): String = context.getString(
|
||||
R.string.pods_single_basic_status_short,
|
||||
getBatteryLevelHeadset(context),
|
||||
)
|
||||
|
||||
override fun getStatusLong(context: Context): List<String> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
val isHeadphonesBeingWorn: Boolean
|
||||
get() = rawStatus.isBitSet(1)
|
||||
|
||||
|
||||
@@ -18,11 +18,4 @@ data class UnknownAppleDevice(
|
||||
return context.getString(R.string.pods_unknown_label)
|
||||
}
|
||||
|
||||
override fun getStatusShort(context: Context): String {
|
||||
return context.getString(R.string.pods_unknown_label)
|
||||
}
|
||||
|
||||
override fun getStatusLong(context: Context): List<String> {
|
||||
return listOf(rawDataHex)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.67,4H14V2h-4v2H8.33C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33V5.33C17,4.6 16.4,4 15.67,4zM11,20v-5.5H9L13,7v5.5h2L11,20z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M11.07,12.85c0.77,-1.39 2.25,-2.21 3.11,-3.44c0.91,-1.29 0.4,-3.7 -2.18,-3.7c-1.69,0 -2.52,1.28 -2.87,2.34L6.54,6.96C7.25,4.83 9.18,3 11.99,3c2.35,0 3.96,1.07 4.78,2.41c0.7,1.15 1.11,3.3 0.03,4.9c-1.2,1.77 -2.35,2.31 -2.97,3.45c-0.25,0.46 -0.35,0.76 -0.35,2.24h-2.89C10.58,15.22 10.46,13.95 11.07,12.85zM14,20c0,1.1 -0.9,2 -2,2s-2,-0.9 -2,-2c0,-1.1 0.9,-2 2,-2S14,18.9 14,20z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pod_left"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawableStart="@drawable/ic_airpod_left_24"
|
||||
tools:text="100%" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pod_case"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:drawableStart="@drawable/ic_airpod_case_24"
|
||||
tools:text="100%" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pod_right"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:drawableStart="@drawable/ic_airpod_right_24"
|
||||
tools:text="100%" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headphones_label"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
tools:text="AirPods Max" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headphones"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:drawableStart="@drawable/ic_device_generic_headphones"
|
||||
android:gravity="center"
|
||||
tools:text="100%" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:drawableStart="@drawable/ic_baseline_question_mark_24"
|
||||
android:gravity="center"
|
||||
tools:text="100%" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="notification_height_collapsed">64dp</dimen>
|
||||
</resources>
|
||||
@@ -36,7 +36,6 @@
|
||||
<string name="pods_single_headphones_label">Headphones</string>
|
||||
<string name="pods_dual_left_label">Left pod</string>
|
||||
<string name="pods_dual_right_label">Right pod</string>
|
||||
<string name="pods_dual_case_status_short">L %1$s | C %2$s | R %3$s</string>
|
||||
<string name="pods_case_label">Case</string>
|
||||
<string name="pods_case_status_open_label">Open</string>
|
||||
<string name="pods_case_status_closed_label">Closed</string>
|
||||
|
||||
Reference in New Issue
Block a user