Skip to content
Open
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
36 changes: 21 additions & 15 deletions app/src/main/kotlin/com/wisp/app/ui/component/GalleryCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -324,21 +324,27 @@ fun GalleryCard(
)
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = displayName,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable(onClick = onProfileClick)
)
profile?.nip05?.let { nip05 ->
Nip05Badge(
nip05 = nip05,
pubkey = event.pubkey,
nip05Repo = nip05Repo,
onClick = onProfileClick
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = displayName,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(1f, fill = false)
.clickable(onClick = onProfileClick)
)
profile?.nip05?.let { nip05 ->
Spacer(Modifier.width(4.dp))
Nip05Badge(
nip05 = nip05,
pubkey = event.pubkey,
nip05Repo = nip05Repo,
onClick = onProfileClick,
showHandle = false
)
}
}
}
Text(
Expand Down Expand Up @@ -935,7 +941,7 @@ private fun formatGalleryTimestamp(epoch: Long): String {
if (hours < 24) return "${hours}h"

val days = diff / (24 * 60 * 60 * 1000L)
if (days == 1L) return "yesterday"
if (days < 7) return "${days}d"

val date = Date(millis)
val cal = java.util.Calendar.getInstance()
Expand Down
73 changes: 50 additions & 23 deletions app/src/main/kotlin/com/wisp/app/ui/component/PostCard.kt
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.zIndex
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.filled.CheckCircle
import androidx.compose.material.icons.filled.Verified
import androidx.compose.material.icons.filled.Cancel
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close
Expand Down Expand Up @@ -346,14 +346,28 @@ fun PostCard(
)
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Text(
text = displayName,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.clickable(onClick = onProfileClick)
)
Row(verticalAlignment = Alignment.CenterVertically) {
Text(
text = displayName,
style = MaterialTheme.typography.titleMedium,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(1f, fill = false)
.clickable(onClick = onProfileClick)
)
profile?.nip05?.let { nip05 ->
Spacer(Modifier.width(4.dp))
Nip05Badge(
nip05 = nip05,
pubkey = event.pubkey,
nip05Repo = nip05Repo,
onClick = onProfileClick,
showHandle = false
)
}
}
// NIP-38: user status (hide on replies to reduce clutter)
val statusVersion by eventRepo?.statusVersion?.collectAsState() ?: remember { mutableIntStateOf(0) }
val userStatus = remember(statusVersion, event.pubkey) {
Expand All @@ -369,16 +383,6 @@ fun PostCard(
fontStyle = androidx.compose.ui.text.font.FontStyle.Italic
)
}
if (!Nip10.isReply(event)) {
profile?.nip05?.let { nip05 ->
Nip05Badge(
nip05 = nip05,
pubkey = event.pubkey,
nip05Repo = nip05Repo,
onClick = onProfileClick
)
}
}
}
if (isPrivate) {
Icon(
Expand Down Expand Up @@ -1295,7 +1299,7 @@ private val dateTimeYearFormat = SimpleDateFormat("MMM d, yyyy", Locale.US)

/**
* Format an epoch timestamp into a relative or absolute time string.
* Avoids Calendar allocations — uses simple arithmetic for "yesterday" check.
* Avoids Calendar allocations for the s/m/h/d tiers — simple arithmetic.
*/
private fun formatTimestamp(epoch: Long): String {
val now = System.currentTimeMillis()
Expand All @@ -1313,7 +1317,7 @@ private fun formatTimestamp(epoch: Long): String {
if (hours < 24) return "${hours}h"

val days = diff / (24 * 60 * 60 * 1000L)
if (days == 1L) return "yesterday"
if (days < 7) return "${days}d"

val date = Date(millis)
val cal = java.util.Calendar.getInstance()
Expand Down Expand Up @@ -1342,6 +1346,10 @@ internal fun Nip05Badge(
maxLines: Int = 1,
verifiedTint: Color = MaterialTheme.colorScheme.primary,
iconLeading: Boolean = false,
/** When false, render only the verification icon — no handle text. The handle itself
* is reserved for the profile screen; everywhere else (feed, threads, comments) just
* the badge icon appears next to the username. */
showHandle: Boolean = true,
modifier: Modifier = Modifier
) {
if (nip05.isBlank()) return
Expand Down Expand Up @@ -1370,10 +1378,29 @@ internal fun Nip05Badge(
}
)
) {
if (!showHandle) {
// Icon-only badge: appears once verification resolves. No retry icon here —
// a transient relay error shouldn't flag every row across the timeline.
when {
status == Nip05Status.VERIFIED -> Icon(
Icons.Default.Verified,
contentDescription = "Verified",
tint = verifiedTint,
modifier = Modifier.size(14.dp)
)
isImpersonator -> Icon(
Icons.Default.Cancel,
contentDescription = "Impersonator",
tint = Color.Red,
modifier = Modifier.size(14.dp)
)
}
return@Row
}
if (iconLeading) {
if (status == Nip05Status.VERIFIED) {
Icon(
Icons.Default.CheckCircle,
Icons.Default.Verified,
contentDescription = "Verified",
tint = verifiedTint,
modifier = Modifier.size(14.dp)
Expand Down Expand Up @@ -1409,7 +1436,7 @@ internal fun Nip05Badge(
if (status == Nip05Status.VERIFIED) {
Spacer(Modifier.width(4.dp))
Icon(
Icons.Default.CheckCircle,
Icons.Default.Verified,
contentDescription = "Verified",
tint = verifiedTint,
modifier = Modifier.size(14.dp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.ContentScale
Expand Down Expand Up @@ -196,6 +197,10 @@ private fun BaseProfilePicture(
@Composable
private fun FollowBadge(size: Int, modifier: Modifier = Modifier) {
val badgeSize = (size * 0.3f).coerceIn(10f, 16f)
// iOS parity: the check flips with the color scheme — black on the
// orange circle in dark mode, white in light mode. Derive from the
// active surface so theme presets stay correct too.
val checkColor = if (MaterialTheme.colorScheme.surface.luminance() < 0.5f) Color.Black else Color.White
Box(
contentAlignment = Alignment.Center,
modifier = modifier
Expand All @@ -207,7 +212,7 @@ private fun FollowBadge(size: Int, modifier: Modifier = Modifier) {
Icon(
Icons.Default.Check,
contentDescription = "Following",
tint = MaterialTheme.colorScheme.onPrimary,
tint = checkColor,
modifier = Modifier.size((badgeSize * 0.65f).dp)
)
}
Expand Down
19 changes: 16 additions & 3 deletions app/src/main/kotlin/com/wisp/app/ui/component/RichContent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1316,15 +1316,28 @@ fun QuotedNote(
Row(verticalAlignment = Alignment.CenterVertically) {
ProfilePicture(url = profile?.picture, size = 34)
Spacer(Modifier.width(10.dp))
Column(modifier = Modifier.weight(1f)) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
Text(
text = profile?.displayString
?: event.pubkey.toNpub().let { "${it.take(12)}...${it.takeLast(4)}" },
style = MaterialTheme.typography.titleSmall,
color = MaterialTheme.colorScheme.onSurface,
maxLines = 1,
overflow = TextOverflow.Ellipsis
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false)
)
profile?.nip05?.let { nip05 ->
Spacer(Modifier.width(4.dp))
Nip05Badge(
nip05 = nip05,
pubkey = event.pubkey,
nip05Repo = noteActions?.nip05Repo,
showHandle = false
)
}
}
Text(
text = formatQuotedTimestamp(event.created_at),
Expand Down Expand Up @@ -2172,7 +2185,7 @@ private fun formatQuotedTimestamp(epoch: Long): String {
seconds < 60 -> "${seconds}s"
minutes < 60 -> "${minutes}m"
hours < 24 -> "${hours}h"
days == 1L -> "yesterday"
days < 7 -> "${days}d"
else -> java.text.SimpleDateFormat("MMM d", java.util.Locale.US).format(java.util.Date(epoch * 1000))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2256,7 +2256,7 @@ private fun formatNotifTimestamp(epoch: Long): String {
if (hours < 24) return "${hours}h"

val days = diff / (24 * 60 * 60 * 1000L)
if (days == 1L) return "yesterday"
if (days < 7) return "${days}d"

val date = Date(millis)
val cal = java.util.Calendar.getInstance()
Expand Down