Skip to content

Commit ada7e6e

Browse files
committed
feat(balance): add transaction detail viewing via beta flags
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent 5ef3c93 commit ada7e6e

9 files changed

Lines changed: 390 additions & 71 deletions

File tree

apps/flipcash/features/balance/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ dependencies {
5454

5555
implementation(project(":apps:flipcash:core"))
5656
implementation(project(":apps:flipcash:shared:activityfeed"))
57+
implementation(project(":apps:flipcash:shared:featureflags"))
5758
implementation(project(":libs:datetime"))
5859
implementation(project(":libs:logging"))
5960
implementation(project(":libs:messaging"))

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/BalanceScreen.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class BalanceScreen: ModalScreen, NamedScreen, Parcelable {
5353
BalanceScreenContent(viewModel)
5454

5555
RepeatOnLifecycle(Lifecycle.State.RESUMED) {
56-
viewModel.dispatchEvent(BalanceViewModel.Event.UpdateFeed)
56+
viewModel.dispatchEvent(BalanceViewModel.Event.ResetSelections)
5757
}
5858
}
5959
}
@@ -62,5 +62,4 @@ class BalanceScreen: ModalScreen, NamedScreen, Parcelable {
6262
@Composable
6363
fun PreloadBalance() {
6464
val viewModel = getActivityScopedViewModel<BalanceViewModel>()
65-
viewModel.dispatchEvent(BalanceViewModel.Event.UpdateFeed)
6665
}

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceScreenContent.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ internal fun BalanceScreenContent(viewModel: BalanceViewModel) {
5959
.fillParentMaxWidth()
6060
.animateItem(),
6161
message = message,
62-
onCancelRequested = {
63-
viewModel.dispatchEvent(BalanceViewModel.Event.OnCancelRequested(message))
64-
}
62+
canViewDetails = state.canViewDetails,
63+
isExpanded = state.expandedItem == message.id,
64+
dispatch = viewModel::dispatchEvent
6565
)
6666

6767
if (index < feed.itemCount - 1) {

apps/flipcash/features/balance/src/main/kotlin/com/flipcash/app/balance/internal/BalanceViewModel.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import com.flipcash.app.core.extensions.onResult
77
import com.flipcash.app.core.feed.ActivityFeedMessage
88
import com.flipcash.app.core.feed.MessageMetadata
99
import com.flipcash.app.core.money.formatted
10+
import com.flipcash.app.featureflags.FeatureFlag
11+
import com.flipcash.app.featureflags.FeatureFlagController
1012
import com.flipcash.features.balance.R
1113
import com.flipcash.services.user.UserManager
1214
import com.getcode.manager.BottomBarAction
1315
import com.getcode.manager.BottomBarManager
1416
import com.getcode.opencode.controllers.BalanceController
1517
import com.getcode.opencode.controllers.TransactionController
18+
import com.getcode.opencode.model.core.ID
1619
import com.getcode.opencode.model.financial.LocalFiat
1720
import com.getcode.solana.keys.PublicKey
1821
import com.getcode.util.resources.ResourceHelper
@@ -31,6 +34,7 @@ internal class BalanceViewModel @Inject constructor(
3134
balanceController: BalanceController,
3235
feedCoordinator: ActivityFeedCoordinator,
3336
transactionController: TransactionController,
37+
featureFlags: FeatureFlagController,
3438
userManager: UserManager,
3539
resources: ResourceHelper,
3640
) : BaseViewModel2<BalanceViewModel.State, BalanceViewModel.Event>(
@@ -39,11 +43,15 @@ internal class BalanceViewModel @Inject constructor(
3943
) {
4044
data class State(
4145
val balance: LocalFiat? = null,
46+
val canViewDetails: Boolean = false,
47+
val expandedItem: ID? = null,
4248
)
4349

4450
sealed interface Event {
4551
data class OnBalanceUpdated(val balance: LocalFiat) : Event
46-
data object UpdateFeed : Event
52+
data class OnTransactionDetailsEnabled(val enabled: Boolean): Event
53+
data class ViewDetails(val id: ID?): Event
54+
data object ResetSelections : Event
4755
data class OnCancelRequested(val message: ActivityFeedMessage) : Event
4856
data class CancelTransfer(val vault: PublicKey) : Event
4957
}
@@ -53,6 +61,16 @@ internal class BalanceViewModel @Inject constructor(
5361
.onEach { dispatchEvent(Event.OnBalanceUpdated(it)) }
5462
.launchIn(viewModelScope)
5563

64+
featureFlags.observe(FeatureFlag.TransactionDetails)
65+
.onEach { dispatchEvent(Event.OnTransactionDetailsEnabled(it)) }
66+
.launchIn(viewModelScope)
67+
68+
eventFlow
69+
.filterIsInstance<Event.ResetSelections>()
70+
.onEach {
71+
dispatchEvent(Event.ViewDetails(null))
72+
}.launchIn(viewModelScope)
73+
5674
eventFlow
5775
.filterIsInstance<Event.OnCancelRequested>()
5876
.map { it.message }
@@ -120,10 +138,19 @@ internal class BalanceViewModel @Inject constructor(
120138
internal companion object {
121139
val updateStateForEvent: (Event) -> ((State) -> State) = { event ->
122140
when (event) {
123-
Event.UpdateFeed -> { state -> state }
141+
Event.ResetSelections -> { state -> state }
124142
is Event.OnCancelRequested -> { state -> state }
125143
is Event.CancelTransfer -> { state -> state }
126144
is Event.OnBalanceUpdated -> { state -> state.copy(balance = event.balance) }
145+
is Event.OnTransactionDetailsEnabled -> { state -> state.copy(canViewDetails = event.enabled) }
146+
is Event.ViewDetails -> { state ->
147+
val currentlyExpanded = state.expandedItem
148+
if (currentlyExpanded == event.id) {
149+
state.copy(expandedItem = null)
150+
} else {
151+
state.copy(expandedItem = event.id)
152+
}
153+
}
127154
}
128155
}
129156
}
Lines changed: 119 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,149 @@
11
package com.flipcash.app.balance.internal.components
22

3-
import androidx.compose.foundation.clickable
4-
import androidx.compose.foundation.layout.Arrangement
3+
import androidx.compose.animation.AnimatedContent
4+
import androidx.compose.animation.animateColorAsState
5+
import androidx.compose.animation.core.animateDpAsState
6+
import androidx.compose.animation.slideInVertically
7+
import androidx.compose.animation.slideOutVertically
8+
import androidx.compose.animation.togetherWith
9+
import androidx.compose.foundation.background
10+
import androidx.compose.foundation.layout.Box
511
import androidx.compose.foundation.layout.Column
6-
import androidx.compose.foundation.layout.Row
7-
import androidx.compose.foundation.layout.padding
8-
import androidx.compose.material.Text
12+
import androidx.compose.foundation.layout.Spacer
13+
import androidx.compose.foundation.layout.fillMaxWidth
14+
import androidx.compose.material.Surface
915
import androidx.compose.runtime.Composable
10-
import androidx.compose.runtime.derivedStateOf
16+
import androidx.compose.runtime.CompositionLocalProvider
1117
import androidx.compose.runtime.getValue
12-
import androidx.compose.runtime.remember
13-
import androidx.compose.ui.Alignment
1418
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.graphics.compositeOver
21+
import androidx.compose.ui.platform.LocalContext
22+
import androidx.compose.ui.tooling.preview.Preview
23+
import androidx.compose.ui.unit.dp
24+
import com.flipcash.app.balance.internal.BalanceViewModel
1525
import com.flipcash.app.core.feed.ActivityFeedMessage
1626
import com.flipcash.app.core.feed.MessageMetadata
17-
import com.flipcash.app.core.ui.FlagWithFiat
18-
import com.flipcash.services.models.ActivityFeedNotification
19-
import com.flipcash.services.models.NotificationMetadata
27+
import com.flipcash.app.core.feed.MessageState
28+
import com.flipcash.app.theme.FlipcashDesignSystem
29+
import com.getcode.ed25519.Ed25519
30+
import com.getcode.opencode.compose.ExchangeStub
31+
import com.getcode.opencode.compose.LocalExchange
2032
import com.getcode.opencode.model.financial.CurrencyCode
33+
import com.getcode.opencode.model.financial.LocalFiat
34+
import com.getcode.opencode.model.financial.Rate
35+
import com.getcode.opencode.model.financial.toFiat
36+
import com.getcode.solana.keys.PublicKey
2137
import com.getcode.theme.CodeTheme
22-
import com.getcode.util.DateUtils
23-
import com.getcode.util.format
38+
import com.getcode.utils.decodeBase58
2439
import kotlinx.datetime.Instant
2540

2641
@Composable
2742
internal fun FeedItem(
2843
message: ActivityFeedMessage,
44+
canViewDetails: Boolean,
45+
isExpanded: Boolean,
2946
modifier: Modifier = Modifier,
30-
onCancelRequested: () -> Unit,
47+
dispatch: (BalanceViewModel.Event) -> Unit,
3148
) {
32-
val canCancel by remember(message.metadata) {
33-
derivedStateOf {
34-
message.metadata ?: return@derivedStateOf false
35-
val metadata = (message.metadata as? MessageMetadata.SentUsdc) ?: return@derivedStateOf false
36-
metadata.canCancel
37-
}
38-
}
49+
val elevation by animateDpAsState(if (isExpanded) 8.dp else 0.dp)
3950

40-
Row(
41-
modifier = modifier
42-
.clickable(enabled = canCancel) { onCancelRequested() }
43-
.padding(
44-
horizontal = CodeTheme.dimens.inset,
45-
vertical = CodeTheme.dimens.inset,
46-
),
47-
horizontalArrangement = Arrangement.SpaceBetween
51+
Column(
52+
modifier = modifier,
4853
) {
49-
Column(
50-
modifier = Modifier.weight(1f),
54+
Surface(
55+
color = CodeTheme.colors.background,
56+
elevation = elevation,
5157
) {
52-
Text(
53-
text = message.text,
54-
style = CodeTheme.typography.textMedium,
55-
color = CodeTheme.colors.textMain
56-
)
57-
Text(
58-
text = message.timestamp.formatRelativeToToday(),
59-
style = CodeTheme.typography.textSmall,
60-
color = CodeTheme.colors.textSecondary
58+
FeedItemSummary(
59+
message = message,
60+
canViewDetails = canViewDetails,
61+
modifier = Modifier
62+
.fillMaxWidth(),
63+
dispatch = dispatch,
6164
)
6265
}
6366

64-
message.amount?.let { amount ->
65-
Column(
66-
horizontalAlignment = Alignment.End
67-
) {
68-
FlagWithFiat(fiat = amount.converted)
67+
AnimatedContent(
68+
targetState = isExpanded,
69+
modifier = Modifier.fillMaxWidth(),
70+
transitionSpec = {
71+
slideInVertically { -it } togetherWith slideOutVertically { -it }
72+
}
73+
) { expanded ->
74+
if (expanded) {
75+
FeedItemDetails(
76+
message = message,
77+
modifier = Modifier.fillMaxWidth(),
78+
dispatch = dispatch
79+
)
80+
} else {
81+
Spacer(modifier = Modifier.fillMaxWidth())
82+
}
83+
}
84+
}
85+
}
86+
87+
88+
private val cadUsdRate = Rate(fx = 1.371881, currency = CurrencyCode.CAD)
89+
private val usdCadRate = Rate(fx = 1.0 / 1.371881, currency = CurrencyCode.CAD)
90+
private val oneDollarCad = 1.00.toFiat(CurrencyCode.CAD)
91+
92+
val rates = mapOf(
93+
CurrencyCode.CAD to cadUsdRate,
94+
CurrencyCode.USD to usdCadRate
95+
)
96+
private val oneDollarLocalized = LocalFiat(
97+
usdc = oneDollarCad.convertingTo(usdCadRate),
98+
converted = oneDollarCad,
99+
)
100+
private val sampleItem = ActivityFeedMessage(
101+
id = "3GHjGey5F3fVProC3mYpiBpi7dCegFNz3wYtHSTiQnPt".decodeBase58().toList(),
102+
text = "Gave",
103+
amount = oneDollarLocalized,
104+
timestamp = Instant.parse("2025-06-03T16:25:00-04:00"),
105+
state = MessageState.COMPLETED,
106+
metadata = MessageMetadata.GaveUsdc
107+
)
108+
109+
@Preview
110+
@Composable
111+
private fun Preview_CollapsedItem() {
112+
FlipcashDesignSystem {
113+
CompositionLocalProvider(
114+
LocalExchange provides ExchangeStub(
115+
providedRates = rates,
116+
context = LocalContext.current
117+
)
118+
) {
119+
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
120+
FeedItem(
121+
message = sampleItem,
122+
isExpanded = false,
123+
canViewDetails = true,
124+
) { }
69125
}
70126
}
71127
}
72128
}
73129

74-
private fun Instant.formatRelativeToToday(): String {
75-
if (DateUtils.isToday(toEpochMilliseconds())) {
76-
return format("hh:mm a")
130+
@Preview
131+
@Composable
132+
private fun Preview_ExpandedItem() {
133+
FlipcashDesignSystem {
134+
CompositionLocalProvider(
135+
LocalExchange provides ExchangeStub(
136+
providedRates = rates,
137+
context = LocalContext.current
138+
)
139+
) {
140+
Box(modifier = Modifier.background(CodeTheme.colors.background)) {
141+
FeedItem(
142+
message = sampleItem,
143+
isExpanded = true,
144+
canViewDetails = true,
145+
) { }
146+
}
147+
}
77148
}
78-
return format("MMMM dd, yyyy")
79149
}

0 commit comments

Comments
 (0)