|
| 1 | +package com.flipcash.app.debug |
| 2 | + |
| 3 | +import android.content.BroadcastReceiver |
| 4 | +import android.content.Context |
| 5 | +import android.content.Intent |
| 6 | +import com.flipcash.app.persistence.sources.ChatMemberDataSource |
| 7 | +import com.flipcash.services.models.chat.ChatId |
| 8 | +import com.flipcash.services.models.chat.ReactionUpdate |
| 9 | +import com.flipcash.shared.chat.alerts.ReactionActorProfiles |
| 10 | +import com.flipcash.shared.chat.alerts.ReactionAlertController |
| 11 | +import com.flipcash.shared.chat.alerts.ReactionAlertEvent |
| 12 | +import dagger.hilt.EntryPoint |
| 13 | +import dagger.hilt.InstallIn |
| 14 | +import dagger.hilt.android.EntryPointAccessors |
| 15 | +import dagger.hilt.components.SingletonComponent |
| 16 | +import kotlinx.coroutines.CoroutineScope |
| 17 | +import kotlinx.coroutines.Dispatchers |
| 18 | +import kotlinx.coroutines.SupervisorJob |
| 19 | +import kotlinx.coroutines.delay |
| 20 | +import kotlinx.coroutines.flow.first |
| 21 | +import kotlinx.coroutines.launch |
| 22 | + |
| 23 | +/** |
| 24 | + * Debug builds only: fires canned in-app reaction alerts so the banner can be driven from adb. |
| 25 | + * |
| 26 | + * adb shell am broadcast -a com.flipcash.debug.REACTION_ALERT --es kind reaction |
| 27 | + * adb shell am broadcast -a com.flipcash.debug.REACTION_ALERT --es kind boost |
| 28 | + * adb shell am broadcast -a com.flipcash.debug.REACTION_ALERT --es kind stack |
| 29 | + * |
| 30 | + * The reactor is the first known chat member whose name is `--es name` (default "XCTests"), and the |
| 31 | + * photo comes through the same lookup the real stream uses. `stack` sends four reactors one after |
| 32 | + * another so the card grows from 1 to 4 people. |
| 33 | + */ |
| 34 | +class ReactionAlertDebugReceiver : BroadcastReceiver() { |
| 35 | + |
| 36 | + @EntryPoint |
| 37 | + @InstallIn(SingletonComponent::class) |
| 38 | + interface Deps { |
| 39 | + fun controller(): ReactionAlertController |
| 40 | + fun members(): ChatMemberDataSource |
| 41 | + fun profiles(): ReactionActorProfiles |
| 42 | + } |
| 43 | + |
| 44 | + override fun onReceive(context: Context, intent: Intent) { |
| 45 | + val deps = EntryPointAccessors.fromApplication(context.applicationContext, Deps::class.java) |
| 46 | + val controller = deps.controller() |
| 47 | + val members = deps.members() |
| 48 | + val profiles = deps.profiles() |
| 49 | + val kind = intent.getStringExtra("kind") ?: "reaction" |
| 50 | + val name = intent.getStringExtra("name") ?: "XCTests" |
| 51 | + val pending = goAsync() |
| 52 | + scope.launch { |
| 53 | + try { |
| 54 | + fire(kind, name, controller, members, profiles) |
| 55 | + } finally { |
| 56 | + pending.finish() |
| 57 | + } |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + private suspend fun fire( |
| 62 | + kind: String, |
| 63 | + name: String, |
| 64 | + controller: ReactionAlertController, |
| 65 | + members: ChatMemberDataSource, |
| 66 | + profiles: ReactionActorProfiles, |
| 67 | + ) { |
| 68 | + val all = members.observeAll().first() |
| 69 | + val (chatHex, member) = all.entries.firstNotNullOfOrNull { (hex, list) -> |
| 70 | + list.firstOrNull { it.userProfile.displayName == name }?.let { hex to it } |
| 71 | + } ?: all.entries.firstNotNullOf { (hex, list) -> list.firstOrNull()?.let { hex to it } } |
| 72 | + val chat = ChatId(chatHex.chunked(2).map { it.toInt(16).toByte() }.toByteArray()) |
| 73 | + val resolved = profiles.resolve(member.userId) |
| 74 | + val actorName = resolved?.displayName?.takeIf { it.isNotBlank() } ?: member.userProfile.displayName |
| 75 | + val photo = member.userProfile.profilePicture ?: resolved?.profilePicture |
| 76 | + |
| 77 | + fun event(who: List<Byte>, whoName: String, whoPhoto: Boolean, emoji: String, action: ReactionUpdate.Action, cents: Long? = null) = |
| 78 | + ReactionAlertEvent( |
| 79 | + chatId = chat, |
| 80 | + messageId = 1, |
| 81 | + emoji = emoji, |
| 82 | + actor = who, |
| 83 | + action = action, |
| 84 | + boostDeltaCents = cents, |
| 85 | + actorName = whoName, |
| 86 | + chatName = "Moony", |
| 87 | + quote = "Me! I'll send my \$40 tonight", |
| 88 | + actorPhoto = if (whoPhoto) photo else null, |
| 89 | + ) |
| 90 | + |
| 91 | + when (kind) { |
| 92 | + "boost" -> controller.offer(event(member.userId, actorName, true, "🔥", ReactionUpdate.Action.BOOSTED, 500)) |
| 93 | + "stack" -> { |
| 94 | + val crowd = listOf("Chloe" to "❤️", "Sam" to "😂", "Jo" to "👏") |
| 95 | + controller.offer(event(member.userId, actorName, true, "🔥", ReactionUpdate.Action.ADDED)) |
| 96 | + crowd.forEachIndexed { i, (n, e) -> |
| 97 | + delay(900) |
| 98 | + controller.offer(event(listOf(i.toByte(), 9), n, false, e, ReactionUpdate.Action.ADDED)) |
| 99 | + } |
| 100 | + } |
| 101 | + else -> controller.offer(event(member.userId, actorName, true, "🔥", ReactionUpdate.Action.ADDED)) |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + private companion object { |
| 106 | + val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) |
| 107 | + } |
| 108 | +} |
0 commit comments