diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index daeba2726..1c7b318dd 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -2025,19 +2025,25 @@
Notifications Allowed
Allowed in Android settings
- Notifications are turned off
+ Notifications are turned off
Turned off for Flipcash in Android settings
- Chat notifications are off
+ Chat Notifications
+ Deposit Notifications
+ Buy and Sell Notifications
+ Price Alerts
+ Contact Notifications
+ Misc. Notifications
+ Chat notifications are off
Messages and replies won\'t show
- Deposit notifications are off
+ Deposit notifications are off
Deposits and withdrawals won\'t show
- Buy and sell notifications are off
+ Buy and sell notifications are off
Purchases and sales won\'t show
- Price alerts are off
+ Price alerts are off
Price moves won\'t show
- Contact notifications are off
+ Contact notifications are off
You won\'t hear when contacts join
- Misc. notifications are off
+ Misc. notifications are off
Other updates from Flipcash won\'t show
Other Categories
@@ -2051,29 +2057,28 @@
%1$s and %2$s
Battery Optimization
Flipcash is not optimized
- Battery optimization is on
+ Battery optimization is on
Android may delay notifications
Background Activity
Not restricted · standby: %1$s
Android is limiting Flipcash in the background
- Background activity is restricted
+ Background activity is restricted
Notifications may not arrive
Google Play Services
Available
Google Play services needs an update
- Google Play services isn\'t available
+ Google Play services isn\'t available
Push notifications need it
Registered for Push
- Checking registration…
Contacting Flipcash
Confirmed just now
- Couldn\'t get a push token from Google
- Flipcash rejected this phone\'s push token
- Couldn\'t reach Flipcash
+ Couldn\'t get a push token from Google
+ Flipcash rejected this phone\'s push token
+ Couldn\'t reach Flipcash
Check your connection
Last confirmed %1$s
Last confirmed just now
- Registration failed
+ Registration failed
diff --git a/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitles.kt b/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitles.kt
new file mode 100644
index 000000000..91b800da5
--- /dev/null
+++ b/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitles.kt
@@ -0,0 +1,72 @@
+package com.flipcash.app.notificationstatus.internal
+
+import androidx.annotation.StringRes
+import com.flipcash.app.auth.push.PushRegistrationOutcome
+import com.flipcash.core.R
+
+/**
+ * A row's title: its check name, picked by [key] alone, so it reads the same pending, checking,
+ * passing or not. The mark and the Fix button show a problem; the title never says it.
+ *
+ * @param key a [CheckRow.key]
+ */
+@StringRes
+fun checkTitle(key: String): Int = when (key) {
+ PermissionKey -> R.string.title_notificationCheckAllowed
+ OtherChannelsKey -> R.string.title_notificationCheckOtherCategories
+ BatteryKey -> R.string.title_notificationCheckBattery
+ BackgroundKey -> R.string.title_notificationCheckBackground
+ GooglePlayKey -> R.string.title_notificationCheckPlayServices
+ RegistrationKey -> R.string.title_notificationCheckRegistered
+ else -> when (StatusChannel.entries.firstOrNull { channelKey(it) == key }) {
+ StatusChannel.Chat -> R.string.title_notificationCheckChat
+ StatusChannel.DepositWithdrawal -> R.string.title_notificationCheckDeposits
+ StatusChannel.BuySell -> R.string.title_notificationCheckBuySell
+ StatusChannel.Gain -> R.string.title_notificationCheckGain
+ StatusChannel.ContactJoin -> R.string.title_notificationCheckContacts
+ StatusChannel.General -> R.string.title_notificationCheckGeneral
+ // evaluateNotificationStatus gives it no row: it only gates the test button.
+ StatusChannel.TestPush, null -> error("$key has no row")
+ }
+}
+
+/**
+ * The sentence that leads a settled row's detail when it has a problem, "Battery optimization is
+ * on", with any explanation on the line after it. Null for a passing row, whose detail is one line,
+ * and for a registration still running, whose detail is only "Contacting Flipcash".
+ */
+@StringRes
+fun problemLine(row: CheckRow): Int? = when (row) {
+ is CheckRow.Pending, is CheckRow.Rechecking, is CheckRow.OtherChannels -> null
+ is CheckRow.Permission -> R.string.subtitle_notificationCheckNotAllowedProblem.takeUnless { row.allowed }
+ is CheckRow.BlockedChannel -> when (row.channel) {
+ StatusChannel.Chat -> R.string.subtitle_notificationCheckChatOffProblem
+ StatusChannel.DepositWithdrawal -> R.string.subtitle_notificationCheckDepositsOffProblem
+ StatusChannel.BuySell -> R.string.subtitle_notificationCheckBuySellOffProblem
+ StatusChannel.Gain -> R.string.subtitle_notificationCheckGainOffProblem
+ StatusChannel.ContactJoin -> R.string.subtitle_notificationCheckContactsOffProblem
+ StatusChannel.General -> R.string.subtitle_notificationCheckGeneralOffProblem
+ StatusChannel.TestPush -> error("${row.channel.id} has no row")
+ }
+ is CheckRow.Battery -> R.string.subtitle_notificationCheckBatteryOnProblem.takeIf { row.optimized }
+ is CheckRow.Background -> when (row.status) {
+ CheckStatus.Fail -> R.string.subtitle_notificationCheckBackgroundRestrictedProblem
+ CheckStatus.Warning -> R.string.subtitle_notificationCheckBackgroundLimited
+ else -> null
+ }
+ is CheckRow.GooglePlay -> when (row.status) {
+ CheckStatus.Fail -> R.string.subtitle_notificationCheckPlayServicesMissingProblem
+ CheckStatus.Warning -> R.string.subtitle_notificationCheckPlayServicesUpdate
+ else -> null
+ }
+ is CheckRow.Registration -> when (val state = row.state) {
+ RegistrationState.Checking -> null
+ is RegistrationState.Finished -> when (state.outcome) {
+ PushRegistrationOutcome.Ok -> null
+ PushRegistrationOutcome.NoToken -> R.string.subtitle_notificationCheckNoTokenProblem
+ PushRegistrationOutcome.InvalidPushToken -> R.string.subtitle_notificationCheckTokenRejectedProblem
+ PushRegistrationOutcome.Transport -> R.string.subtitle_notificationCheckUnreachableProblem
+ PushRegistrationOutcome.Failed -> R.string.subtitle_notificationCheckFailedProblem
+ }
+ }
+}
diff --git a/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/NotificationStatusContent.kt b/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/NotificationStatusContent.kt
index 9715ebbf2..458d91734 100644
--- a/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/NotificationStatusContent.kt
+++ b/apps/flipcash/features/notification-status/src/main/kotlin/com/flipcash/app/notificationstatus/internal/NotificationStatusContent.kt
@@ -332,7 +332,7 @@ private fun Group(
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
- last.row?.let { CheckRowItem(it, state, onFix) }
+ last.row?.let { CheckRowItem(it, onFix) }
}
}
}
@@ -340,8 +340,8 @@ private fun Group(
}
@Composable
-private fun CheckRowItem(row: CheckRow, state: NotificationStatusViewModel.State, onFix: (Fix) -> Unit) {
- val text = row.text(state)
+private fun CheckRowItem(row: CheckRow, onFix: (Fix) -> Unit) {
+ val text = row.text()
Row(
modifier = Modifier
.fillMaxWidth()
@@ -399,91 +399,79 @@ private fun FixButton(fix: Fix, enabled: Boolean, onFix: (Fix) -> Unit) {
)
}
+// The title is the check name in every state. The detail leads with the problem, if there is one,
+// and puts the explanation on the second line: the two lines the row reserves.
@Composable
-private fun CheckRow.text(state: NotificationStatusViewModel.State): Pair = when (this) {
- is CheckRow.Pending -> stringResource(
- when (key) {
- PermissionKey -> R.string.title_notificationCheckAllowed
- OtherChannelsKey -> R.string.title_notificationCheckOtherCategories
- BatteryKey -> R.string.title_notificationCheckBattery
- BackgroundKey -> R.string.title_notificationCheckBackground
- else -> R.string.title_notificationCheckPlayServices
- },
- ) to stringResource(R.string.title_notificationStatusChecking)
+private fun CheckRow.text(): Pair {
+ val title = stringResource(checkTitle(key))
+ if (this is CheckRow.Pending || this is CheckRow.Rechecking) {
+ return title to stringResource(R.string.title_notificationStatusChecking)
+ }
+ val problem = problemLine(this)?.let { stringResource(it) }
+ val detail = listOfNotNull(problem, explanation()?.takeIf { it.isNotEmpty() }).joinToString("\n")
+ return title to detail
+}
- is CheckRow.Rechecking -> previous.text(state).first to stringResource(R.string.title_notificationStatusChecking)
+// The detail's line below the problem, or a passing row's only line. Null when there is none.
+@Composable
+private fun CheckRow.explanation(): String? = when (this) {
+ is CheckRow.Pending, is CheckRow.Rechecking -> null
- is CheckRow.Permission -> if (allowed) {
- stringResource(R.string.title_notificationCheckAllowed) to stringResource(R.string.subtitle_notificationCheckAllowed)
- } else {
- stringResource(R.string.title_notificationCheckNotAllowed) to stringResource(R.string.subtitle_notificationCheckNotAllowed)
- }
+ is CheckRow.Permission -> stringResource(
+ if (allowed) R.string.subtitle_notificationCheckAllowed else R.string.subtitle_notificationCheckNotAllowed,
+ )
is CheckRow.BlockedChannel -> when (channel) {
- StatusChannel.Chat -> R.string.title_notificationCheckChatOff to R.string.subtitle_notificationCheckChatOff
- StatusChannel.DepositWithdrawal -> R.string.title_notificationCheckDepositsOff to R.string.subtitle_notificationCheckDepositsOff
- StatusChannel.BuySell -> R.string.title_notificationCheckBuySellOff to R.string.subtitle_notificationCheckBuySellOff
- StatusChannel.Gain -> R.string.title_notificationCheckGainOff to R.string.subtitle_notificationCheckGainOff
- StatusChannel.ContactJoin -> R.string.title_notificationCheckContactsOff to R.string.subtitle_notificationCheckContactsOff
- StatusChannel.General -> R.string.title_notificationCheckGeneralOff to R.string.subtitle_notificationCheckGeneralOff
+ StatusChannel.Chat -> R.string.subtitle_notificationCheckChatOff
+ StatusChannel.DepositWithdrawal -> R.string.subtitle_notificationCheckDepositsOff
+ StatusChannel.BuySell -> R.string.subtitle_notificationCheckBuySellOff
+ StatusChannel.Gain -> R.string.subtitle_notificationCheckGainOff
+ StatusChannel.ContactJoin -> R.string.subtitle_notificationCheckContactsOff
+ StatusChannel.General -> R.string.subtitle_notificationCheckGeneralOff
// evaluateNotificationStatus gives it no row: it only gates the test button.
StatusChannel.TestPush -> error("${channel.id} has no row")
- }.let { (title, detail) -> stringResource(title) to stringResource(detail) }
+ }.let { stringResource(it) }
is CheckRow.OtherChannels ->
- stringResource(R.string.title_notificationCheckOtherCategories) to
- stringResource(R.string.subtitle_notificationCheckOtherCategories, categoryList(covered))
+ stringResource(R.string.subtitle_notificationCheckOtherCategories, categoryList(covered))
- is CheckRow.Battery -> if (optimized) {
- stringResource(R.string.title_notificationCheckBatteryOn) to stringResource(R.string.subtitle_notificationCheckBatteryOn)
- } else {
- stringResource(R.string.title_notificationCheckBattery) to stringResource(R.string.subtitle_notificationCheckBattery)
- }
+ is CheckRow.Battery -> stringResource(
+ if (optimized) R.string.subtitle_notificationCheckBatteryOn else R.string.subtitle_notificationCheckBattery,
+ )
is CheckRow.Background -> when (status) {
- CheckStatus.Fail -> stringResource(R.string.title_notificationCheckBackgroundRestricted) to
- stringResource(R.string.subtitle_notificationCheckBackgroundRestricted)
- CheckStatus.Warning -> stringResource(R.string.title_notificationCheckBackground) to
- stringResource(R.string.subtitle_notificationCheckBackgroundLimited)
- else -> stringResource(R.string.title_notificationCheckBackground) to
- stringResource(R.string.subtitle_notificationCheckBackground, bucketName(bucket))
+ CheckStatus.Fail -> stringResource(R.string.subtitle_notificationCheckBackgroundRestricted)
+ // The problem line says it all.
+ CheckStatus.Warning -> null
+ else -> stringResource(R.string.subtitle_notificationCheckBackground, bucketName(bucket))
}
is CheckRow.GooglePlay -> when (status) {
- CheckStatus.Fail -> stringResource(R.string.title_notificationCheckPlayServicesMissing) to
- stringResource(R.string.subtitle_notificationCheckPlayServicesMissing)
- CheckStatus.Warning -> stringResource(R.string.title_notificationCheckPlayServices) to
- stringResource(R.string.subtitle_notificationCheckPlayServicesUpdate)
- else -> stringResource(R.string.title_notificationCheckPlayServices) to
- stringResource(R.string.subtitle_notificationCheckPlayServices)
+ CheckStatus.Fail -> stringResource(R.string.subtitle_notificationCheckPlayServicesMissing)
+ CheckStatus.Warning -> null
+ else -> stringResource(R.string.subtitle_notificationCheckPlayServices)
}
is CheckRow.Registration -> when (val registration = this.state) {
- RegistrationState.Checking -> stringResource(R.string.title_notificationCheckRegistering) to
- stringResource(R.string.subtitle_notificationCheckRegistering)
+ RegistrationState.Checking -> stringResource(R.string.subtitle_notificationCheckRegistering)
is RegistrationState.Finished -> when (registration.outcome) {
- PushRegistrationOutcome.Ok ->
- stringResource(R.string.title_notificationCheckRegistered) to
- stringResource(R.string.subtitle_notificationCheckRegistered)
- // A failure says what went wrong in the title; these have nothing more to explain.
- PushRegistrationOutcome.NoToken -> stringResource(R.string.title_notificationCheckNoToken) to ""
- PushRegistrationOutcome.InvalidPushToken ->
- stringResource(R.string.title_notificationCheckTokenRejected) to ""
- PushRegistrationOutcome.Transport ->
- stringResource(R.string.title_notificationCheckUnreachable) to
- (registration.lastConfirmedAt?.let {
- val confirmedAt = it.toEpochMilliseconds()
- // DateUtils says "0 minutes ago" under a minute.
- if (System.currentTimeMillis() - confirmedAt < DateUtils.MINUTE_IN_MILLIS) {
- stringResource(R.string.subtitle_notificationCheckLastConfirmedJustNow)
- } else {
- stringResource(
- R.string.subtitle_notificationCheckLastConfirmed,
- DateUtils.getRelativeTimeSpanString(confirmedAt).toString(),
- )
- }
- } ?: stringResource(R.string.subtitle_notificationCheckUnreachable))
- PushRegistrationOutcome.Failed -> stringResource(R.string.title_notificationCheckFailed) to ""
+ PushRegistrationOutcome.Ok -> stringResource(R.string.subtitle_notificationCheckRegistered)
+ // The problem line says what went wrong; these have nothing more to explain.
+ PushRegistrationOutcome.NoToken,
+ PushRegistrationOutcome.InvalidPushToken,
+ PushRegistrationOutcome.Failed -> null
+ PushRegistrationOutcome.Transport -> registration.lastConfirmedAt?.let {
+ val confirmedAt = it.toEpochMilliseconds()
+ // DateUtils says "0 minutes ago" under a minute.
+ if (System.currentTimeMillis() - confirmedAt < DateUtils.MINUTE_IN_MILLIS) {
+ stringResource(R.string.subtitle_notificationCheckLastConfirmedJustNow)
+ } else {
+ stringResource(
+ R.string.subtitle_notificationCheckLastConfirmed,
+ DateUtils.getRelativeTimeSpanString(confirmedAt).toString(),
+ )
+ }
+ } ?: stringResource(R.string.subtitle_notificationCheckUnreachable)
}
}
}
diff --git a/apps/flipcash/features/notification-status/src/test/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitlesTest.kt b/apps/flipcash/features/notification-status/src/test/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitlesTest.kt
new file mode 100644
index 000000000..9729a46d1
--- /dev/null
+++ b/apps/flipcash/features/notification-status/src/test/kotlin/com/flipcash/app/notificationstatus/internal/CheckRowTitlesTest.kt
@@ -0,0 +1,102 @@
+package com.flipcash.app.notificationstatus.internal
+
+import com.flipcash.app.auth.push.PushRegistrationOutcome
+import com.flipcash.core.R
+import org.junit.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.time.Instant
+
+class CheckRowTitlesTest {
+
+ @Test
+ fun `a row keeps its check name as the title whatever its state`() {
+ // The title is picked by key, so pending, checking, passing and problem rows share it.
+ val rows = listOf(
+ CheckRow.Pending(BatteryKey),
+ CheckRow.Rechecking(CheckRow.Battery(optimized = true)),
+ CheckRow.Battery(optimized = false),
+ CheckRow.Battery(optimized = true),
+ )
+ rows.forEach { assertEquals(R.string.title_notificationCheckBattery, checkTitle(it.key), it.toString()) }
+ }
+
+ @Test
+ fun `each blocked channel row is titled by its channel`() {
+ val expected = mapOf(
+ StatusChannel.Chat to R.string.title_notificationCheckChat,
+ StatusChannel.DepositWithdrawal to R.string.title_notificationCheckDeposits,
+ StatusChannel.BuySell to R.string.title_notificationCheckBuySell,
+ StatusChannel.Gain to R.string.title_notificationCheckGain,
+ StatusChannel.ContactJoin to R.string.title_notificationCheckContacts,
+ StatusChannel.General to R.string.title_notificationCheckGeneral,
+ )
+ assertEquals(StatusChannel.withRows.toSet(), expected.keys)
+ expected.forEach { (channel, title) ->
+ assertEquals(title, checkTitle(CheckRow.BlockedChannel(channel).key))
+ }
+ }
+
+ @Test
+ fun `registration row is Registered for Push in every outcome`() {
+ val states = listOf(RegistrationState.Checking) +
+ PushRegistrationOutcome.entries.map { RegistrationState.Finished(it) }
+ states.forEach {
+ assertEquals(R.string.title_notificationCheckRegistered, checkTitle(CheckRow.Registration(it).key))
+ }
+ }
+
+ @Test
+ fun `a registration still running has no problem line`() {
+ // Its detail is "Contacting Flipcash" alone.
+ assertNull(problemLine(CheckRow.Registration(RegistrationState.Checking)))
+ }
+
+ @Test
+ fun `every phone row key has a title`() {
+ (PhoneRowKeys + RegistrationKey).forEach { checkTitle(it) }
+ }
+
+ @Test
+ fun `problem sentence leads the detail of a warning or fail`() {
+ assertEquals(
+ R.string.subtitle_notificationCheckBatteryOnProblem,
+ problemLine(CheckRow.Battery(optimized = true)),
+ )
+ assertEquals(
+ R.string.subtitle_notificationCheckNotAllowedProblem,
+ problemLine(CheckRow.Permission(allowed = false)),
+ )
+ assertEquals(
+ R.string.subtitle_notificationCheckUnreachableProblem,
+ problemLine(CheckRow.Registration(RegistrationState.Finished(PushRegistrationOutcome.Transport))),
+ )
+ }
+
+ @Test
+ fun `every warning or fail row has a problem line and no passing row does`() {
+ val rows = buildList {
+ add(CheckRow.Permission(allowed = true))
+ add(CheckRow.Permission(allowed = false))
+ StatusChannel.withRows.forEach { add(CheckRow.BlockedChannel(it)) }
+ add(CheckRow.OtherChannels(StatusChannel.withRows))
+ add(CheckRow.Battery(optimized = true))
+ add(CheckRow.Battery(optimized = false))
+ listOf("active", "rare").forEach { add(CheckRow.Background(restricted = false, bucket = it)) }
+ add(CheckRow.Background(restricted = true, bucket = "active"))
+ PlayServices.entries.forEach { add(CheckRow.GooglePlay(it, resolvable = true)) }
+ PushRegistrationOutcome.entries.forEach {
+ add(CheckRow.Registration(RegistrationState.Finished(it)))
+ add(CheckRow.Registration(RegistrationState.Finished(it, lastConfirmedAt = Instant.DISTANT_PAST)))
+ }
+ }
+ rows.forEach { row ->
+ if (row.status == CheckStatus.Pass) {
+ assertNull(problemLine(row), row.toString())
+ } else {
+ assertNotNull(problemLine(row), row.toString())
+ }
+ }
+ }
+}
diff --git a/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationIdPlanner.kt b/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationIdPlanner.kt
new file mode 100644
index 000000000..c9c28ffec
--- /dev/null
+++ b/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationIdPlanner.kt
@@ -0,0 +1,19 @@
+package com.flipcash.app.notifications
+
+/**
+ * The id the test push posts under, every time. A new test replaces the last one in the shade
+ * rather than posting beside it: Android 16 auto-groups a second notification from the app and
+ * posts the newcomer silently, so a test sent while the last is still showing would not pop up.
+ * Replacing the notification still alerts, because the builder does not set `onlyAlertOnce`.
+ */
+const val TEST_PUSH_NOTIFICATION_ID = 0x7E57
+
+/**
+ * The id a notification that is not a chat posts under: [TEST_PUSH_NOTIFICATION_ID] on
+ * [NotificationChannels.CHANNEL_TEST_PUSH], and [randomId] otherwise, so other pushes each post a
+ * notification of their own. A chat posts under its chat id and does not come through here.
+ *
+ * @param channelId the id of the channel the notification posts on
+ */
+fun planNotificationId(channelId: String, randomId: () -> Int): Int =
+ if (channelId == NotificationChannels.CHANNEL_TEST_PUSH) TEST_PUSH_NOTIFICATION_ID else randomId()
diff --git a/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationService.kt b/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationService.kt
index 69e8a7032..8214aec52 100644
--- a/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationService.kt
+++ b/apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationService.kt
@@ -377,7 +377,7 @@ class NotificationService : FirebaseMessagingService(),
builder.applyChatStyle(chatId, styling, groupKey, title, body, payload?.chatMetadata)
} else {
builder.setContentTitle(title).setContentText(body)
- SecureRandom().nextInt(Int.MAX_VALUE)
+ planNotificationId(channel.id, randomId = { SecureRandom().nextInt(Int.MAX_VALUE) })
}
notificationManager.notify(notificationId, builder.build())
diff --git a/apps/flipcash/shared/notifications/src/test/kotlin/com/flipcash/app/notifications/NotificationIdPlannerTest.kt b/apps/flipcash/shared/notifications/src/test/kotlin/com/flipcash/app/notifications/NotificationIdPlannerTest.kt
new file mode 100644
index 000000000..543f76db3
--- /dev/null
+++ b/apps/flipcash/shared/notifications/src/test/kotlin/com/flipcash/app/notifications/NotificationIdPlannerTest.kt
@@ -0,0 +1,30 @@
+package com.flipcash.app.notifications
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+
+class NotificationIdPlannerTest {
+
+ @Test
+ fun `test push always posts under the same id`() {
+ // A second test replaces the first instead of joining it in an auto-group, where Android 16
+ // posts it silently.
+ val ids = listOf(1, 2).map { random ->
+ planNotificationId(NotificationChannels.CHANNEL_TEST_PUSH, randomId = { random })
+ }
+ assertEquals(listOf(TEST_PUSH_NOTIFICATION_ID, TEST_PUSH_NOTIFICATION_ID), ids)
+ }
+
+ @Test
+ fun `other pushes keep a random id`() {
+ listOf(
+ NotificationChannels.CHANNEL_DEFAULT,
+ NotificationChannels.CHANNEL_DEPOSIT_WITHDRAWAL,
+ NotificationChannels.CHANNEL_BUY_SELL,
+ NotificationChannels.CHANNEL_GAIN,
+ NotificationChannels.CHANNEL_CONTACT_JOIN,
+ ).forEach { channelId ->
+ assertEquals(42, planNotificationId(channelId, randomId = { 42 }), channelId)
+ }
+ }
+}