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
2 changes: 1 addition & 1 deletion apps/flipcash/core/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1242,7 +1242,7 @@
<string name="placeholder_profileDisplayName">Your Name</string>
<string name="subtitle_profileImageRecommended">500x500 Recommended</string>
<string name="title_myTipCard">My Tip Card</string>
<string name="label_tipUser">Tip %1$s</string>
<string name="title_shareProfile">Chat with %1$s on Flipcash</string>
<string name="subtitle_myTipCard">Share Your Tip Card to Get Tipped</string>
<string name="action_showTipCard">Show My Tip Card</string>
<string name="title_sendTip">Send a Tip</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,16 +467,14 @@ internal class MenuScreenViewModel @Inject constructor(
.filterIsInstance<Event.ShareTipCard>()
.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,
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,24 +69,28 @@ 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/<username>` 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
}

/**
* 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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down Expand Up @@ -217,7 +217,7 @@ internal class InternalShareSheetController(
shareGroupInvite(shareable)
}

is Shareable.TipCard -> shareTipCard(shareable)
is Shareable.Profile -> shareProfile(shareable)

is Shareable.TipCodeImage -> shareTipCodeImage(shareable)
}
Expand Down Expand Up @@ -392,23 +392,23 @@ 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)
}
}

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)
}

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(
Expand All @@ -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),
Expand All @@ -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.
*/
Expand All @@ -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)
}
Expand Down
Loading