Skip to content

Commit 3c73fa1

Browse files
committed
feat(tokens): add GiveUsdf flag with auto-switch on disable
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 5c67fd7 commit 3c73fa1

4 files changed

Lines changed: 46 additions & 3 deletions

File tree

apps/flipcash/shared/featureflags/src/main/kotlin/com/flipcash/app/featureflags/FeatureFlag.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,15 @@ sealed interface FeatureFlag<T: Any> {
216216
override val persistLogOut: Boolean = false
217217
}
218218

219+
@FeatureFlagMarker
220+
data object GiveUsdf: FeatureFlag<Boolean> {
221+
override val key: String = "give_usdf_enabled"
222+
override val default: Boolean = false
223+
override val launched: Boolean = false
224+
override val visible: Boolean = true
225+
override val persistLogOut: Boolean = false
226+
}
227+
219228
@FeatureFlagMarker
220229
data object NavBar : FeatureFlag<NavBarConfig> {
221230
override val key: String = "nav_bar_config"
@@ -260,6 +269,7 @@ val FeatureFlag<*>.title: String
260269
FeatureFlag.OnboardingPhoneVerification -> "Onboarding Phone Verification"
261270
FeatureFlag.Messenger -> "Messenger"
262271
FeatureFlag.NavBar -> "Navigation Bar"
272+
FeatureFlag.GiveUsdf -> "Give USDF"
263273
}
264274

265275
val FeatureFlag<*>.message: String
@@ -285,6 +295,7 @@ val FeatureFlag<*>.message: String
285295
FeatureFlag.OnboardingPhoneVerification -> "When enabled, new accounts will be prompted to verify their phone number during onboarding"
286296
FeatureFlag.Messenger -> "When enabled, tapping a contact will open the chat messenger instead of navigating directly to send"
287297
FeatureFlag.NavBar -> "Customize the order and labels of navigation bar buttons"
298+
FeatureFlag.GiveUsdf -> "When enabled, you'll gain the ability to send USDF directly"
288299
}
289300

290301

apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenCoordinator.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import androidx.datastore.preferences.preferencesDataStoreFile
1010
import androidx.lifecycle.DefaultLifecycleObserver
1111
import androidx.lifecycle.LifecycleOwner
1212
import androidx.lifecycle.ProcessLifecycleOwner
13+
import com.flipcash.app.featureflags.FeatureFlag
14+
import com.flipcash.app.featureflags.FeatureFlagController
1315
import com.flipcash.app.persistence.sources.TokenDataSource
1416
import com.flipcash.app.tokens.core.ReservesBalanceProvider
1517
import com.getcode.opencode.controllers.AccountController
@@ -85,6 +87,7 @@ class TokenCoordinator @Inject constructor(
8587
private val exchange: Exchange,
8688
private val verifiedFiatCalculator: VerifiedFiatCalculator,
8789
private val dataSource: TokenDataSource,
90+
private val featureFlags: FeatureFlagController,
8891
) : TokenMetadataProvider, SessionListener, DefaultLifecycleObserver, ReservesBalanceProvider {
8992

9093
companion object {
@@ -166,6 +169,12 @@ class TokenCoordinator @Inject constructor(
166169
exchange.updateUserMints(mints)
167170
}
168171
.launchIn(scope)
172+
173+
// Re-evaluate selected token when GiveUsdf flag changes
174+
featureFlags.observe(FeatureFlag.GiveUsdf)
175+
.filter { _hydrated.value }
176+
.onEach { ensureValidTokenSelection() }
177+
.launchIn(scope)
169178
}
170179

171180
override fun onStart(owner: LifecycleOwner) {
@@ -516,10 +525,14 @@ class TokenCoordinator @Inject constructor(
516525
?.get(mintPreferenceKey)
517526
?.let { Mint(it) }
518527

528+
val canGiveUsdf = featureFlags.get(FeatureFlag.GiveUsdf)
529+
val excludedMints = if (!canGiveUsdf) setOf(Mint.usdf) else emptySet()
530+
519531
val resolved = resolveTokenSelection(
520532
balances = _state.value.balances,
521533
currentSelection = currentSelection,
522534
rate = exchange.preferredRate,
535+
excludedMints = excludedMints,
523536
)
524537

525538
if (resolved != null && resolved != currentSelection) {

apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/TokenSelectionResolver.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@ internal fun resolveTokenSelection(
1515
balances: Map<Mint, Fiat>,
1616
currentSelection: Mint?,
1717
rate: Rate,
18+
excludedMints: Set<Mint> = emptySet(),
1819
): Mint? {
1920
if (balances.isEmpty()) return null
2021

22+
val eligible = balances.filterKeys { it !in excludedMints }
23+
2124
val baseline = 0.01.toFiat(rate.currency)
2225

2326
fun Fiat.meetsThreshold(): Boolean {
@@ -26,15 +29,15 @@ internal fun resolveTokenSelection(
2629
}
2730

2831
// Keep current selection if it still meets the threshold
29-
if (currentSelection != null) {
30-
val balance = balances[currentSelection]
32+
if (currentSelection != null && currentSelection !in excludedMints) {
33+
val balance = eligible[currentSelection]
3134
if (balance != null && balance.meetsThreshold()) {
3235
return currentSelection
3336
}
3437
}
3538

3639
// Fall back to the highest balance that meets the threshold
37-
return balances
40+
return eligible
3841
.filter { it.value.meetsThreshold() }
3942
.maxByOrNull { it.value }
4043
?.key

apps/flipcash/shared/tokens/src/main/kotlin/com/flipcash/app/tokens/ui/SelectTokenViewModel.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class SelectTokenViewModel @Inject constructor(
4343
exchange: Exchange,
4444
resources: ResourceHelper,
4545
dispatchers: DispatcherProvider,
46+
featureFlags: FeatureFlagController,
4647
) : BaseViewModel<SelectTokenViewModel.State, SelectTokenViewModel.Event>(
4748
initialState = State(purpose = TokenPurpose.Balance),
4849
updateStateForEvent = updateStateForEvent,
@@ -52,6 +53,7 @@ class SelectTokenViewModel @Inject constructor(
5253
data class State(
5354
val purpose: TokenPurpose,
5455
val rate: Rate = Rate.oneToOne,
56+
val canGiveUsdf: Boolean = false,
5557
val discoveryEnabled: Boolean = false,
5658
val tokens: List<TokenWithLocalizedBalance>? = null,
5759
val selectedToken: Mint? = null,
@@ -85,6 +87,8 @@ class SelectTokenViewModel @Inject constructor(
8587
data object OnTokenChanged : Event
8688

8789
data class OpenScreen(val route: AppRoute) : Event
90+
91+
data class OnCanGiveUsdf(val enabled: Boolean): Event
8892
}
8993

9094
init {
@@ -93,6 +97,10 @@ class SelectTokenViewModel @Inject constructor(
9397
.onEach { dispatchEvent(Event.OnRateChanged(it)) }
9498
.launchIn(viewModelScope)
9599

100+
featureFlags.observe(FeatureFlag.GiveUsdf)
101+
.onEach { dispatchEvent(Event.OnCanGiveUsdf(it)) }
102+
.launchIn(viewModelScope)
103+
96104
eventFlow
97105
.filterIsInstance<Event.OnPurposeChanged>()
98106
.map { it.purpose }
@@ -156,6 +164,13 @@ class SelectTokenViewModel @Inject constructor(
156164
when (purpose) {
157165
// show all tokens we have accounts for as deposit targets
158166
TokenPurpose.Deposit -> true
167+
TokenPurpose.Select -> {
168+
if (it.token.address == Mint.usdf) {
169+
stateFlow.value.canGiveUsdf && hasBalance
170+
} else {
171+
hasBalance
172+
}
173+
}
159174
// show all tokens with non-zero balance
160175
else -> hasBalance
161176
}
@@ -191,6 +206,7 @@ class SelectTokenViewModel @Inject constructor(
191206
is Event.OnTokenSelected -> { state -> state.copy(selectedToken = event.mint) }
192207
is Event.OnTokenChanged -> { state -> state }
193208
is Event.OpenScreen -> { state -> state }
209+
is Event.OnCanGiveUsdf -> { state -> state.copy(canGiveUsdf = event.enabled) }
194210
}
195211
}
196212
}

0 commit comments

Comments
 (0)