mirror of
https://github.com/d4rken-org/capod.git
synced 2026-09-14 18:26:11 -04:00
Implement popup notifications on case open.
This commit is contained in:
+2
-2
@@ -11,7 +11,7 @@ import eu.darken.capod.pods.core.apple.SingleApplePods
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class NotificationViewFactory @Inject constructor(
|
||||
class MonitorNotificationViewFactory @Inject constructor(
|
||||
@ApplicationContext private val context: Context
|
||||
) {
|
||||
|
||||
@@ -52,7 +52,7 @@ class NotificationViewFactory @Inject constructor(
|
||||
|
||||
private fun createSingleBasicApplePods(device: BasicSingleApplePods): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.monitor_notification_single_pods_small
|
||||
R.layout.monitor_notification_single_pods_basic_small
|
||||
).apply {
|
||||
setTextViewText(R.id.headphones_label, device.getLabel(context))
|
||||
|
||||
@@ -26,7 +26,7 @@ import javax.inject.Inject
|
||||
class MonitorNotifications @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
notificationManager: NotificationManager,
|
||||
private val notificationViewFactory: NotificationViewFactory
|
||||
private val notificationViewFactory: MonitorNotificationViewFactory
|
||||
) {
|
||||
|
||||
private val builder: NotificationCompat.Builder
|
||||
|
||||
@@ -7,7 +7,9 @@ import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.flow.setupCommonEventHandlers
|
||||
import eu.darken.capod.common.flow.withPrevious
|
||||
import eu.darken.capod.monitor.core.PodMonitor
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import eu.darken.capod.pods.core.apple.DualApplePods
|
||||
import eu.darken.capod.reaction.popup.ui.PopUpNotification
|
||||
import eu.darken.capod.reaction.settings.ReactionSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
@@ -19,12 +21,14 @@ import javax.inject.Singleton
|
||||
@Singleton
|
||||
class PopUpReaction @Inject constructor(
|
||||
private val podMonitor: PodMonitor,
|
||||
private val reactionSettings: ReactionSettings
|
||||
private val reactionSettings: ReactionSettings,
|
||||
private val popUpNotification: PopUpNotification,
|
||||
) {
|
||||
|
||||
fun monitor(): Flow<Unit> = reactionSettings.showPopUpOnCaseOpen.flow
|
||||
.flatMapLatest { if (it) podMonitor.mainDevice else emptyFlow() }
|
||||
.withPrevious()
|
||||
.setupCommonEventHandlers(TAG) { "monitor" }
|
||||
.map { (previous, current) ->
|
||||
if (previous is DualApplePods? && current is DualApplePods) {
|
||||
log(TAG) {
|
||||
@@ -42,15 +46,16 @@ class PopUpReaction @Inject constructor(
|
||||
if (isSameDeviceWithCaseNowOpen || isNewDeviceWithJustOpenedCase) {
|
||||
log(TAG) { "Case lid status changed for monitored device." }
|
||||
|
||||
if (current.caseLidState == DualApplePods.LidState.OPEN) {
|
||||
if (current.caseLidState == DualApplePods.LidState.OPEN && current.model != PodDevice.Model.UNKNOWN) {
|
||||
log(TAG, INFO) { "Show popup" }
|
||||
} else if (current.caseLidState == DualApplePods.LidState.CLOSED) {
|
||||
popUpNotification.showNotification(current)
|
||||
} else if (current.caseLidState != DualApplePods.LidState.OPEN) {
|
||||
log(TAG, INFO) { "Hide popup" }
|
||||
popUpNotification.hideNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.setupCommonEventHandlers(TAG) { "monitor" }
|
||||
|
||||
companion object {
|
||||
private val TAG = logTag("Reaction", "PopUp")
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package eu.darken.capod.reaction.popup.ui
|
||||
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.app.NotificationCompat
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import eu.darken.capod.R
|
||||
import eu.darken.capod.common.BuildConfigWrap
|
||||
import eu.darken.capod.common.debug.logging.log
|
||||
import eu.darken.capod.common.debug.logging.logTag
|
||||
import eu.darken.capod.common.notifications.PendingIntentCompat
|
||||
import eu.darken.capod.main.ui.MainActivity
|
||||
import eu.darken.capod.pods.core.PodDevice
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
class PopUpNotification @Inject constructor(
|
||||
@ApplicationContext private val context: Context,
|
||||
private val notificationManager: NotificationManager,
|
||||
private val notificationViewFactory: PopUpNotificationViewFactory
|
||||
) {
|
||||
|
||||
private val builder: NotificationCompat.Builder
|
||||
|
||||
init {
|
||||
NotificationChannel(
|
||||
NOTIFICATION_CHANNEL_ID,
|
||||
context.getString(R.string.notification_channel_reaction_popup_label),
|
||||
NotificationManager.IMPORTANCE_HIGH
|
||||
).run {
|
||||
vibrationPattern = LongArray(1) { 0 }
|
||||
enableVibration(true)
|
||||
enableLights(false)
|
||||
setSound(null, null)
|
||||
notificationManager.createNotificationChannel(this)
|
||||
}
|
||||
|
||||
val openIntent = Intent(context, MainActivity::class.java)
|
||||
val openPi = PendingIntent.getActivity(
|
||||
context,
|
||||
0,
|
||||
openIntent,
|
||||
PendingIntentCompat.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
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)
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getBuilder(device: PodDevice): NotificationCompat.Builder = builder.apply {
|
||||
setStyle(NotificationCompat.DecoratedCustomViewStyle())
|
||||
setCustomContentView(notificationViewFactory.createContentView(device))
|
||||
setSmallIcon(device.iconRes)
|
||||
setSubText(null)
|
||||
}
|
||||
|
||||
fun showNotification(device: PodDevice) {
|
||||
val notification = getBuilder(device).build()
|
||||
log(TAG) { "showNotification(): $device" }
|
||||
notificationManager.notify(NOTIFICATION_ID, notification)
|
||||
}
|
||||
|
||||
fun hideNotification() {
|
||||
log(TAG) { "hideNotification()" }
|
||||
notificationManager.cancel(NOTIFICATION_ID)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val TAG = logTag("Reaction", "PopUp", "Notifications")
|
||||
private val NOTIFICATION_CHANNEL_ID =
|
||||
"${BuildConfigWrap.APPLICATION_ID}.notification.channel.reaction.popup"
|
||||
internal const val NOTIFICATION_ID = 9000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package eu.darken.capod.reaction.popup.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 PopUpNotificationViewFactory @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 -> throw IllegalArgumentException("Unexpected device: $device")
|
||||
}
|
||||
|
||||
private fun createDualApplePods(device: DualApplePods): RemoteViews = RemoteViews(
|
||||
context.packageName,
|
||||
R.layout.popup_notification_dual_pods
|
||||
).apply {
|
||||
setTextViewText(R.id.pod_label, device.getLabel(context))
|
||||
|
||||
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.popup_notification_single_pods
|
||||
).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.popup_notification_single_pods_basic
|
||||
).apply {
|
||||
setTextViewText(R.id.headphones_label, device.getLabel(context))
|
||||
|
||||
device.getBatteryLevelHeadset(context)
|
||||
.run { setTextViewText(R.id.headphones, this) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
<?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,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,72 @@
|
||||
<?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"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/pod_label"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
tools:text="AirPods Pro" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,29 @@
|
||||
<?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"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headphones_label"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
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_gravity="center"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:drawableStart="@drawable/ic_device_generic_headphones"
|
||||
tools:text="100%" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,38 @@
|
||||
<?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"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headphones_label"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:gravity="center"
|
||||
tools:text="AirPods Max" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/headphones"
|
||||
style="@style/TextAppearance.Compat.Notification.Title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:drawableStart="@drawable/ic_device_generic_headphones"
|
||||
android:gravity="center"
|
||||
tools:text="100%" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -145,4 +145,5 @@
|
||||
<string name="upgrade_capod_description">Get additional features and support the developer.</string>
|
||||
<string name="settings_popup_caseopen_label">Show popup</string>
|
||||
<string name="settings_popup_caseopen_description">Show a popup when the device case is opened (experimental).</string>
|
||||
<string name="notification_channel_reaction_popup_label">Popup device reactions</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user