diff --git a/apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/internal/phone/PhoneVerificationViewModel.kt b/apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/internal/phone/PhoneVerificationViewModel.kt index 6832454a6..aca38e216 100644 --- a/apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/internal/phone/PhoneVerificationViewModel.kt +++ b/apps/flipcash/features/contact-verification/src/main/kotlin/com/flipcash/app/contact/verification/internal/phone/PhoneVerificationViewModel.kt @@ -3,8 +3,9 @@ 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.app.core.extensions.onResult -import com.flipcash.app.featureflags.FeatureFlag import com.flipcash.app.featureflags.FeatureFlagController import com.flipcash.app.phone.CountryLocale import com.flipcash.app.phone.PhoneUtils @@ -50,6 +51,7 @@ internal class PhoneVerificationViewModel @Inject constructor( private val featureFlags: FeatureFlagController, private val resources: ResourceHelper, private val dispatchers: DispatcherProvider, + private val analytics: FlipcashAnalyticsService, ) : BaseViewModel( initialState = State(), updateStateForEvent = updateStateForEvent, @@ -140,7 +142,7 @@ internal class PhoneVerificationViewModel @Inject constructor( val cleanedNumber = phoneUtils.cleanNumber(number, locale) ContactMethod.Phone(cleanedNumber) } - .onEach { handleSendVerificationCode(it) } + .onEach { handleSendVerificationCode(it, false) } .launchIn(viewModelScope) eventFlow @@ -151,7 +153,7 @@ internal class PhoneVerificationViewModel @Inject constructor( val cleanedNumber = phoneUtils.cleanNumber(number, locale) ContactMethod.Phone(cleanedNumber) } - .onEach { handleSendVerificationCode(it) } + .onEach { handleSendVerificationCode(it, true) } .launchIn(viewModelScope) eventFlow @@ -179,6 +181,7 @@ internal class PhoneVerificationViewModel @Inject constructor( viewModelScope.launch { delay(1.seconds) + analytics.action(Action.VerifiedPhoneNumber) dispatchEvent(Event.OnCodeVerified) dispatchEvent(Event.OnVerifyingCodeChanged()) } @@ -228,6 +231,7 @@ internal class PhoneVerificationViewModel @Inject constructor( .onResult( onSuccess = { trace(message = "linkForPayment succeeded", type = TraceType.Process) + analytics.action(Action.LinkedPhoneNumber) dispatchEvent(Event.OnPhoneVerificationComplete) }, onError = { @@ -288,7 +292,7 @@ internal class PhoneVerificationViewModel @Inject constructor( }.launchIn(viewModelScope) } - private suspend fun handleSendVerificationCode(method: ContactMethod) { + private suspend fun handleSendVerificationCode(method: ContactMethod, isResend: Boolean) { dispatchEvent(Event.OnSendingCodeChanged(loading = true)) if (stateFlow.value.attempts >= 3) { dispatchEvent(Event.OnSendingCodeChanged()) @@ -301,6 +305,10 @@ internal class PhoneVerificationViewModel @Inject constructor( return } + if (!isResend) { + analytics.action(Action.EnteredPhoneNumber) + } + verificationController.sendVerificationCode(method) .onSuccess { dispatchEvent(Event.OnSendingCodeChanged(success = true)) @@ -336,17 +344,6 @@ internal class PhoneVerificationViewModel @Inject constructor( } } - suspend fun awaitLinkForPayment() { - val isEnabled = featureFlags.observe(FeatureFlag.PhoneNumberSend).value || - userManager.state.value.flags?.enablePhoneNumberSend == true - if (!isEnabled) return - - val number = stateFlow.value.numberTextFieldState.text.toString() - val locale = stateFlow.value.selectedLocale - val cleanedNumber = phoneUtils.cleanNumber(number, locale) - verificationController.linkForPayment(ContactMethod.Phone(cleanedNumber)) - } - private fun startTimer() { dispatchEvent( Event.OnTimerTick( @@ -376,7 +373,6 @@ internal class PhoneVerificationViewModel @Inject constructor( } override fun onCleared() { - super.onCleared() stopTimer() } diff --git a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Actions.kt b/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Actions.kt index 44c79ded7..234e75f86 100644 --- a/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Actions.kt +++ b/apps/flipcash/shared/analytics/src/main/kotlin/com/flipcash/app/analytics/Actions.kt @@ -3,6 +3,17 @@ package com.flipcash.app.analytics import com.getcode.libs.analytics.AppAction sealed interface Action : AppAction { + data object EnteredPhoneNumber : Action { + override val value: String = "Entered Phone Number" + } + data object VerifiedPhoneNumber: Action { + override val value: String = "Verified Phone Number" + } + + data object LinkedPhoneNumber: Action { + override val value: String = "Linked Phone Number" + } + data object CompletedOnboarding : Action { override val value: String = "Complete Onboarding" } diff --git a/apps/flipcash/shared/contacts/build.gradle.kts b/apps/flipcash/shared/contacts/build.gradle.kts index 5f922142d..551b6fcbb 100644 --- a/apps/flipcash/shared/contacts/build.gradle.kts +++ b/apps/flipcash/shared/contacts/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(project(":services:flipcash")) implementation(project(":services:opencode")) + implementation(project(":apps:flipcash:shared:analytics")) implementation(project(":apps:flipcash:shared:persistence:db")) implementation(project(":apps:flipcash:shared:persistence:sources")) implementation(project(":apps:flipcash:shared:phone")) diff --git a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt index 0aba6799d..8719be0e4 100644 --- a/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt +++ b/apps/flipcash/shared/contacts/src/main/kotlin/com/flipcash/app/contacts/ContactCoordinator.kt @@ -13,6 +13,8 @@ import androidx.datastore.preferences.preferencesDataStoreFile import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ProcessLifecycleOwner +import com.flipcash.app.analytics.Action +import com.flipcash.app.analytics.FlipcashAnalyticsService import com.flipcash.app.contacts.device.DeviceContactLookup import com.flipcash.app.contacts.device.PickedContactData import com.flipcash.app.contacts.device.ScopeAwareContactReader @@ -55,6 +57,7 @@ import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @@ -79,6 +82,7 @@ class ContactCoordinator @Inject constructor( private val userManager: UserManager, private val featureFlagController: FeatureFlagController, private val dispatchers: DispatcherProvider, + private val analytics: FlipcashAnalyticsService, ) : SessionListener, DefaultLifecycleObserver { companion object { @@ -265,14 +269,16 @@ class ContactCoordinator @Inject constructor( // Profile may not be loaded yet on the first Ready transition; // wait for the verified phone number to arrive. val phone = userManager.state - .map { it.userProfile?.verifiedPhoneNumber } - .filterNotNull() - .first() - - contactVerificationController.linkForPayment(ContactMethod.Phone(phone)) - .onSuccess { - contactPrefs.edit { it[KEY_LINKED_FOR_PAYMENT] = true } - } + .mapNotNull { it.userProfile?.verifiedPhoneNumber } + .firstOrNull() + + if (phone != null) { + contactVerificationController.linkForPayment(ContactMethod.Phone(phone)) + .onSuccess { + analytics.action(Action.LinkedPhoneNumber) + contactPrefs.edit { it[KEY_LINKED_FOR_PAYMENT] = true } + } + } } finally { linkMutex.unlock() }