P2 Stream H: standalone TaskSuggestionsScreen

Port iOS TaskSuggestionsView as a standalone route reachable outside
onboarding. Uses shared suggestions API + accept/skip analytics in
non-onboarding variant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-04-18 13:10:47 -05:00
parent 7d71408bcc
commit 19471d780d
19 changed files with 2161 additions and 3 deletions
@@ -69,8 +69,9 @@ class FcmService : FirebaseMessagingService() {
messageId: String?
): android.app.Notification {
val contentIntent = buildContentIntent(payload, messageId)
val notificationId = (messageId ?: payload.deepLink ?: payload.title).hashCode()
return NotificationCompat.Builder(this, channelId)
val builder = NotificationCompat.Builder(this, channelId)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(payload.title.ifBlank { getString(R.string.app_name) })
.setContentText(payload.body)
@@ -78,7 +79,96 @@ class FcmService : FirebaseMessagingService() {
.setAutoCancel(true)
.setContentIntent(contentIntent)
.setPriority(priorityForChannel(channelId))
.build()
addActionButtons(builder, payload, notificationId, messageId)
return builder.build()
}
/**
* Attach iOS-parity action buttons (`NotificationCategories.swift`) to
* the [builder] based on [payload.type]:
*
* - task_reminder / task_overdue → Complete, Snooze, Open
* - residence_invite → Accept, Decline, Open
* - subscription → no actions (matches iOS TASK_COMPLETED)
*
* All actions fan out to [NotificationActionReceiver] under the
* `com.tt.honeyDue.notifications` package.
*/
private fun addActionButtons(
builder: NotificationCompat.Builder,
payload: NotificationPayload,
notificationId: Int,
messageId: String?
) {
val seed = (messageId ?: payload.deepLink ?: payload.title).hashCode()
val extras: Map<String, Any?> = mapOf(
NotificationActions.EXTRA_TASK_ID to payload.taskId,
NotificationActions.EXTRA_RESIDENCE_ID to payload.residenceId,
NotificationActions.EXTRA_NOTIFICATION_ID to notificationId,
NotificationActions.EXTRA_TITLE to payload.title,
NotificationActions.EXTRA_BODY to payload.body,
NotificationActions.EXTRA_TYPE to payload.type,
NotificationActions.EXTRA_DEEP_LINK to payload.deepLink
)
when (payload.type) {
NotificationChannels.TASK_REMINDER,
NotificationChannels.TASK_OVERDUE -> {
if (payload.taskId != null) {
builder.addAction(
0,
getString(R.string.notif_action_complete),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.COMPLETE, seed, extras
)
)
builder.addAction(
0,
getString(R.string.notif_action_snooze),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.SNOOZE, seed, extras
)
)
}
builder.addAction(
0,
getString(R.string.notif_action_open),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.OPEN, seed, extras
)
)
}
NotificationChannels.RESIDENCE_INVITE -> {
if (payload.residenceId != null) {
builder.addAction(
0,
getString(R.string.notif_action_accept),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.ACCEPT_INVITE, seed, extras
)
)
builder.addAction(
0,
getString(R.string.notif_action_decline),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.DECLINE_INVITE, seed, extras
)
)
}
builder.addAction(
0,
getString(R.string.notif_action_open),
NotificationActionReceiver.actionPendingIntent(
this, NotificationActions.OPEN, seed, extras
)
)
}
else -> {
// subscription + unknown: tap-to-open only. iOS parity.
}
}
}
private fun buildContentIntent(