diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/NotificationViewFactory.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt similarity index 95% rename from app/src/main/java/eu/darken/capod/monitor/ui/NotificationViewFactory.kt rename to app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt index 1357eba5..b634d665 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/NotificationViewFactory.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotificationViewFactory.kt @@ -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)) diff --git a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt index fa868e3f..460460f8 100644 --- a/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt +++ b/app/src/main/java/eu/darken/capod/monitor/ui/MonitorNotifications.kt @@ -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 diff --git a/app/src/main/java/eu/darken/capod/reaction/popup/PopUpReaction.kt b/app/src/main/java/eu/darken/capod/reaction/popup/PopUpReaction.kt index ece5b67d..10ba5940 100644 --- a/app/src/main/java/eu/darken/capod/reaction/popup/PopUpReaction.kt +++ b/app/src/main/java/eu/darken/capod/reaction/popup/PopUpReaction.kt @@ -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 = 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") diff --git a/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotification.kt b/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotification.kt new file mode 100644 index 00000000..30b6d386 --- /dev/null +++ b/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotification.kt @@ -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 + } +} diff --git a/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotificationViewFactory.kt b/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotificationViewFactory.kt new file mode 100644 index 00000000..1dc8697d --- /dev/null +++ b/app/src/main/java/eu/darken/capod/reaction/popup/ui/PopUpNotificationViewFactory.kt @@ -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) } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/monitor_notification_dual_pods_big.xml b/app/src/main/res/layout/monitor_notification_dual_pods_big.xml deleted file mode 100644 index 77d9ef65..00000000 --- a/app/src/main/res/layout/monitor_notification_dual_pods_big.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/monitor_notification_single_pods_basic_small.xml b/app/src/main/res/layout/monitor_notification_single_pods_basic_small.xml new file mode 100644 index 00000000..2c9577cd --- /dev/null +++ b/app/src/main/res/layout/monitor_notification_single_pods_basic_small.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_notification_dual_pods.xml b/app/src/main/res/layout/popup_notification_dual_pods.xml new file mode 100644 index 00000000..6dffd6f1 --- /dev/null +++ b/app/src/main/res/layout/popup_notification_dual_pods.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_notification_single_pods.xml b/app/src/main/res/layout/popup_notification_single_pods.xml new file mode 100644 index 00000000..e160d10f --- /dev/null +++ b/app/src/main/res/layout/popup_notification_single_pods.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/popup_notification_single_pods_basic.xml b/app/src/main/res/layout/popup_notification_single_pods_basic.xml new file mode 100644 index 00000000..e92ba0fe --- /dev/null +++ b/app/src/main/res/layout/popup_notification_single_pods_basic.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e12e1be..c0646d43 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -145,4 +145,5 @@ Get additional features and support the developer. Show popup Show a popup when the device case is opened (experimental). + Popup device reactions \ No newline at end of file