Skip to content

feat(analytics): add group chat events and send them from Android - #1561

Merged
bmc08gt merged 8 commits into
code/cashfrom
feat/group-chat-analytics
Sep 25, 2026
Merged

bmc08gt merged 8 commits into
code/cashfrom
feat/group-chat-analytics

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Group chats sent no analytics beyond the chat events they share with DMs. This adds a Group domain to events.toml (generating GroupEvents), Chat Muted and Chat Unmuted in the Chat domain, and fires all of them from Android.

iOS needs a flipcash-shared-core-spm release after this merges before it can send these events. The Swift typealiases for GroupEvents and the new lists are in AnalyticsEvents.swift.

Catalogue

Event Properties
Group: New Opened none
Group: Created State, Error?, Gate Mint?, Has Picture
Group: Edited Field (Name, Picture), State, Error?
Group: Invite Sheet Opened Source (Chat, Profile), Member Count
Group: Invite Shared Method (Share, Copy)
Group: Invite Followed Source (Link, QR, Chat Card)
Group: Gate Shown Access (Eligible, Blocked), Gate Mint?, Member Count
Group: Gate Funding Tapped Method (Buy Token, Add Cash), Gate Mint?
Group: Joined State, Error?, Member Count, Gated
Group: Left State, Error?, Member Count
Group: Info Opened Member Count, Is Member
Chat Muted Chat Type, Duration, State, Error?
Chat Unmuted Chat Type, State, Error?
  • MuteDuration: 1 Hour, 8 Hours, 1 Week, Always. Android's MuteState and iOS's MuteChatSheet offer the same four.
  • Error: the RPC's result name from the proto (Denied, NotFound, RulesNotSatisfied, TitleModerated, …), or Network for a transport failure. Throwable.chatResult in Conversions.kt does the mapping. It also returns Validation when a request fails proto validation before it is sent, since there is no server result to name.
  • Gate Mint: left out when the group has no token gate, including a rule that any currency satisfies.
  • Member Count: the roster the screen held before the action.
  • Drift: TokenInfoSource.CHAT_GATE notes that iOS doesn't send it. Its gate buy button pushes the buy screen and never opens Token Info. Group: Gate Funding Tapped covers that tap on both apps.

Android call sites

  • New Opened and Created: NewChatScreen and CreateGroupViewModel.create().
  • Edited: EditGroupNameViewModel and EditGroupPictureViewModel.
  • Muted and Unmuted: MuteChatViewModel only had the chat id. AppRoute.MuteChat now carries the ChatType, and ConversationReference gains chatType so the chat list can say what kind of DM it is instead of only isGroup. The sheet passes up the MuteOption rather than the MuteState built from it, so the picked duration is still known when the event fires.
  • Gate, invite, join, leave, info: ChatViewModel. The screens dispatch the taps as events, so the count and mint come from the group the view model holds. Gate Shown is taken once per view model, which the chat flow scopes to one visit, so a balance change under an open chat doesn't send it again.
  • Invite Followed:
    • Link: App.kt's DeepLinkListener, which hears each link once. The cold-start path in MainRoot re-derives its routes on every auth re-emission and would count one link several times. The listener runs before auth resolves, so a logged-out tap on an invite counts as followed too.
    • QR: Scanner's GroupChatInvite branch.
    • Chat Card: an invite card in the transcript that opens a different group.

Tests

  • GroupEventsTest and ChatEventsTest pin each new event's literal strings on the Android host, macosArm64 and iosSimulatorArm64. AnalyticsEventsTests.swift covers one group event through SharedCoreKit.
  • ConversionsTest covers chatResult and gateMint.
  • ChatGroupAnalyticsTest covers Gate Shown once, and without a mint for an ungated group. It also covers join and leave results, the invite, funding and info taps, and the chat card. There are also emission tests for create, edit and mute, using RecordingAnalytics.

Adds a Group domain (GroupEvents) with eleven events covering create,
edit, invite, gate, join, leave and info, and Chat Muted / Chat Unmuted
in the Chat domain. MuteDuration matches the four options both apps
offer. TokenInfoSource CHAT_GATE gets a drift note: iOS's gate buy
button pushes the buy screen and never opens Token Info.
…p events

chatResult gives the proto result name the Error property carries
(Denied, NotFound, RulesNotSatisfied, ...), or Network for a transport
failure; class names are spelled out because R8 renames them. gateMint
is the listener rule's single mint, or null for an ungated or
any-currency group.
New Opened fires on the Create Public Group row, the flow's only entry.
Created fires when StartChat returns, with the proto result name on a
failure.
Fires when EditChat returns. A picture the blob store refuses never
reaches EditChat, so it sends nothing.
The sheet now hands the view model the MuteOption instead of the
MuteState built from it, so the picked duration is still known when the
event fires. MuteChat carries the chat type because the sheet only had
the id; ConversationReference gains chatType so the chat list can pass a
DM's kind rather than only isGroup.
…ents

ChatViewModel sends Group: Gate Shown, Invite Sheet Opened, Invite Shared,
Gate Funding Tapped, Joined, Left and Info Opened. The screens dispatch the
taps as events, so the member count and gate mint come from the group the
view model already holds, read before the join or leave moves the roster.

Gate Shown is taken once per view model, which the chat flow scopes to one
visit, so a balance change under an open chat does not send it again.
…ite cards

A link is reported from the app's DeepLinkListener, which hears each
link once; the cold-start path re-derives its routes on every auth
re-emission and would count one link several times. The scanner reports
QR in its GroupChatInvite branch, and the transcript reports Chat Card
when an invite card opens a different group.
@bmc08gt bmc08gt self-assigned this Sep 25, 2026
@github-actions github-actions Bot added type: feature New functionality area: scanner QR/Kikcode scanning, camera and removed type: feature New functionality labels Sep 25, 2026
Created, Joined, Left, Muted and Unmuted now go through a
FlipcashAnalytics extension next to their callers, as Edited already
did, so each call site is one line and the State and Error derivation
lives with the event. This drops the two private Result.analyticsState
copies.
@github-actions github-actions Bot added the type: feature New functionality label Sep 25, 2026
@bmc08gt
bmc08gt merged commit 30e478d into code/cash Sep 25, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: scanner QR/Kikcode scanning, camera type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant