update screener card

This commit is contained in:
2026-06-19 16:06:30 +07:00
parent b1cb8b0fea
commit 19bce35fd4
6 changed files with 114 additions and 26 deletions

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:autoMirrored="true"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880Q397,880 324,848.5ZM480,800Q534,800 584,782.5Q634,765 676,732L228,284Q195,326 177.5,376Q160,426 160,480Q160,614 253,707Q346,800 480,800ZM732,676Q765,634 782.5,584Q800,534 800,480Q800,346 707,253Q614,160 480,160Q426,160 376,177.5Q326,195 284,228L732,676ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M336,680L480,536L624,680L680,624L536,480L680,336L624,280L480,424L336,280L280,336L424,480L280,624L336,680ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M424,664L706,382L650,326L424,552L310,438L254,494L424,664ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M330,840L120,630L120,330L330,120L630,120L840,330L840,630L630,840L330,840ZM366,650L480,536L594,650L650,594L536,480L650,366L594,310L480,424L366,310L310,366L424,480L310,594L366,650ZM364,760L596,760L760,596L760,364L596,200L364,200L200,364L200,596L364,760ZM480,480L480,480L480,480L480,480L480,480L480,480L480,480L480,480Z" />
</vector>

View File

@@ -23,6 +23,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@@ -31,7 +32,6 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialShapes import androidx.compose.material3.MaterialShapes
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
@@ -60,6 +60,8 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.Res
import coop.composeapp.generated.resources.ic_arrow_back import coop.composeapp.generated.resources.ic_arrow_back
import coop.composeapp.generated.resources.ic_cancel
import coop.composeapp.generated.resources.ic_check_circle
import coop.composeapp.generated.resources.ic_send import coop.composeapp.generated.resources.ic_send
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@@ -72,10 +74,13 @@ import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room import su.reya.coop.Room
import su.reya.coop.Screen import su.reya.coop.Screen
import su.reya.coop.formatAsGroupHeader import su.reya.coop.formatAsGroupHeader
import su.reya.coop.humanReadable
import su.reya.coop.roomId import su.reya.coop.roomId
import su.reya.coop.shared.Avatar import su.reya.coop.shared.Avatar
import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.nameFlow import su.reya.coop.shared.nameFlow
import su.reya.coop.shared.pictureFlow import su.reya.coop.shared.pictureFlow
import su.reya.coop.short
@Composable @Composable
fun ChatScreen(id: Long, screening: Boolean = false) { fun ChatScreen(id: Long, screening: Boolean = false) {
@@ -275,11 +280,13 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
when (requireScreening) { when (requireScreening) {
true -> { true -> {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
FilledTonalButton( Button(
onClick = { navigator.navigate(Screen.Home) }, onClick = { navigator.goBack() },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
Text( Text(
@@ -287,7 +294,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
style = MaterialTheme.typography.titleMedium, style = MaterialTheme.typography.titleMedium,
) )
} }
Button( FilledTonalButton(
onClick = { requireScreening = false }, onClick = { requireScreening = false },
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
@@ -316,6 +323,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
) )
} }
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
fun ScreenerCard(room: Room) { fun ScreenerCard(room: Room) {
val pubkey = room.members.firstOrNull() ?: return val pubkey = room.members.firstOrNull() ?: return
@@ -334,7 +342,7 @@ fun ScreenerCard(room: Room) {
val displayName = profile?.displayName ?: profile?.name ?: "No name" val displayName = profile?.displayName ?: profile?.name ?: "No name"
val picture = profile?.picture val picture = profile?.picture
LaunchedEffect(Unit) { LaunchedEffect(pubkey) {
scope.launch { scope.launch {
// Check contact // Check contact
viewModel.verifyContact(pubkey).let { isContact = it } viewModel.verifyContact(pubkey).let { isContact = it }
@@ -345,55 +353,95 @@ fun ScreenerCard(room: Room) {
} }
} }
OutlinedCard( Column(
modifier = Modifier.padding(16.dp) modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.padding(top = 48.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Avatar( Avatar(
picture = picture, picture = picture,
description = "Profile picture", description = "Profile picture",
modifier = Modifier.size(48.dp), modifier = Modifier.size(120.dp),
shape = MaterialShapes.Cookie12Sided.toShape(), shape = MaterialShapes.Cookie12Sided.toShape(),
) )
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text( Text(
text = displayName, text = displayName,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = MaterialTheme.typography.titleMediumEmphasized, style = MaterialTheme.typography.titleLargeEmphasized.copy(
fontFamily = getExpressiveFontFamily()
),
) )
Text(
text = pubkey.short(),
textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.outline
)
}
} }
Column( Column(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp), verticalArrangement = Arrangement.spacedBy(4.dp),
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Icon(
painter = painterResource(
if (isContact) Res.drawable.ic_check_circle else Res.drawable.ic_cancel
),
contentDescription = "Warning",
tint = if (isContact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
)
Text( Text(
text = if (isContact) "Contact" else "Not a contact", text = if (isContact) "Contact" else "Not a contact",
style = MaterialTheme.typography.labelMediumEmphasized
) )
} }
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Icon(
painter = painterResource(
if (mutualContacts.isNotEmpty()) Res.drawable.ic_check_circle else Res.drawable.ic_cancel
),
contentDescription = "Warning",
tint = if (mutualContacts.isNotEmpty()) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.error
)
Text( Text(
text = if (mutualContacts.isEmpty()) "No contacts in common" else "${mutualContacts.size} contacts in common", text = if (mutualContacts.isEmpty()) "No contacts in common" else "${mutualContacts.size} contacts in common",
style = MaterialTheme.typography.labelMediumEmphasized
) )
} }
lastActivity?.toHumanDatetime()?.let {
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp),
) { ) {
Text( Icon(
text = "Last activity: $it" painter = painterResource(Res.drawable.ic_check_circle),
contentDescription = "Warning",
tint = if (lastActivity != null) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.outline
)
Text(
text = if (lastActivity == null) "Don't have any public activities" else "Last activity at ${lastActivity?.humanReadable()}",
style = MaterialTheme.typography.labelMediumEmphasized
) )
}
} }
} }
} }

View File

@@ -992,8 +992,11 @@ class Nostr {
suspend fun verifyContact(pubkey: PublicKey): Boolean { suspend fun verifyContact(pubkey: PublicKey): Boolean {
try { try {
val currentUser =
signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in")
val kind = Kind.fromStd(KindStandard.CONTACT_LIST) val kind = Kind.fromStd(KindStandard.CONTACT_LIST)
val filter = Filter().kind(kind).author(pubkey).limit(1u) val filter = Filter().kind(kind).author(currentUser).limit(1u)
val events = client?.database()?.query(filter) val events = client?.database()?.query(filter)
val pubkeys = events?.first()?.tags()?.publicKeys() ?: listOf() val pubkeys = events?.first()?.tags()?.publicKeys() ?: listOf()