Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
0240397
refactor(analytics): add the track interface beside the legacy service
bmc08gt Sep 24, 2026
9fdc2a6
refactor(analytics): convert Android domain types for the shared buil…
bmc08gt Sep 24, 2026
8b536af
refactor(analytics): move chat events onto the shared builders
bmc08gt Sep 24, 2026
fe474f9
refactor(analytics): move transfer events onto the shared builders
bmc08gt Sep 24, 2026
92a4a64
refactor(analytics): move add money events onto the shared builders
bmc08gt Sep 24, 2026
e048268
refactor(analytics): move onramp events onto the shared builders
bmc08gt Sep 24, 2026
ef0d7db
refactor(analytics): move wallet events onto the shared builders
bmc08gt Sep 24, 2026
76e9b68
refactor(analytics): move token info events onto the shared builders
bmc08gt Sep 24, 2026
256afe6
refactor(analytics): move buy and sell events onto the shared builders
bmc08gt Sep 24, 2026
692a554
refactor(analytics): move scan events onto the shared builders
bmc08gt Sep 24, 2026
f92acd9
refactor(analytics): move deeplink events onto the shared builders
bmc08gt Sep 24, 2026
d956e4e
refactor(analytics): move display name events onto the shared builders
bmc08gt Sep 24, 2026
df97cdf
refactor(analytics): move error modal events onto the shared builders
bmc08gt Sep 24, 2026
2b275b1
refactor(analytics): move button and account events onto the shared b…
bmc08gt Sep 24, 2026
d5027e5
refactor(analytics): delete the legacy analytics service
bmc08gt Sep 24, 2026
5b827db
test(chat): pin State: Failure on a failed chat send
bmc08gt Sep 24, 2026
efc71a2
refactor(analytics): move RecordingAnalytics into test fixtures
bmc08gt Sep 24, 2026
e5c12b4
refactor(tipping): drop the unused analytics dependency from TippingC…
bmc08gt Sep 24, 2026
5b53fb0
test(messenger): run the chat send-failure test on the test dispatcher
bmc08gt Sep 24, 2026
d6a9c63
fix(tokens): track currency-info opens and shares from the card overlay
bmc08gt Sep 25, 2026
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 @@ -12,7 +12,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.android.BuildConfig
import com.flipcash.app.appsettings.AppSettingsCoordinator
import com.flipcash.app.appsettings.LocalAppSettings
Expand Down Expand Up @@ -49,7 +49,7 @@ import com.flipcash.app.updates.AppUpdateController
import com.flipcash.app.updates.LocalAppUpdater
import com.flipcash.services.user.UserManager
import com.flipcash.shared.tipping.TippingCoordinator
import com.getcode.libs.analytics.LocalAnalytics
import com.flipcash.app.analytics.LocalAnalytics
import com.getcode.opencode.compose.LocalExchange
import com.getcode.opencode.exchange.Exchange
import com.getcode.ui.testing.LocalUiTesting
Expand Down Expand Up @@ -118,7 +118,7 @@ class MainActivity : FragmentActivity() {
lateinit var featureFlagController: FeatureFlagController

@Inject
lateinit var analytics: FlipcashAnalyticsService
lateinit var analytics: FlipcashAnalytics

@Inject
lateinit var phoneUtils: PhoneUtils
Expand Down Expand Up @@ -241,4 +241,4 @@ private fun Activity.handleUncaughtException() {
killProcess(myPid())
exitProcess(2)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.scene.OverlayScene
import androidx.navigation3.scene.SinglePaneSceneStrategy
import com.flipcash.analytics.events.DeeplinkEvents
import com.flipcash.app.analytics.rememberAnalytics
import com.flipcash.app.analytics.withoutQueryOrFragment
import com.flipcash.app.android.BuildConfig
import com.flipcash.app.bill.customization.BillPlaygroundScaffold
import com.flipcash.app.cardexpand.CardExpansionController
Expand Down Expand Up @@ -117,7 +119,7 @@ internal fun App(
var deeplinkHandled by remember { mutableStateOf(false) }
val userManager = LocalUserManager.current!!
DeepLinkListener {
analytics.deeplinkOpened(it.data)
analytics.track(DeeplinkEvents.open(it.data.withoutQueryOrFragment()))
deepLink = it
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.compose.runtime.remember
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavEntryDecorator
import androidx.navigation3.runtime.NavKey
import com.flipcash.analytics.events.ErrorModalEvents
import com.flipcash.app.analytics.rememberAnalytics
import com.flipcash.app.core.AppRoute
import com.getcode.manager.BottomBarManager
Expand Down Expand Up @@ -46,11 +47,13 @@ fun NavMessagingEntryDecorator(
BottomBarManager.BottomBarMessageType.DESTRUCTIVE -> Unit
BottomBarManager.BottomBarMessageType.ERROR -> {
val screen = backStack.lastOrNull()?.screenName()
analytics.displayedErrorModal(
title = message.title,
message = message.subtitle,
screen = screen,
callSite = message.callSite,
analytics.track(
ErrorModalEvents.displayed(
title = message.title,
message = message.subtitle,
screen = screen,
callSite = message.callSite,
)
)
}
BottomBarManager.BottomBarMessageType.WARNING -> Unit
Expand Down
1 change: 1 addition & 0 deletions apps/flipcash/features/balance/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies {

implementation(project(":apps:flipcash:shared:transaction-history"))
implementation(project(":apps:flipcash:shared:analytics"))
testImplementation(testFixtures(project(":apps:flipcash:shared:analytics")))
implementation(project(":apps:flipcash:shared:chat"))
implementation(project(":apps:flipcash:shared:featureflags"))
implementation(project(":apps:flipcash:shared:funding"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.flipcash.app.balance.internal

import androidx.lifecycle.viewModelScope
import com.flipcash.app.analytics.Analytics
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.core.ui.onboarding.TutorialItem
import com.flipcash.app.core.AppRoute
import com.flipcash.shared.transactionhistory.ActivityFeedCoordinator
Expand Down Expand Up @@ -35,7 +36,7 @@ internal class WalletViewModel @Inject constructor(
userFlags: UserFlagsCoordinator,
dispatchers: DispatcherProvider,
purchaseMethodController: PurchaseMethodController,
analytics: FlipcashAnalyticsService,
analytics: FlipcashAnalytics,
chatCoordinator: ChatCoordinator,
feedCoordinator: ActivityFeedCoordinator,
tokenCoordinator: TokenCoordinator,
Expand Down Expand Up @@ -176,7 +177,7 @@ internal class WalletViewModel @Inject constructor(
eventFlow
.filterIsInstance<Event.PresentDepositOptions>()
.mapNotNull {
analytics.addMoneyOpened(Analytics.AddMoneySource.Balance)
analytics.track(AddMoneyEvents.opened(AddMoneySource.BALANCE))
purchaseMethodController.presentDepositOptions(popToRoot = true) }
.onEach { route -> dispatchEvent(Event.OpenScreen(route)) }
.launchIn(viewModelScope)
Expand Down Expand Up @@ -243,4 +244,4 @@ internal class WalletViewModel @Inject constructor(
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.flipcash.app.balance.internal

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.flipcash.app.analytics.StubFlipcashAnalytics
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.app.analytics.RecordingAnalytics
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.MainCoroutineRule
import com.flipcash.app.core.dispatchers.TestDispatchers
Expand Down Expand Up @@ -55,14 +57,16 @@ class BalanceViewModelTest {
every { pending } returns MutableStateFlow(null)
}

private val analytics = RecordingAnalytics()

private lateinit var dispatchers: TestDispatchers

private fun createViewModel() = WalletViewModel(
userManager = userManager,
userFlags = userFlags,
dispatchers = dispatchers,
purchaseMethodController = purchaseMethodController,
analytics = StubFlipcashAnalytics(),
analytics = analytics,
chatCoordinator = chatCoordinator,
feedCoordinator = feedCoordinator,
tokenCoordinator = tokenCoordinator,
Expand All @@ -83,6 +87,7 @@ class BalanceViewModelTest {
// The removed AddMoneyUX flag used to short-circuit to a plain Deposit route;
// the add-money sheet must now always be presented.
coVerify(exactly = 1) { purchaseMethodController.presentDepositOptions(popToRoot = true) }
assertEquals(listOf(AddMoneyEvents.opened(AddMoneySource.BALANCE)), analytics.events)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.flipcash.app.balance.internal

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.flipcash.app.analytics.StubFlipcashAnalytics
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.core.ui.onboarding.TutorialItem
import com.flipcash.app.core.MainCoroutineRule
import com.flipcash.app.core.dispatchers.TestDispatchers
Expand Down Expand Up @@ -67,7 +67,7 @@ class WalletMilestoneGatingTest {
userFlags = mockk<UserFlagsCoordinator>(relaxed = true),
dispatchers = dispatchers,
purchaseMethodController = mockk<PurchaseMethodController>(relaxed = true),
analytics = StubFlipcashAnalytics(),
analytics = FlipcashAnalytics.None,
chatCoordinator = chatCoordinator,
feedCoordinator = feedCoordinator,
tokenCoordinator = tokenCoordinator,
Expand Down
1 change: 1 addition & 0 deletions apps/flipcash/features/cash/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation(libs.kotlin.stdlib)
implementation(project(":apps:flipcash:shared:amount-entry"))
implementation(project(":apps:flipcash:shared:analytics"))
testImplementation(testFixtures(project(":apps:flipcash:shared:analytics")))
implementation(project(":apps:flipcash:shared:featureflags"))
implementation(project(":apps:flipcash:shared:session"))
implementation(project(":apps:flipcash:shared:tokens"))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.flipcash.app.cash.internal

import androidx.lifecycle.viewModelScope
import com.flipcash.app.analytics.Analytics
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.core.AppRoute
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.core.tokens.SwapPurpose
Expand Down Expand Up @@ -58,7 +59,7 @@ internal class CashScreenViewModel @Inject constructor(
private val verifiedFiatCalculator: VerifiedFiatCalculator,
tokenCoordinator: TokenCoordinator,
transactionController: TransactionOperations,
analytics: FlipcashAnalyticsService,
analytics: FlipcashAnalytics,
dispatchers: DispatcherProvider,
) : BaseViewModel<CashScreenViewModel.State, CashScreenViewModel.Event>(
initialState = State(),
Expand Down Expand Up @@ -289,7 +290,7 @@ internal class CashScreenViewModel @Inject constructor(
// route directly to the swap amount screen, skipping token info
val mint = stateFlow.value.selectedTokenAddress!!
if (mint == Mint.usdf) {
analytics.addMoneyOpened(Analytics.AddMoneySource.GiveShortfall)
analytics.track(AddMoneyEvents.opened(AddMoneySource.GIVE_SHORTFALL))
}
dispatchEvent(
Event.OpenScreen(
Expand Down Expand Up @@ -340,4 +341,4 @@ internal class CashScreenViewModel @Inject constructor(
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.flipcash.app.cash.internal

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import com.flipcash.app.analytics.StubFlipcashAnalytics
import com.flipcash.analytics.AddMoneySource
import com.flipcash.analytics.events.AddMoneyEvents
import com.flipcash.app.analytics.RecordingAnalytics
import com.flipcash.app.core.MainCoroutineRule
import com.flipcash.app.core.dispatchers.TestDispatchers
import com.flipcash.app.tokens.TokenCoordinator
Expand Down Expand Up @@ -51,6 +53,8 @@ class CashScreenViewModelTest {
private val tokenCoordinator: TokenCoordinator = mockk(relaxed = true)
private val transactionController: TransactionOperations = mockk(relaxed = true)

private val analytics = RecordingAnalytics()

private lateinit var dispatchers: TestDispatchers

private val updateStateForEvent = CashScreenViewModel.updateStateForEvent
Expand Down Expand Up @@ -79,7 +83,7 @@ class CashScreenViewModelTest {
verifiedFiatCalculator = verifiedFiatCalculator,
tokenCoordinator = tokenCoordinator,
transactionController = transactionController,
analytics = StubFlipcashAnalytics(),
analytics = analytics,
dispatchers = dispatchers,
)
}
Expand Down Expand Up @@ -266,4 +270,26 @@ class CashScreenViewModelTest {
val result = vm.checkSendLimit()
assertFalse(result, "checkSendLimit should return false when amount is within limit")
}

@Test
fun `a USDF shortfall tracks Add Money Opened from Give Shortfall`() = runTest(mainCoroutineRule.dispatcher) {
dispatchers = TestDispatchers(testScheduler)
val vm = createViewModel()
vm.dispatchEvent(CashScreenViewModel.Event.OnTokenSelected(Mint.usdf))
vm.dispatchEvent(CashScreenViewModel.Event.AddCashToWallet(Fiat(5.0)))
advanceUntilIdle()

assertEquals(listOf(AddMoneyEvents.opened(AddMoneySource.GIVE_SHORTFALL)), analytics.events)
}

@Test
fun `a shortfall in another token tracks nothing`() = runTest(mainCoroutineRule.dispatcher) {
dispatchers = TestDispatchers(testScheduler)
val vm = createViewModel()
vm.dispatchEvent(CashScreenViewModel.Event.OnTokenSelected(Mint("kinXdEcpDQeHPEuQnqmUgtYykqKGVFq6CeVX5iAHJq6")))
vm.dispatchEvent(CashScreenViewModel.Event.AddCashToWallet(Fiat(5.0)))
advanceUntilIdle()

assertTrue(analytics.events.isEmpty())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import com.flipcash.app.analytics.Analytics
import com.flipcash.analytics.OnrampStep
import com.flipcash.analytics.events.OnrampEvents
import com.flipcash.app.analytics.rememberAnalytics
import com.flipcash.app.contact.verification.internal.email.EmailMagicLinkScreen
import com.flipcash.app.contact.verification.internal.email.EmailVerificationViewModel
Expand Down Expand Up @@ -43,7 +44,7 @@ fun EmailMagicLinkContent(

val analytics = rememberAnalytics()
LaunchedEffect(Unit) {
analytics.onrampVerification(Analytics.OnrampVerificationStep.ConfirmEmail)
analytics.track(OnrampEvents.step(OnrampStep.CONFIRM_EMAIL))
}

Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.flipcash.app.analytics.Analytics
import com.flipcash.analytics.OnrampStep
import com.flipcash.analytics.events.OnrampEvents
import com.flipcash.app.analytics.rememberAnalytics
import com.flipcash.app.contact.verification.internal.email.EmailEntryScreen
import com.flipcash.app.contact.verification.internal.email.EmailVerificationViewModel
Expand Down Expand Up @@ -56,7 +57,7 @@ fun EmailVerificationContent(

val analytics = rememberAnalytics()
LaunchedEffect(Unit) {
analytics.onrampVerification(Analytics.OnrampVerificationStep.EnterEmail)
analytics.track(OnrampEvents.step(OnrampStep.ENTER_EMAIL))
}

BackHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewWrapper
import com.flipcash.app.analytics.Analytics
import com.flipcash.analytics.OnrampStep
import com.flipcash.analytics.events.OnrampEvents
import com.flipcash.app.analytics.rememberAnalytics
import com.flipcash.app.core.verification.VerificationResult
import com.flipcash.app.core.verification.VerificationStep
Expand Down Expand Up @@ -55,7 +56,7 @@ fun VerificationIntroScreen(

val analytics = rememberAnalytics()
LaunchedEffect(Unit) {
analytics.onrampVerification(Analytics.OnrampVerificationStep.ShowInfo)
analytics.track(OnrampEvents.step(OnrampStep.SHOW_INFO))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.flipcash.app.contact.verification.internal.phone
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.runtime.snapshotFlow
import androidx.lifecycle.viewModelScope
import com.flipcash.app.analytics.Action
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.analytics.events.AccountEvents
import com.flipcash.app.analytics.FlipcashAnalytics
import com.flipcash.app.core.extensions.onResult
import com.flipcash.app.featureflags.FeatureFlagController
import com.flipcash.app.phone.CountryLocale
Expand Down Expand Up @@ -52,7 +52,7 @@ internal class PhoneVerificationViewModel @Inject constructor(
private val featureFlags: FeatureFlagController,
private val resources: ResourceHelper,
private val dispatchers: DispatcherProvider,
private val analytics: FlipcashAnalyticsService,
private val analytics: FlipcashAnalytics,
) : BaseViewModel<PhoneVerificationViewModel.State, PhoneVerificationViewModel.Event>(
initialState = State(),
updateStateForEvent = updateStateForEvent,
Expand Down Expand Up @@ -189,7 +189,7 @@ internal class PhoneVerificationViewModel @Inject constructor(

viewModelScope.launch {
delay(1.seconds)
analytics.action(Action.VerifiedPhoneNumber)
analytics.track(AccountEvents.verifiedPhoneNumber())
dispatchEvent(Event.OnCodeVerified)
dispatchEvent(Event.OnVerifyingCodeChanged())
}
Expand Down Expand Up @@ -239,7 +239,7 @@ internal class PhoneVerificationViewModel @Inject constructor(
.onResult(
onSuccess = {
trace(tag = traceTag, message = "linkForPayment succeeded", type = TraceType.Process)
analytics.action(Action.LinkedPhoneNumber)
analytics.track(AccountEvents.linkedPhoneNumber())
dispatchEvent(Event.OnPhoneVerificationComplete)
},
onError = {
Expand Down Expand Up @@ -315,7 +315,7 @@ internal class PhoneVerificationViewModel @Inject constructor(
}

if (!isResend) {
analytics.action(Action.EnteredPhoneNumber)
analytics.track(AccountEvents.enteredPhoneNumber())
}

trace(tag = traceTag, message = if (isResend) "Resending verification code" else "Sending verification code", type = TraceType.Process)
Expand Down Expand Up @@ -432,4 +432,4 @@ internal class PhoneVerificationViewModel @Inject constructor(
}
}
}
}
}
Loading
Loading