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 @@ -25,6 +25,7 @@ import com.flipcash.app.tokens.TokenCoordinator
import com.flipcash.features.messenger.R
import com.flipcash.services.models.UserProfile
import com.flipcash.services.models.chat.ChatId
import com.flipcash.services.models.chat.ChatType
import com.flipcash.services.models.chat.DeliveryStatus
import com.flipcash.services.models.chat.MessageContent
import com.flipcash.services.models.chat.TypingState
Expand Down Expand Up @@ -251,6 +252,14 @@ internal class ChatViewModel @Inject constructor(
.map { it.participant is ChatParticipant.TipUser }
.distinctUntilChanged()

/** The [ChatType] backing this conversation, derived from the resolved participant. */
private val ChatParticipant?.chatType: ChatType
get() = when (this) {
is ChatParticipant.TipUser -> ChatType.TIP_DM
is ChatParticipant.Contact -> ChatType.CONTACT_DM
null -> ChatType.UNKNOWN
}

private val amountStyleFlow by lazy {
isTipFlow
.map { amountStyle(isTip = it) }
Expand Down Expand Up @@ -530,18 +539,19 @@ internal class ChatViewModel @Inject constructor(
val textToSend = stateFlow.value.chatInputState.text.toString()
val chatId = stateFlow.value.chatId ?: return@onEach
if (textToSend.isBlank()) return@onEach
val chatType = stateFlow.value.participant.chatType

stateFlow.value.chatInputState.setTextAndPlaceCursorAtEnd("")

viewModelScope.launch {
chatCoordinator.sendMessage(chatId, textToSend)
.onSuccess {
trace("message sent successfully")
analytics.messageSentInChat()
analytics.messageSentInChat(type = chatType)
}
.onFailure { cause ->
trace("message failed to send - ${cause.localizedMessage}")
analytics.messageSentInChat(error = cause)
analytics.messageSentInChat(type = chatType, error = cause)
}
}
}
Expand Down Expand Up @@ -663,11 +673,18 @@ internal class ChatViewModel @Inject constructor(
}
}

// A payment into a tip DM is a tip; a contact DM is a plain cash send.
val transferEvent = if (stateFlow.value.participant is ChatParticipant.TipUser) {
Analytics.Transfer.SentTip
} else {
Analytics.Transfer.SentCash
}

result.onSuccess {
dispatchEvent(Event.SendStateUpdated(success = true))
delay(400.milliseconds)
analytics.transfer(
event = Analytics.Transfer.SentCash,
event = transferEvent,
amount = verifiedFiat.localFiat,
successful = true,
)
Expand All @@ -678,7 +695,7 @@ internal class ChatViewModel @Inject constructor(
}.onFailure { cause ->
dispatchEvent(Event.SendStateUpdated())
analytics.transfer(
event = Analytics.Transfer.SentCash,
event = transferEvent,
amount = verifiedFiat.localFiat,
error = cause,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flipcash.app.analytics
import androidx.compose.runtime.Composable
import com.flipcash.app.core.navigation.DeeplinkType
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
import com.flipcash.services.models.chat.ChatType
import com.getcode.ed25519.Ed25519.KeyPair
import com.getcode.libs.analytics.AnalyticsService
import com.getcode.libs.analytics.AppAction
Expand Down Expand Up @@ -35,7 +36,9 @@ interface FlipcashAnalyticsService : AnalyticsService {
fun openTokenInfo(source: Analytics.TokenInfoSource, mint: Mint)
fun buy(method: Analytics.PurchaseMethod, mint: Mint, amount: Fiat, error: Throwable? = null)
fun sell(mint: Mint, amount: Fiat, feeAmount: Fiat, error: Throwable? = null)
fun messageSentInChat(error: Throwable? = null)
fun messageSentInChat(type: ChatType, error: Throwable? = null)
fun tipCardScanned()
fun tipCardPresented()
fun deeplinkOpened(url: String)
fun deeplinkParsed(type: DeeplinkType?, url: String)
fun deeplinkRouted(type: DeeplinkType, error: Throwable? = null)
Expand Down Expand Up @@ -64,6 +67,7 @@ object Analytics {
}

data object SentCash : Transfer
data object SentTip : Transfer
}
enum class OnrampSource { Settings, Balance, Give }
enum class AddMoneySource { Menu, GiveShortfall, BuyShortfall, Chat, Scanner, Balance }
Expand Down Expand Up @@ -114,7 +118,9 @@ class StubFlipcashAnalytics : FlipcashAnalyticsService {
override fun buy(method: Analytics.PurchaseMethod, mint: Mint, amount: Fiat, error: Throwable?) = Unit
override fun sell(mint: Mint, amount: Fiat, feeAmount: Fiat, error: Throwable?) = Unit

override fun messageSentInChat(error: Throwable?) = Unit
override fun messageSentInChat(type: ChatType, error: Throwable?) = Unit
override fun tipCardScanned() = Unit
override fun tipCardPresented() = Unit

override fun deeplinkOpened(url: String) = Unit
override fun deeplinkParsed(type: DeeplinkType?, url: String) = Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.flipcash.app.analytics
import androidx.core.net.toUri
import com.flipcash.app.core.navigation.DeeplinkType
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
import com.flipcash.services.models.chat.ChatType
import com.getcode.ed25519.Ed25519.KeyPair
import com.getcode.opencode.model.core.ID
import com.getcode.opencode.model.financial.CurrencyCode
Expand Down Expand Up @@ -133,22 +134,38 @@ internal sealed interface AnalyticsEvent {
override val name = "Receive Cash Link"
}

sealed interface ChatEvent: AnalyticsEvent {
data object SentCash : ChatEvent {
override val name = "Sent Cash"
}
data object SentTip : Transfer {
override val name = "Sent Tip"
}

data object SentCash : ChatEvent {
override val name = "Sent Cash"
}

sealed interface ChatEvent: AnalyticsEvent {
data class SentMessage(
val chatType: ChatType,
val error: Throwable? = null
): ChatEvent {
override val name = "Sent Message"

override fun toProperties() = buildMap {
put("Chat Type", chatType.propertyValue)
error?.let { put("Error", it.message.orEmpty()) }
}
}
}

sealed interface TipCardEvent : AnalyticsEvent {
data object Scanned : TipCardEvent {
override val name = "Tip Card Scanned"
}

data object Presented : TipCardEvent {
override val name = "Tip Card Presented"
}
}

sealed interface PoolEvent : AnalyticsEvent {
val id: ID
override fun toProperties() = mapOf("ID" to id.base58)
Expand Down Expand Up @@ -359,6 +376,13 @@ internal val Analytics.AddMoneyMethod.propertyValue: String
Analytics.AddMoneyMethod.Reserves -> "Reserves"
}

internal val ChatType.propertyValue: String
get() = when (this) {
ChatType.CONTACT_DM -> "Contact"
ChatType.TIP_DM -> "Tip"
ChatType.UNKNOWN -> "Unknown"
}

internal fun LocalFiat.asProperties(): Map<String, String> {
return buildMap {
putAll(underlyingTokenAmount.asProperties())
Expand Down Expand Up @@ -388,5 +412,6 @@ internal fun Analytics.Transfer.toAnalyticsEvent(): AnalyticsEvent = when (this)
is Analytics.Transfer.ClaimedCashLink -> AnalyticsEvent.ClaimedCashLink
is Analytics.Transfer.SentCashLink.Clipboard -> AnalyticsEvent.SentCashLink(clipboard = true)
is Analytics.Transfer.SentCashLink.App -> AnalyticsEvent.SentCashLink(app = name)
is Analytics.Transfer.SentCash -> AnalyticsEvent.ChatEvent.SentCash
is Analytics.Transfer.SentCash -> AnalyticsEvent.SentCash
is Analytics.Transfer.SentTip -> AnalyticsEvent.SentTip
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.flipcash.app.analytics.asProperties
import com.flipcash.app.analytics.toAnalyticsEvent
import com.flipcash.app.core.navigation.DeeplinkType
import com.flipcash.services.internal.model.thirdparty.OnRampProvider
import com.flipcash.services.models.chat.ChatType
import com.getcode.ed25519.Ed25519.KeyPair
import com.getcode.libs.analytics.AppAction
import com.getcode.libs.analytics.AppActionSource
Expand Down Expand Up @@ -238,8 +239,16 @@ internal class MixpanelAnalyticsDelegate @Inject constructor(
track(AnalyticsEvent.TokenTransactionEvent.Sell(mint, amount, feeAmount, error))
}

override fun messageSentInChat(error: Throwable?) {
track(AnalyticsEvent.ChatEvent.SentMessage(error = error))
override fun messageSentInChat(type: ChatType, error: Throwable?) {
track(AnalyticsEvent.ChatEvent.SentMessage(chatType = type, error = error))
}

override fun tipCardScanned() {
track(AnalyticsEvent.TipCardEvent.Scanned)
}

override fun tipCardPresented() {
track(AnalyticsEvent.TipCardEvent.Presented)
}

override fun deeplinkOpened(url: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ class CodeScanDelegate @Inject constructor(
// rendezvous grab. Hand the id to the shell, which routes to TipCardOperations to
// resolve and present the card.
val userId = payload.userId ?: return
analytics.tipCardScanned()
_events.trySend(Event.TipCardScanned(userId))
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.flipcash.app.session.internal.delegates

import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.session.TipCardOperations
import com.flipcash.libs.coroutines.DispatcherProvider
Expand Down Expand Up @@ -36,6 +37,7 @@ import javax.inject.Singleton
@Singleton
class TipCardDelegate @Inject constructor(
private val tippingCoordinator: TippingCoordinator,
private val analytics: FlipcashAnalyticsService,
dispatchers: DispatcherProvider,
) : TipCardOperations {

Expand All @@ -60,6 +62,7 @@ class TipCardDelegate @Inject constructor(
// Always present the card. Whether the tip modal slides up (or an
// add-money prompt shows instead) is decided in the UI from the
// coordinator's affordability state — see TipCardDecorator.
analytics.tipCardPresented()
_events.trySend(Event.Present(card))
}
.onFailure {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.flipcash.shared.tipping

import com.flipcash.app.analytics.Analytics
import com.flipcash.app.analytics.FlipcashAnalyticsService
import com.flipcash.app.core.bill.Scannable
import com.flipcash.app.core.chat.ChatIdentifier
import com.flipcash.app.core.tipping.TipAmount
Expand Down Expand Up @@ -64,6 +66,7 @@ class TippingCoordinator @Inject constructor(
private val verifiedFiatCalculator: VerifiedFiatCalculator,
private val resources: ResourceHelper,
private val purchaseMethodController: PurchaseMethodController,
private val analytics: FlipcashAnalyticsService,
) : TipSelectionHolder {
/** The signed-in user's id ([UserManager.accountId]), or null if unavailable. */
val currentUserId: ID?
Expand Down Expand Up @@ -179,24 +182,24 @@ class TippingCoordinator @Inject constructor(
setSendState(LoadingSuccessState(success = true))
delay(400.milliseconds)
setSendState(LoadingSuccessState())
// analytics.transfer(
// event = Analytics.Transfer.SentCash,
// amount = verifiedFiat.localFiat,
// successful = true,
// )
analytics.transfer(
event = Analytics.Transfer.SentTip,
amount = verifiedFiat.localFiat,
successful = true,
)

// Hand off to the tipped user's chat via the tips flow, so backing out of the
// chat lands on the tips list.
canonicalChatId?.let {
_events.emit(TipEvent.LaunchChat(ChatIdentifier.ByChatId(it)))
}
}.onFailure {
}.onFailure { cause ->
setSendState(LoadingSuccessState())
// analytics.transfer(
// event = Analytics.Transfer.SentCash,
// amount = verifiedFiat.localFiat,
// error = cause,
// )
analytics.transfer(
event = Analytics.Transfer.SentTip,
amount = verifiedFiat.localFiat,
error = cause,
)
BottomBarManager.showError(
title = resources.getString(R.string.error_title_cashFailedToSend),
message = resources.getString(R.string.error_description_cashFailedToSend),
Expand Down
Loading