diff --git a/composeApp/src/androidMain/composeResources/drawable/ic_block.xml b/composeApp/src/androidMain/composeResources/drawable/ic_block.xml
new file mode 100644
index 0000000..137fd90
--- /dev/null
+++ b/composeApp/src/androidMain/composeResources/drawable/ic_block.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/composeApp/src/androidMain/composeResources/drawable/ic_cancel.xml b/composeApp/src/androidMain/composeResources/drawable/ic_cancel.xml
new file mode 100644
index 0000000..e12530b
--- /dev/null
+++ b/composeApp/src/androidMain/composeResources/drawable/ic_cancel.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/androidMain/composeResources/drawable/ic_check_circle.xml b/composeApp/src/androidMain/composeResources/drawable/ic_check_circle.xml
new file mode 100644
index 0000000..a247a73
--- /dev/null
+++ b/composeApp/src/androidMain/composeResources/drawable/ic_check_circle.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/androidMain/composeResources/drawable/ic_warning.xml b/composeApp/src/androidMain/composeResources/drawable/ic_warning.xml
new file mode 100644
index 0000000..08484c4
--- /dev/null
+++ b/composeApp/src/androidMain/composeResources/drawable/ic_warning.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt
index acd38ec..fde4acf 100644
--- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt
+++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt
@@ -23,6 +23,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox
import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.FilledTonalButton
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
@@ -31,7 +32,6 @@ import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.MaterialShapes
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Surface
@@ -60,6 +60,8 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coop.composeapp.generated.resources.Res
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 kotlinx.coroutines.launch
import org.jetbrains.compose.resources.painterResource
@@ -72,10 +74,13 @@ import su.reya.coop.LocalSnackbarHostState
import su.reya.coop.Room
import su.reya.coop.Screen
import su.reya.coop.formatAsGroupHeader
+import su.reya.coop.humanReadable
import su.reya.coop.roomId
import su.reya.coop.shared.Avatar
+import su.reya.coop.shared.getExpressiveFontFamily
import su.reya.coop.shared.nameFlow
import su.reya.coop.shared.pictureFlow
+import su.reya.coop.short
@Composable
fun ChatScreen(id: Long, screening: Boolean = false) {
@@ -275,11 +280,13 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
when (requireScreening) {
true -> {
Row(
- modifier = Modifier.fillMaxWidth(),
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
- FilledTonalButton(
- onClick = { navigator.navigate(Screen.Home) },
+ Button(
+ onClick = { navigator.goBack() },
modifier = Modifier.weight(1f)
) {
Text(
@@ -287,7 +294,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
style = MaterialTheme.typography.titleMedium,
)
}
- Button(
+ FilledTonalButton(
onClick = { requireScreening = false },
modifier = Modifier.weight(1f)
) {
@@ -316,6 +323,7 @@ fun ChatScreen(id: Long, screening: Boolean = false) {
)
}
+@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
fun ScreenerCard(room: Room) {
val pubkey = room.members.firstOrNull() ?: return
@@ -334,7 +342,7 @@ fun ScreenerCard(room: Room) {
val displayName = profile?.displayName ?: profile?.name ?: "No name"
val picture = profile?.picture
- LaunchedEffect(Unit) {
+ LaunchedEffect(pubkey) {
scope.launch {
// Check contact
viewModel.verifyContact(pubkey).let { isContact = it }
@@ -345,55 +353,95 @@ fun ScreenerCard(room: Room) {
}
}
- OutlinedCard(
- modifier = Modifier.padding(16.dp)
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .padding(top = 48.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Column(
modifier = Modifier.fillMaxWidth(),
- verticalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Avatar(
picture = picture,
description = "Profile picture",
- modifier = Modifier.size(48.dp),
+ modifier = Modifier.size(120.dp),
shape = MaterialShapes.Cookie12Sided.toShape(),
)
- Text(
- text = displayName,
- textAlign = TextAlign.Center,
- style = MaterialTheme.typography.titleMediumEmphasized,
- )
+ Column(
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = displayName,
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.titleLargeEmphasized.copy(
+ fontFamily = getExpressiveFontFamily()
+ ),
+ )
+ Text(
+ text = pubkey.short(),
+ textAlign = TextAlign.Center,
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.outline
+ )
+ }
}
Column(
modifier = Modifier.fillMaxWidth(),
- verticalArrangement = Arrangement.spacedBy(8.dp),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
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 = if (isContact) "Contact" else "Not a contact",
+ style = MaterialTheme.typography.labelMediumEmphasized
)
}
Row(
modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
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 = if (mutualContacts.isEmpty()) "No contacts in common" else "${mutualContacts.size} contacts in common",
+ style = MaterialTheme.typography.labelMediumEmphasized
)
}
- lastActivity?.toHumanDatetime()?.let {
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(8.dp),
- ) {
- Text(
- text = "Last activity: $it"
- )
- }
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ Icon(
+ 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
+ )
}
}
}
diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt
index 0aca391..0ef6116 100644
--- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt
+++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt
@@ -992,8 +992,11 @@ class Nostr {
suspend fun verifyContact(pubkey: PublicKey): Boolean {
try {
+ val currentUser =
+ signer.getPublicKeyAsync() ?: throw IllegalStateException("User not signed in")
+
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 pubkeys = events?.first()?.tags()?.publicKeys() ?: listOf()