From f788aa437f6eed9c93fc0394fbf88d94e2bcb76b Mon Sep 17 00:00:00 2001 From: Brandon McAnsh Date: Wed, 30 Sep 2026 13:59:53 -0400 Subject: [PATCH] fix(share): title every profile share "Chat with X on Flipcash" The profile Sharesheet title depended on where the share started: the You tab and the share-profile widget said "Tip X", and a profile's Share shortcut used the bare display name. Shareable.TipCard becomes Shareable.Profile, which takes the owner's display name and builds the title in the share controller, so the three callers can no longer disagree. label_tipUser had no other use and is replaced by title_shareProfile. --- .../core/src/main/res/values/strings.xml | 2 +- .../app/menu/internal/MenuScreenViewModel.kt | 10 ++++------ .../internal/screens/MessengerScreen.kt | 5 ++--- .../screens/profile/ProfileShortcuts.kt | 4 ++-- .../app/shareable/ShareSheetController.kt | 16 ++++++++++------ .../InternalShareConfirmationController.kt | 2 +- .../internal/InternalShareSheetController.kt | 18 ++++++++++-------- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml index aee06a45f6..7bd13b3c05 100644 --- a/apps/flipcash/core/src/main/res/values/strings.xml +++ b/apps/flipcash/core/src/main/res/values/strings.xml @@ -1242,7 +1242,7 @@ Your Name 500x500 Recommended My Tip Card - Tip %1$s + Chat with %1$s on Flipcash Share Your Tip Card to Get Tipped Show My Tip Card Send a Tip diff --git a/apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt b/apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt index 854a426b99..980b11cf72 100644 --- a/apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt +++ b/apps/flipcash/features/menu/src/main/kotlin/com/flipcash/app/menu/internal/MenuScreenViewModel.kt @@ -467,16 +467,14 @@ internal class MenuScreenViewModel @Inject constructor( .filterIsInstance() .mapNotNull { tippingCoordinator.currentUserId } .map { userId -> - // Title shown above the link, e.g. "Tip Ada" (same label as the card). - val title = stateFlow.value.tipCard?.user?.displayName - ?.let { resources.getString(R.string.label_tipUser, it) } + val user = stateFlow.value.tipCard?.user // Attach the eagerly-rendered preview if it's ready; null shares the URL alone. shareable.present( - Shareable.TipCard( + Shareable.Profile( userId = userId, + displayName = user?.displayName, + username = user?.username, preview = tipCodePreviewCache.get(userId), - title = title, - username = stateFlow.value.tipCard?.user?.username, ) ) } diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt index 8e568f6a0d..7dbd925b2d 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/MessengerScreen.kt @@ -49,7 +49,6 @@ import com.flipcash.shared.chat.models.ChatAction import com.flipcash.app.core.LocalUserManager import com.flipcash.app.shareable.LocalShareController import com.flipcash.app.shareable.Shareable -import com.flipcash.features.messenger.R import kotlinx.coroutines.launch import androidx.compose.runtime.rememberCoroutineScope import com.getcode.navigation.core.LocalCodeNavigator @@ -245,9 +244,9 @@ internal fun MessengerScreen(viewModel: ChatViewModel) { // shares it (`rememberProfileShare`): the handle form, titled with their name. is ChatAction.ShareProfile -> scope.launch { shareController.present( - Shareable.TipCard( + Shareable.Profile( userId = action.userId, - title = action.displayName?.let { context.getString(R.string.label_tipUser, it) }, + displayName = action.displayName, username = action.username, ) ) diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ProfileShortcuts.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ProfileShortcuts.kt index a99684afe4..5bd103337a 100644 --- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ProfileShortcuts.kt +++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/profile/ProfileShortcuts.kt @@ -86,9 +86,9 @@ internal fun rememberProfileShare(): (ChatParticipant.TipUser) -> Unit { { user -> scope.launch { shareController.present( - Shareable.TipCard( + Shareable.Profile( userId = user.userId, - title = user.profile.displayName, + displayName = user.profile.displayName, username = user.profile.username, ) ) diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt index 8720f53abc..46629fd14c 100644 --- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt +++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/ShareSheetController.kt @@ -69,16 +69,20 @@ sealed interface Shareable { override val pendingData: ShareablePendingData? = null } - data class TipCard( + /** + * A user's profile link. The Sharesheet title ("Chat with Ada on Flipcash") is built from + * [displayName] when sharing, so every place that shares a profile titles it the same way. + */ + data class Profile( val userId: ID, - // Optional pre-rendered Sharesheet preview. Null degrades to sharing the URL alone. - val preview: TipCodePreview? = null, - // Optional Sharesheet title shown above the link (e.g. "Tip Brandon McAnsh"). - val title: String? = null, + // The owner's display name. Null shares the link without a title. + val displayName: String? = null, // The owner's claimed handle, when they have one. Carried so the shared link is the vanity // form the You tab shows and copies — sharing a UUID for a card that displays // `flipcash.com/` would hand out a second, unrecognisable address for it. val username: String? = null, + // Optional pre-rendered Sharesheet preview. Null degrades to sharing the URL alone. + val preview: TipCodePreview? = null, ): Shareable { override val pendingData: ShareablePendingData? = null } @@ -86,7 +90,7 @@ sealed interface Shareable { /** * The tip code itself, as a file (see [TipCodeExport]) — what the "Download" action produces. * - * Distinct from [TipCard]: that shares the tip *link* and only uses an image as the Sharesheet's + * Distinct from [Profile]: that shares the profile *link* and only uses an image as the Sharesheet's * thumbnail. Here the file IS the payload, so it goes out as an `EXTRA_STREAM` of the export's * own MIME type and a "save to Files" target receives something real. */ diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt index 4dc5e307f8..ed10feba52 100644 --- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt +++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareConfirmationController.kt @@ -31,7 +31,7 @@ internal class InternalShareConfirmationController( // Nothing to take back if the link never leaves — the chat exists either way, and // the same link can be shared again. Only a cash link needs confirming. is Shareable.GroupInvite -> ShareConfirmationResult.Confirmed(shareResult) - is Shareable.TipCard -> ShareConfirmationResult.Confirmed(shareResult) + is Shareable.Profile -> ShareConfirmationResult.Confirmed(shareResult) is Shareable.TipCodeImage -> ShareConfirmationResult.Confirmed(shareResult) } } diff --git a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt index fff5bf441f..882121ba32 100644 --- a/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt +++ b/apps/flipcash/shared/shareable/src/main/kotlin/com/flipcash/app/shareable/internal/InternalShareSheetController.kt @@ -116,7 +116,7 @@ internal class InternalShareSheetController( is Shareable.TokenInfo -> Unit is Shareable.Invite -> Unit is Shareable.GroupInvite -> Unit - is Shareable.TipCard -> Unit + is Shareable.Profile -> Unit is Shareable.TipCodeImage -> Unit } } @@ -217,7 +217,7 @@ internal class InternalShareSheetController( shareGroupInvite(shareable) } - is Shareable.TipCard -> shareTipCard(shareable) + is Shareable.Profile -> shareProfile(shareable) is Shareable.TipCodeImage -> shareTipCodeImage(shareable) } @@ -392,7 +392,7 @@ internal class InternalShareSheetController( if (preview != null) { // The Sharesheet draws its thumbnail from ClipData, not from any extra — see - // shareTipCard. The payload stays the text; this is preview only. + // shareProfile. The payload stays the text; this is preview only. clipData = ClipData.newUri(context.contentResolver, "Group picture", preview) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } @@ -400,7 +400,7 @@ internal class InternalShareSheetController( val share = Intent.createChooser(intent, null).apply { // addFlags, not `flags =` — assigning would wipe the read grant createChooser migrates - // onto the chooser intent, and the Sharesheet could not open the image. See shareTipCard. + // onto the chooser intent, and the Sharesheet could not open the image. See shareProfile. addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) if (preview != null) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) } @@ -408,7 +408,7 @@ internal class InternalShareSheetController( context.startActivity(share) } - private fun shareTipCard(shareable: Shareable.TipCard) { + private fun shareProfile(shareable: Shareable.Profile) { // Addressed the same way the You tab's link row addresses it, so what gets shared is what // the card says it is. val url = Linkify.tipcard( @@ -421,7 +421,9 @@ internal class InternalShareSheetController( // URL's OG tags. type stays text/plain and the bitmap is NOT an EXTRA_STREAM. type = "text/plain" putExtra(Intent.EXTRA_TEXT, url) - shareable.title?.let { putExtra(Intent.EXTRA_TITLE, it) } + shareable.displayName + ?.let { resources.getString(R.string.title_shareProfile, it) } + ?.let { putExtra(Intent.EXTRA_TITLE, it) } if (preview != null) { // Android draws the Sharesheet thumbnail from the intent's ClipData (a content:// URI), @@ -446,7 +448,7 @@ internal class InternalShareSheetController( } /** - * Shares the exported code file itself (PNG/SVG). Unlike [shareTipCard], the payload here IS the + * Shares the exported code file itself (PNG/SVG). Unlike [shareProfile], the payload here IS the * file: it goes out as an `EXTRA_STREAM` of the export's own MIME type, so "save to Files" and * image-consuming targets receive something real rather than a link. */ @@ -467,7 +469,7 @@ internal class InternalShareSheetController( } val share = Intent.createChooser(intent, null).apply { - // addFlags, not `flags =` — see shareTipCard: assigning would wipe the migrated grant. + // addFlags, not `flags =` — see shareProfile: assigning would wipe the migrated grant. addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) }