Skip to content

Commit 390ec3b

Browse files
committed
fix(flipcash): handle notification presentation while app in foreground
also expose push token when beta enabled Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent bb9219d commit 390ec3b

9 files changed

Lines changed: 127 additions & 19 deletions

File tree

apps/flipcash/app/src/main/AndroidManifest.xml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
<uses-permission android:name="android.permission.CAMERA" />
1111

1212
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
13-
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
1413

1514
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
1615
<!-- needed for network connectivity detection in Api24NetworkObserver -->

apps/flipcash/core/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
<string name="title_clipboardLabelDepositAddress">Deposit Address</string>
104104
<string name="title_clipboardLabelPublicKey">Public Key</string>
105105
<string name="title_clipboardLabelAccountId">Account ID</string>
106+
<string name="title_clipboardLabelPushToken">Push Token</string>
106107

107108
<string name="prompt_description_viewAccessKey">Your Access Key will grant access to your Flipcash account. Keep it private and safe.</string>
108109

apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/AccountInfoHeader.kt

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,26 @@ internal fun AccountInfoHeader(
4747
modifier = Modifier.fillMaxSize(),
4848
verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1)
4949
) {
50-
CopyableTextEntry(
51-
label = "Public Key",
52-
value = state.publicKey.orEmpty()
53-
) { dispatch(MyAccountScreenViewModel.Event.CopyPublicKey) }
50+
if (!state.publicKey.isNullOrEmpty()) {
51+
CopyableTextEntry(
52+
label = "Public Key",
53+
value = state.publicKey
54+
) { dispatch(MyAccountScreenViewModel.Event.CopyPublicKey) }
55+
}
5456

55-
CopyableTextEntry(
56-
label = "Account ID",
57-
value = state.accountId.orEmpty()
58-
) { dispatch(MyAccountScreenViewModel.Event.CopyAccountId) }
57+
if (!state.accountId.isNullOrEmpty()) {
58+
CopyableTextEntry(
59+
label = "Account ID",
60+
value = state.accountId
61+
) { dispatch(MyAccountScreenViewModel.Event.CopyAccountId) }
62+
}
63+
64+
if (!state.pushToken.isNullOrEmpty()) {
65+
CopyableTextEntry(
66+
label = "Push Token",
67+
value = state.pushToken
68+
) { dispatch(MyAccountScreenViewModel.Event.CopyPushToken) }
69+
}
5970

6071
BetaIndicator(
6172
modifier = Modifier.align(Alignment.End)

apps/flipcash/features/myaccount/src/main/kotlin/com/flipcash/app/myaccount/internal/MyAccountScreenViewModel.kt

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,30 +47,42 @@ internal class MyAccountScreenViewModel @Inject constructor(
4747
val showAccountInfo: Boolean = false,
4848
val accountId: String? = null,
4949
val publicKey: String? = null,
50+
val pushToken: String? = null,
5051
val items: List<MenuItem<Event>> = FullMenuList
5152
)
5253

5354
internal sealed interface Event {
54-
data class OnUserAssociated(val userId: String?, val publicKey: String?) : Event
55+
data class OnUserAssociated(
56+
val userId: String?,
57+
val publicKey: String?,
58+
val pushToken: String? = null
59+
) : Event
60+
5561
data class OnBetaFeaturesUnlocked(val unlocked: Boolean) : Event
56-
data object OnTitleClicked: Event
62+
data object OnTitleClicked : Event
5763
data class ToggleAccountInfo(val show: Boolean) : Event
5864
data object OnAccessKeyClicked : Event
59-
data object OnViewAccessKey: Event
65+
data object OnViewAccessKey : Event
6066
data object OnDeleteAccountClicked : Event
6167
data object OnAccountDeleted : Event
6268
data object CopyPublicKey : Event
6369
data object CopyAccountId : Event
70+
data object CopyPushToken : Event
6471
}
6572

6673
init {
6774
userManager.state
68-
.map { it.accountId to it.cluster }
69-
.onEach { (id, cluster) ->
70-
val userId = id?.base58
71-
val publicKey = cluster?.authorityPublicKey?.base58()
72-
73-
dispatchEvent(Event.OnUserAssociated(userId, publicKey))
75+
.onEach {
76+
val userId = it.accountId?.base58
77+
val publicKey = it.cluster?.authorityPublicKey?.base58()
78+
79+
dispatchEvent(
80+
Event.OnUserAssociated(
81+
userId = userId,
82+
publicKey = publicKey,
83+
pushToken = it.pushToken
84+
)
85+
)
7486
}.launchIn(viewModelScope)
7587

7688
combine(
@@ -138,6 +150,16 @@ internal class MyAccountScreenViewModel @Inject constructor(
138150
)
139151
}.launchIn(viewModelScope)
140152

153+
eventFlow
154+
.filterIsInstance<Event.CopyPushToken>()
155+
.mapNotNull { stateFlow.value.pushToken }
156+
.onEach {
157+
clipboardManager.setText(
158+
text = it,
159+
label = resources.getString(R.string.title_clipboardLabelPushToken)
160+
)
161+
}.launchIn(viewModelScope)
162+
141163
eventFlow
142164
.filterIsInstance<Event.OnAccessKeyClicked>()
143165
.onEach {
@@ -161,12 +183,14 @@ internal class MyAccountScreenViewModel @Inject constructor(
161183
state.copy(
162184
accountId = event.userId,
163185
publicKey = event.publicKey,
186+
pushToken = event.pushToken
164187
)
165188
}
166189

167190
Event.OnViewAccessKey,
168191
Event.CopyPublicKey,
169192
Event.CopyAccountId,
193+
Event.CopyPushToken,
170194
Event.OnTitleClicked,
171195
Event.OnDeleteAccountClicked,
172196
Event.OnAccountDeleted,

apps/flipcash/shared/authentication/src/main/kotlin/com/flipcash/app/auth/AuthManager.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ class AuthManager @Inject constructor(
198198
val pushToken = Firebase.messaging.token() ?: return
199199
pushController.addToken(pushToken)
200200
.onSuccess {
201+
userManager.set(pushToken = pushToken)
201202
trace("push token updated", type = TraceType.Silent)
202203
}.onFailure {
203204
trace(message = "Failure updating push token", error = it)

apps/flipcash/shared/notifications/src/main/AndroidManifest.xml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
22

3+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
4+
35
<application>
46
<service
57
android:name="com.flipcash.app.notifications.NotificationService"
@@ -15,7 +17,7 @@
1517

1618
<meta-data
1719
android:name="com.google.firebase.messaging.default_notification_color"
18-
android:value="#FF001A0C" />
20+
android:value="@color/notification_color" />
1921
</application>
2022

2123
</manifest>

apps/flipcash/shared/notifications/src/main/kotlin/com/flipcash/app/notifications/NotificationService.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
package com.flipcash.app.notifications
22

3+
import android.Manifest
4+
import android.app.PendingIntent
5+
import android.content.Context
6+
import android.content.Intent
7+
import android.content.pm.PackageManager
8+
import android.media.RingtoneManager
9+
import android.net.Uri
10+
import androidx.compose.ui.graphics.Color
11+
import androidx.compose.ui.graphics.toArgb
12+
import androidx.core.app.ActivityCompat
13+
import androidx.core.app.NotificationCompat
14+
import androidx.core.app.NotificationManagerCompat
315
import com.flipcash.app.auth.AuthManager
416
import com.flipcash.services.controllers.PushController
517
import com.flipcash.services.user.UserManager
18+
import com.flipcash.shared.notifications.R
619
import com.getcode.utils.TraceType
720
import com.getcode.utils.trace
821
import com.google.firebase.messaging.FirebaseMessagingService
22+
import com.google.firebase.messaging.RemoteMessage
923
import dagger.hilt.android.AndroidEntryPoint
1024
import kotlinx.coroutines.CoroutineScope
1125
import kotlinx.coroutines.Dispatchers
1226
import kotlinx.coroutines.launch
27+
import java.security.SecureRandom
1328
import javax.inject.Inject
1429

1530
@AndroidEntryPoint
@@ -24,12 +39,16 @@ class NotificationService: FirebaseMessagingService(), CoroutineScope by Corouti
2439
@Inject
2540
lateinit var pushController: PushController
2641

42+
@Inject
43+
lateinit var notificationManager: NotificationManagerCompat
44+
2745
override fun onNewToken(token: String) {
2846
super.onNewToken(token)
2947
authenticateIfNeeded {
3048
launch {
3149
pushController.addToken(token)
3250
.onSuccess {
51+
userManager.set(pushToken = token)
3352
trace("push token updated", type = TraceType.Silent)
3453
}.onFailure {
3554
trace(message = "Failure updating push token", error = it)
@@ -38,11 +57,47 @@ class NotificationService: FirebaseMessagingService(), CoroutineScope by Corouti
3857
}
3958
}
4059

60+
override fun onMessageReceived(message: RemoteMessage) {
61+
super.onMessageReceived(message)
62+
message.notification?.let { notification ->
63+
val notificationBuilder: NotificationCompat.Builder =
64+
NotificationCompat.Builder(this, "fcm_fallback_notification_channel")
65+
.setPriority(NotificationCompat.PRIORITY_HIGH)
66+
.setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION))
67+
.setSmallIcon(R.drawable.flipcash_logo)
68+
.setColor(getColor(R.color.notification_color))
69+
.setAutoCancel(true)
70+
.setContentTitle(notification.title)
71+
.setContentText(notification.body)
72+
.setContentIntent(buildContentIntent())
73+
74+
val random = SecureRandom()
75+
val notificationId = random.nextInt(256)
76+
77+
if (ActivityCompat.checkSelfPermission(
78+
this,
79+
Manifest.permission.POST_NOTIFICATIONS
80+
) == PackageManager.PERMISSION_GRANTED
81+
) {
82+
notificationManager.notify(notificationId, notificationBuilder.build())
83+
}
84+
}
85+
}
86+
4187
private fun authenticateIfNeeded(block: () -> Unit) {
4288
if (userManager.accountCluster == null) {
4389
authManager.init { block() }
4490
} else {
4591
block()
4692
}
4793
}
94+
95+
internal fun Context.buildContentIntent(): PendingIntent {
96+
return PendingIntent.getActivity(
97+
this,
98+
99,
99+
packageManager.getLaunchIntentForPackage(packageName),
100+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
101+
)
102+
}
48103
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<resources>
3+
<color name="notification_color">#FF001A0C</color>
4+
</resources>

services/flipcash/src/main/kotlin/com/flipcash/services/user/UserManager.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import com.getcode.opencode.model.core.ID
1212
import com.getcode.opencode.model.core.NoId
1313
import com.getcode.opencode.model.core.uuid
1414
import com.getcode.services.opencode.BuildConfig
15+
import com.google.firebase.ktx.Firebase
16+
import com.google.firebase.messaging.ktx.messaging
1517
import com.hoc081098.channeleventbus.ChannelEventBus
1618
import com.mixpanel.android.mpmetrics.MixpanelAPI
1719
import kotlinx.coroutines.flow.MutableStateFlow
@@ -76,12 +78,17 @@ class UserManager @Inject constructor(
7678
val accountId: ID? = null,
7779
val flags: UserFlags? = null,
7880
val isTimelockUnlocked: Boolean = false,
81+
val pushToken: String? = null,
7982
)
8083

8184
init {
8285
balanceController.onTimelockUnlocked = {
8386
didDetectUnlockedAccount()
8487
}
88+
89+
Firebase.messaging.token.addOnSuccessListener { token ->
90+
set(pushToken = token)
91+
}
8592
}
8693

8794
fun establish(entropy: String) {
@@ -133,6 +140,10 @@ class UserManager @Inject constructor(
133140
associate()
134141
}
135142

143+
fun set(pushToken: String?) {
144+
_state.update { it.copy(pushToken = pushToken) }
145+
}
146+
136147
private fun didDetectUnlockedAccount() {
137148
_state.update {
138149
if (!it.isTimelockUnlocked) {

0 commit comments

Comments
 (0)