Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,12 @@ class MainActivity : FragmentActivity() {

/**
* Test-only: enable beta flags passed as a launch argument, so flag-gated features
* (blocklist, …) can be exercised in UI tests without toggling them in the
* Labs UI. Mirrors iOS's `--beta-flags`. Debug/UI-test builds only.
* can be exercised in UI tests without toggling them in the Labs UI. Mirrors iOS's
* `--beta-flags`. Debug/UI-test builds only.
*
* launchApp:
* arguments:
* betaFlags: "blocklist_enabled"
* betaFlags: "coinbase_onramp_sandbox_enabled"
*
* The value is a comma-separated list of [FeatureFlag.key]s.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ import com.flipcash.app.core.chat.ChatIdentifier
import com.flipcash.app.core.chat.ChatParticipant
import com.flipcash.app.core.contacts.DeviceContact
import com.flipcash.app.core.ui.ConfirmationStyle
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.featureflags.FeatureFlagController
import com.flipcash.shared.chat.models.ChatListItem
import com.flipcash.shared.chat.models.ReceiptStatus
import com.flipcash.shared.chat.models.SeparatorConfig
Expand Down Expand Up @@ -104,7 +102,6 @@ internal class ChatViewModel @Inject constructor(
private val purchaseMethodController: PurchaseMethodController,
private val userManager: UserManager,
private val resources: ResourceHelper,
private val featureFlags: FeatureFlagController,
private val analytics: FlipcashAnalyticsService,
) : BaseViewModel<ChatViewModel.State, ChatViewModel.Event>(
initialState = State(),
Expand Down Expand Up @@ -147,14 +144,10 @@ internal class ChatViewModel @Inject constructor(
// open would be missed by the bottom bar before it subscribes, whereas state is durable
// until the input is actually composed and can consume it.
val messageInputRequested: Boolean = false,
// Whether the Blocklist beta flag is enabled. Backs canViewProfile; observed in init.
val blocklistEnabled: Boolean = false,
) {
// Opening the participant's profile (the entry point to blocking) is only available for tip
// DMs, and only when the Blocklist beta flag is on. Derived so it stays correct regardless
// of whether the flag or the chat type resolves first.
// Opening the participant's profile (the entry point to blocking) is only available for tip DMs.
val canViewProfile: Boolean
get() = blocklistEnabled && chatType == ChatType.TIP_DM
get() = chatType == ChatType.TIP_DM
}

sealed interface Event {
Expand Down Expand Up @@ -199,7 +192,6 @@ internal class ChatViewModel @Inject constructor(
data class LimitsChanged(val limits: Limits?) : Event
data class AdvanceReadPointer(val messageId: Long) : Event
data class ChatDeactivated(val isReadOnly: Boolean) : Event
data class BlocklistEnabledChanged(val enabled: Boolean) : Event
}

@OptIn(ExperimentalCoroutinesApi::class)
Expand Down Expand Up @@ -444,10 +436,6 @@ internal class ChatViewModel @Inject constructor(
.onEach { dispatchEvent(Event.ChatDeactivated(isReadOnly = it)) }
.launchIn(viewModelScope)

featureFlags.observe(FeatureFlag.Blocklist)
.onEach { dispatchEvent(Event.BlocklistEnabledChanged(it)) }
.launchIn(viewModelScope)

// Advance read pointer when user scrolls to messages
eventFlow
.filterIsInstance<Event.AdvanceReadPointer>()
Expand Down Expand Up @@ -916,7 +904,6 @@ internal class ChatViewModel @Inject constructor(
is Event.LimitsChanged -> { state -> state.copy(limits = event.limits) }
is Event.AdvanceReadPointer -> { state -> state }
is Event.ChatDeactivated -> { state -> state.copy(isAnonymous = event.isReadOnly) }
is Event.BlocklistEnabledChanged -> { state -> state.copy(blocklistEnabled = event.enabled) }
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ internal fun MessengerScreen(viewModel: ChatViewModel) {
}

is ChatAction.ViewProfile -> {
// The triggers (top-bar tap, contact-card chevron) are only clickable when the
// Blocklist beta flag is on, so no gating is needed here.
// The triggers (top-bar tap, contact-card chevron) are only clickable for tip DMs
// (see State.canViewProfile), so no gating is needed here.
state.participant?.let {
keyboard.hideIfVisible {
navigator.push(ChatStep.Profile(it))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ internal fun ChatTopBar(
},
title = {
Row(
// Profile open is gated behind the Blocklist beta flag.
// Profile open is only available for tip DMs (see State.canViewProfile).
modifier = Modifier
.fillMaxWidth()
.then(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ internal fun MessageList(
.fillMaxWidth(0.63f),
onRefreshContact = { onAction(ChatAction.RefreshContact) },
// null hides the chevron and makes the card non-tappable when the
// Blocklist beta flag is off.
// profile isn't viewable (non-tip-DM chats).
onOpenProfile = if (canViewProfile) {
{ onAction(ChatAction.ViewProfile) }
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import com.flipcash.app.featureflags.FeatureFlag
import com.flipcash.app.menu.FullMenuItem
import com.flipcash.app.menu.StaffMenuItem
import com.flipcash.core.R as CoreR
Expand All @@ -24,9 +23,7 @@ internal data object AccessKey : FullMenuItem<MyAccountScreenViewModel.Event>()
override val action: MyAccountScreenViewModel.Event = MyAccountScreenViewModel.Event.OnAccessKeyClicked
}

internal data object Blocklist : FullMenuItem<MyAccountScreenViewModel.Event>(
featureFlag = FeatureFlag.Blocklist,
) {
internal data object Blocklist : FullMenuItem<MyAccountScreenViewModel.Event>() {
override val icon: Painter
@Composable get() = rememberVectorPainter(Icons.Outlined.Block)
override val name: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ internal class MyAccountScreenViewModel @Inject constructor(
internal data class State(
val isBetaEnabled: Boolean = false,
// Default hides staff-only AND flag-gated items until the real flag state loads, so a
// beta-gated item (e.g. Blocklist) never flashes before its flag is resolved.
// beta-gated item never flashes before its flag is resolved.
val items: List<MenuItem<Event>> =
FullMenuList.filterNot { it is StaffMenuItem || it.featureFlag != null }
)
Expand Down Expand Up @@ -169,7 +169,7 @@ internal class MyAccountScreenViewModel @Inject constructor(
} else {
FullMenuList.filterNot { item -> item is StaffMenuItem }
}
// Flag-gated items (e.g. Blocklist) only show when their feature flag is enabled.
// Flag-gated items only show when their feature flag is enabled.
return base.filter { item ->
val flag = item.featureFlag ?: return@filter true
flags.find { it.flag.key == flag.key }?.enabled == true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,15 +138,6 @@ sealed interface FeatureFlag<T: Any> {
override val persistLogOut: Boolean = false
}

@FeatureFlagMarker
data object Blocklist: FeatureFlag<Boolean> {
override val key: String = "blocklist_enabled"
override val default: Boolean = false
override val launched: Boolean = false
override val visible: Boolean = true
override val persistLogOut: Boolean = false
}

companion object {
val entries: List<FeatureFlag<*>>
get() = FeatureFlagEntries.entries
Expand All @@ -171,7 +162,6 @@ val FeatureFlag<*>.title: String
FeatureFlag.GiveUsdf -> "Give/Send USDF"
FeatureFlag.ShowNetworkState -> "Network Offline Indicator"
FeatureFlag.FrostedTipCard -> "Frosted Tip Card"
FeatureFlag.Blocklist -> "Blocklist"
}

val FeatureFlag<*>.message: String
Expand All @@ -187,7 +177,6 @@ val FeatureFlag<*>.message: String
FeatureFlag.GiveUsdf -> "When enabled, you'll gain the ability to send USDF directly and give it as cash"
FeatureFlag.ShowNetworkState -> "When enabled, you'll gain the ability to see the network state on the Scanner when offline"
FeatureFlag.FrostedTipCard -> "When enabled, the tip card in the scanner renders as frosted glass over a blurred snapshot of the camera instead of a solid card"
FeatureFlag.Blocklist -> "When enabled, you'll gain the ability to open a chat participant's profile, block them, and manage your blocklist from My Account"
}


Expand Down
4 changes: 2 additions & 2 deletions maestro/blocking.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ name: "Blocking — block & unblock a chat participant"
tags:
- blocklist
---
# Tipping is on by default (to reach the tip chat); enable blocklist at launch.
# Tipping and blocking are on by default; no beta flags needed to reach the tip chat.
- runFlow:
file: subflows/login_with_flags.yaml
env:
BETA_FLAGS: "blocklist_enabled"
BETA_FLAGS: ""

# Open the tip conversation.
- tapOn: "Tips"
Expand Down
Loading