diff --git a/apps/flipcash/core/src/main/res/drawable/ic_tipcode.xml b/apps/flipcash/core/src/main/res/drawable/ic_tipcode.xml
new file mode 100644
index 000000000..0e7ffc099
--- /dev/null
+++ b/apps/flipcash/core/src/main/res/drawable/ic_tipcode.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/flipcash/core/src/main/res/values/strings.xml b/apps/flipcash/core/src/main/res/values/strings.xml
index 7b8e66d90..757aa4483 100644
--- a/apps/flipcash/core/src/main/res/values/strings.xml
+++ b/apps/flipcash/core/src/main/res/values/strings.xml
@@ -896,5 +896,6 @@
Minimum tip %1$s
Tips Start at %1$s
Enter a larger amount to send this tip
+ via Tip Card
\ No newline at end of file
diff --git a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/ContactInfoContainer.kt b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/ContactInfoContainer.kt
index 041e56664..89ceb320c 100644
--- a/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/ContactInfoContainer.kt
+++ b/apps/flipcash/features/messenger/src/main/kotlin/com/flipcash/app/messenger/internal/screens/components/ContactInfoContainer.kt
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Icon
@@ -27,10 +28,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.tooling.preview.PreviewWrapper
import com.flipcash.app.core.android.IntentUtils
import com.flipcash.app.core.contacts.DeviceContact
import com.flipcash.app.messenger.internal.ChatParticipant
+import com.flipcash.app.theme.FlipcashThemeWrapper
import com.flipcash.features.messenger.R
+import com.flipcash.services.models.UserProfile
import com.getcode.theme.CodeTheme
@Composable
@@ -84,35 +90,55 @@ internal fun ContactInfoContainer(
contract = ActivityResultContracts.StartActivityForResult()
) { onRefreshContact() }
- ContactPill(
+ Indicator(
+ participant = participant,
modifier = Modifier.padding(top = CodeTheme.dimens.inset),
- contact = contact
) {
- contact ?: return@ContactPill
- val intent = IntentUtils.openContact(contact).apply {
- // Remove NEW_TASK so the result callback fires when the user returns,
- // not immediately.
- flags = flags and Intent.FLAG_ACTIVITY_NEW_TASK.inv()
+ when (participant) {
+ is ChatParticipant.Contact -> {
+ val intent = IntentUtils.openContact(participant.contact).apply {
+ // Remove NEW_TASK so the result callback fires when the user returns,
+ // not immediately.
+ flags = flags and Intent.FLAG_ACTIVITY_NEW_TASK.inv()
+ }
+ launcher.launch(intent)
+ }
+ is ChatParticipant.TipUser -> Unit
+ null -> Unit
}
- launcher.launch(intent)
}
}
}
+@Composable
+private fun Indicator(
+ participant: ChatParticipant?,
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+) {
+ when (participant) {
+ is ChatParticipant.Contact -> ContactPill(
+ participant.contact,
+ modifier = modifier,
+ onClick = onClick
+ )
+
+ is ChatParticipant.TipUser -> TipPill(modifier = modifier)
+ null -> Spacer(modifier = modifier.fillMaxWidth())
+ }
+}
+
@Composable
private fun ContactPill(
- contact: DeviceContact?,
+ contact: DeviceContact,
modifier: Modifier = Modifier,
onClick: () -> Unit,
) {
AnimatedContent(contact) { c ->
- if (c == null) {
- Spacer(modifier = modifier.fillMaxWidth())
- return@AnimatedContent
- }
-
val backgroundColor by animateColorAsState(
- if (!c.isUnknown) CodeTheme.colors.surfaceVariant else CodeTheme.colors.warning.copy(alpha = 0.10f)
+ if (!c.isUnknown) CodeTheme.colors.surfaceVariant else CodeTheme.colors.warning.copy(
+ alpha = 0.10f
+ )
)
val contentColor by animateColorAsState(
@@ -124,7 +150,10 @@ private fun ContactPill(
.background(color = backgroundColor, shape = CircleShape)
.clip(CircleShape)
.clickable { onClick() }
- .padding(horizontal = CodeTheme.dimens.grid.x2, vertical = CodeTheme.dimens.grid.x1),
+ .padding(
+ horizontal = CodeTheme.dimens.grid.x2,
+ vertical = CodeTheme.dimens.grid.x1
+ ),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1)
) {
@@ -142,10 +171,87 @@ private fun ContactPill(
)
Text(
- text = if (!c.isUnknown) stringResource(R.string.label_fromYourContacts) else stringResource(R.string.label_addContact),
+ text = if (!c.isUnknown) stringResource(R.string.label_fromYourContacts) else stringResource(
+ R.string.label_addContact
+ ),
color = contentColor,
style = CodeTheme.typography.textSmall,
)
}
}
-}
\ No newline at end of file
+}
+
+@Composable
+private fun TipPill(
+ modifier: Modifier = Modifier,
+) {
+ Row(
+ modifier = modifier
+ .background(color = CodeTheme.colors.surfaceVariant, shape = CircleShape)
+ .clip(CircleShape)
+ .padding(horizontal = CodeTheme.dimens.grid.x2, vertical = CodeTheme.dimens.grid.x1),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x1)
+ ) {
+ Icon(
+ modifier = Modifier.size(CodeTheme.dimens.staticGrid.x4),
+ painter = painterResource(R.drawable.ic_tipcode),
+ contentDescription = null,
+ tint = CodeTheme.colors.textSecondary,
+ )
+
+ Text(
+ text = stringResource(R.string.label_viaTipCard),
+ color = CodeTheme.colors.textSecondary,
+ style = CodeTheme.typography.textSmall,
+ )
+ }
+}
+
+// region Previews
+
+@Preview
+@PreviewWrapper(FlipcashThemeWrapper::class)
+@Composable
+private fun Preview_AllStates() {
+ // A saved device contact: name + number + the "From your contacts" pill.
+ val knownContact = ChatParticipant.Contact(
+ DeviceContact(
+ e164 = "+15551234567",
+ androidContactId = 1L,
+ displayName = "Ada Lovelace",
+ photoUri = null,
+ displayNumber = "(555) 123-4567",
+ )
+ )
+
+ // An unknown number: falls back to the number as the name and shows the warning
+ // "Add contact" pill.
+ val unknownContact = ChatParticipant.Contact(
+ DeviceContact.unknownContact(
+ e164 = "+15559876543",
+ displayNumber = "(555) 987-6543",
+ )
+ )
+
+ // A tip DM's counterparty: identity from a server profile — no phone number, no pill.
+ val tipUser = ChatParticipant.TipUser(
+ userId = listOf(1.toByte()),
+ profile = UserProfile.Empty.copy(displayName = "Grace Hopper"),
+ )
+
+ // Fixed width so every state renders at the same size regardless of name/number length.
+ val cardWidth = Modifier.width(300.dp)
+ Column(
+ modifier = Modifier.padding(CodeTheme.dimens.grid.x4),
+ verticalArrangement = Arrangement.spacedBy(CodeTheme.dimens.grid.x4),
+ ) {
+ ContactInfoContainer(participant = knownContact, modifier = cardWidth)
+ ContactInfoContainer(participant = unknownContact, modifier = cardWidth)
+ ContactInfoContainer(participant = tipUser, modifier = cardWidth)
+ // Null participant: loading / unknown fallback avatar, name empty, no pill.
+ ContactInfoContainer(participant = null, modifier = cardWidth)
+ }
+}
+
+// endregion
\ No newline at end of file