Skip to content

Commit

Permalink
Implement new notification features
Browse files Browse the repository at this point in the history
Signed-off-by: mueller-ma <[email protected]>
  • Loading branch information
mueller-ma committed Jul 7, 2024
1 parent f81eff0 commit 4972e4d
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import kotlinx.coroutines.runBlocking
import org.openhab.habdroid.model.CloudNotification
import org.openhab.habdroid.model.CloudNotificationAction
import org.openhab.habdroid.model.toCloudNotificationAction
import org.openhab.habdroid.model.toOH2IconResource

class FcmMessageListenerService : FirebaseMessagingService() {
Expand All @@ -42,15 +44,20 @@ class FcmMessageListenerService : FirebaseMessagingService() {

when (messageType) {
"notification" -> {
var actions = emptyList<CloudNotificationAction>()
val cloudNotification = CloudNotification(
data["persistedId"].orEmpty(),
data["message"].orEmpty(),
id = data["persistedId"].orEmpty(),
title = data["title"].orEmpty(),
message = data["message"].orEmpty(),
// Older versions of openhab-cloud didn't send the notification generation
// timestamp, so use the (undocumented) google.sent_time as a time reference
// in that case. If that also isn't present, don't show time at all.
data["timestamp"]?.toLong() ?: message.sentTime,
data["icon"].toOH2IconResource(),
data["severity"]
createdTimestamp = data["timestamp"]?.toLong() ?: message.sentTime,
icon = data["icon"].toOH2IconResource(),
severity = data["severity"],
actions = actions,
onClickAction = data["TODO"].toCloudNotificationAction(),
mediaAttachmentUrl = data["TODO"]
)

runBlocking {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ class NotificationHelper(private val context: Context) {
): Notification {
val iconBitmap = getNotificationIcon(message.icon)

// TODO
val contentIntent = makeNotificationClickIntent(message.id, notificationId)
val channelId = getChannelId(message.severity)

Expand All @@ -140,17 +141,26 @@ class NotificationHelper(private val context: Context) {
.setContentIntent(contentIntent)
.build()

return makeNotificationBuilder(channelId, message.createdTimestamp)
val builder = makeNotificationBuilder(channelId, message.createdTimestamp)
.setLargeIcon(iconBitmap)
.setStyle(NotificationCompat.BigTextStyle().bigText(message.message))
.setSound(context.getPrefs().getNotificationTone())
.setContentTitle(message.title)
.setContentText(message.message)
.setSubText(message.severity)
.setContentIntent(contentIntent)
.setDeleteIntent(deleteIntent)
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(publicVersion)
.build()

//message.actions?.forEach {
// // TODO
// val pendingIntent = PendingIntent()
// val action = NotificationCompat.Action(null, it.label, pendingIntent)
// builder.addAction(action)
//}

return builder.build()
}

private suspend fun getNotificationIcon(icon: IconResource?): Bitmap? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@

package org.openhab.habdroid.model

import android.content.Context
import android.graphics.Bitmap
import android.os.Parcelable
import java.text.ParseException
import java.text.SimpleDateFormat
Expand All @@ -21,17 +23,40 @@ import java.util.TimeZone
import kotlinx.parcelize.Parcelize
import org.json.JSONException
import org.json.JSONObject
import org.openhab.habdroid.core.connection.Connection
import org.openhab.habdroid.util.IconBackground
import org.openhab.habdroid.util.ImageConversionPolicy
import org.openhab.habdroid.util.getIconFallbackColor
import org.openhab.habdroid.util.map
import org.openhab.habdroid.util.optStringOrNull

@Parcelize
data class CloudNotification internal constructor(
val id: String,
val title: String,
val message: String,
val createdTimestamp: Long,
val icon: IconResource?,
val severity: String?
val severity: String?,
val actions: List<CloudNotificationAction>?,
val onClickAction: CloudNotificationAction?,
val mediaAttachmentUrl: String?
) : Parcelable {
val idHash get() = id.hashCode()

suspend fun loadImage(connection: Connection, context: Context, size: Int): Bitmap? {
mediaAttachmentUrl ?: return null
//if (mediaAttachmentUrl.startsWith("item:")) {
// val itemName = mediaAttachmentUrl.removePrefix("item:")
// val item = ItemClient.loadItem(connection, itemName)
//
//}
val fallbackColor = context.getIconFallbackColor(IconBackground.APP_THEME)
return connection.httpClient
.get(mediaAttachmentUrl)
.asBitmap(size, fallbackColor, ImageConversionPolicy.PreferTargetSize)
.response
}
}

@Throws(JSONException::class)
Expand All @@ -47,11 +72,36 @@ fun JSONObject.toCloudNotification(): CloudNotification {
}
}

val payload = optJSONObject("payload")
return CloudNotification(
getString("_id"),
getString("message"),
created,
optStringOrNull("icon").toOH2IconResource(),
optStringOrNull("severity")
id = getString("_id"),
title = payload?.optString("title").orEmpty(),
message = payload?.getString("message") ?: getString("message"),
createdTimestamp = created,
icon = payload?.optStringOrNull("icon").toOH2IconResource() ?: optStringOrNull("icon").toOH2IconResource(),
severity = payload?.optStringOrNull("severity") ?: optStringOrNull("severity"),
actions = payload?.optJSONArray("actions")?.map { it.toCloudNotificationAction() }?.filterNotNull(),
onClickAction = payload?.optStringOrNull("on-click").toCloudNotificationAction(),
mediaAttachmentUrl = payload?.optStringOrNull("media-attachment-url")
)
}

@Parcelize
data class CloudNotificationAction internal constructor(
val label: String,
val action: String
) : Parcelable

fun String?.toCloudNotificationAction(): CloudNotificationAction? {
val split = this?.split("=", limit = 2)
if (split?.size != 2) {
return null
}
return CloudNotificationAction(split.component1(), split.component2())
}

fun JSONObject?.toCloudNotificationAction(): CloudNotificationAction? {
val action = this?.optStringOrNull("action") ?: return null
val title = optStringOrNull("action") ?: return null
return CloudNotificationAction(title, action)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ import android.widget.TextView
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import java.util.ArrayList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.openhab.habdroid.R
import org.openhab.habdroid.core.connection.ConnectionFactory
import org.openhab.habdroid.model.CloudNotification
Expand Down Expand Up @@ -96,9 +100,11 @@ class CloudNotificationAdapter(context: Context, private val loadMoreListener: (

class NotificationViewHolder(inflater: LayoutInflater, parent: ViewGroup) :
RecyclerView.ViewHolder(inflater.inflate(R.layout.notificationlist_item, parent, false)) {
private val createdView: TextView = itemView.findViewById(R.id.notificationCreated)
private val titleView: TextView = itemView.findViewById(R.id.notificationTitle)
private val messageView: TextView = itemView.findViewById(R.id.notificationMessage)
private val iconView: WidgetImageView = itemView.findViewById(R.id.notificationImage)
private val createdView: TextView = itemView.findViewById(R.id.notificationCreated)
private val iconView: WidgetImageView = itemView.findViewById(R.id.notificationIcon)
private val imageView: WidgetImageView = itemView.findViewById(R.id.notificationImage)
private val severityView: TextView = itemView.findViewById(R.id.notificationSeverity)

fun bind(notification: CloudNotification) {
Expand All @@ -109,20 +115,33 @@ class CloudNotificationAdapter(context: Context, private val loadMoreListener: (
DateUtils.WEEK_IN_MILLIS,
0
)
titleView.text = notification.title
titleView.isVisible = notification.title.isNotEmpty()
messageView.text = notification.message
messageView.isVisible = notification.message.isNotEmpty()

val conn = ConnectionFactory.activeCloudConnection?.connection
if (notification.icon != null && conn != null) {
iconView.setImageUrl(
conn,
notification.icon.toUrl(
itemView.context,
itemView.context.determineDataUsagePolicy(conn).loadIconsWithState
),
timeoutMillis = 2000
)
} else {
if (conn == null) {
iconView.applyFallbackDrawable()
imageView.isVisible = false
} else {
if (notification.icon != null) {
iconView.setImageUrl(
conn,
notification.icon.toUrl(
itemView.context,
itemView.context.determineDataUsagePolicy(conn).loadIconsWithState
),
timeoutMillis = 2000
)
}
imageView.isVisible = notification.mediaAttachmentUrl != null
CoroutineScope(Dispatchers.IO + Job()).launch {
val bitmap = notification.loadImage(conn, itemView.context, itemView.width)
withContext(Dispatchers.Main) {
imageView.setImageBitmap(bitmap)
}
}
}
severityView.text = notification.severity
severityView.isGone = notification.severity.isNullOrEmpty()
Expand Down
124 changes: 78 additions & 46 deletions mobile/src/main/res/layout/notificationlist_item.xml
Original file line number Diff line number Diff line change
@@ -1,63 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingHorizontal="16dp"
android:minHeight="?attr/listPreferredItemHeight"
android:background="?attr/selectableItemBackground"
android:focusable="false"
android:orientation="horizontal">
android:minHeight="?attr/listPreferredItemHeight"
android:orientation="horizontal"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="8dp">

<org.openhab.habdroid.ui.widget.WidgetImageView
android:id="@+id/notificationImage"
android:id="@+id/notificationIcon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center_vertical"
app:imageScalingType="noScaling"
android:foregroundGravity="center"
app:fallback="@drawable/ic_openhab_appicon_24dp"
app:imageScalingType="noScaling"
app:layout_constraintBottom_toBottomOf="@+id/notificationCreated"
app:layout_constraintEnd_toStartOf="@+id/guideline"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_openhab_appicon_24dp" />

<RelativeLayout
<TextView
android:id="@+id/notificationTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="start"
android:textAppearance="?attr/textAppearanceBodyLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toTopOf="parent"
tools:text="Some title" />

<TextView
android:id="@+id/notificationMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="16dp"
android:layout_gravity="center_vertical">

<TextView
android:id="@+id/notificationMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyLarge"
tools:text="Some notification" />

<TextView
android:id="@+id/notificationCreated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/notificationMessage"
android:layout_alignParentStart="true"
android:textAppearance="?attr/textAppearanceBodySmall"
tools:text="6 hours ago" />

<TextView
android:id="@+id/notificationSeverity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/notificationMessage"
android:layout_toEndOf="@id/notificationCreated"
android:layout_alignBaseline="@id/notificationCreated"
android:layout_alignParentEnd="true"
android:gravity="end"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textStyle="bold"
tools:text="Important" />

</RelativeLayout>

</LinearLayout>
android:layout_marginTop="8dp"
android:textAppearance="?attr/textAppearanceBodyLarge"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/notificationTitle"
tools:text="Some notification" />

<TextView
android:id="@+id/notificationCreated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textAppearance="?attr/textAppearanceBodySmall"
app:layout_constraintStart_toStartOf="@+id/guideline"
app:layout_constraintTop_toBottomOf="@+id/notificationMessage"
tools:text="6 hours ago" />

<TextView
android:id="@+id/notificationSeverity"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:gravity="end"
android:textAppearance="?attr/textAppearanceLabelSmall"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/notificationCreated"
app:layout_constraintTop_toBottomOf="@+id/notificationMessage"
tools:text="Important" />

<org.openhab.habdroid.ui.widget.WidgetImageView
android:id="@+id/notificationImage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/notificationCreated"
app:srcCompat="@android:mipmap/sym_def_app_icon" />

<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_begin="50dp" />

</androidx.constraintlayout.widget.ConstraintLayout>

0 comments on commit 4972e4d

Please sign in to comment.