new chat screen
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M336,680L280,624L424,480L280,337L336,281L480,425L623,281L679,337L535,480L679,624L623,680L480,536L336,680Z"/>
|
||||
</vector>
|
||||
@@ -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="M120,800L120,200Q120,167 143.5,143.5Q167,120 200,120L680,120Q713,120 736.5,143.5Q760,167 760,200L760,403Q750,401 740,400.5Q730,400 720,400Q710,400 700,400.5Q690,401 680,403L680,200Q680,200 680,200Q680,200 680,200L200,200Q200,200 200,200Q200,200 200,200L200,600L483,600Q481,610 480.5,620Q480,630 480,640Q480,650 480.5,660Q481,670 483,680L240,680L120,800ZM280,360L600,360L600,280L280,280L280,360ZM280,520L480,520L480,440L280,440L280,520ZM680,800L680,680L560,680L560,600L680,600L680,480L760,480L760,600L880,600L880,680L760,680L760,800L680,800ZM200,600L200,600L200,200Q200,200 200,200Q200,200 200,200L200,200Q200,200 200,200Q200,200 200,200L200,403Q200,453 200,501.5Q200,550 200,600Z" />
|
||||
</vector>
|
||||
@@ -14,10 +14,10 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
@@ -26,8 +26,10 @@ import su.reya.coop.coop.storage.SecretStore
|
||||
import su.reya.coop.screens.ChatScreen
|
||||
import su.reya.coop.screens.HomeScreen
|
||||
import su.reya.coop.screens.ImportScreen
|
||||
import su.reya.coop.screens.NewChatScreen
|
||||
import su.reya.coop.screens.NewIdentityScreen
|
||||
import su.reya.coop.screens.OnboardingScreen
|
||||
import su.reya.coop.screens.ScanScreen
|
||||
|
||||
val LocalNostrViewModel = staticCompositionLocalOf<NostrViewModel> {
|
||||
error("No NostrViewModel provided")
|
||||
@@ -37,18 +39,26 @@ val LocalSnackbarHostState = staticCompositionLocalOf<SnackbarHostState> {
|
||||
error("No SnackbarHostState provided")
|
||||
}
|
||||
|
||||
val LocalNavController = staticCompositionLocalOf<NavController> {
|
||||
error("No NavController provided")
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun App(dbPath: String) {
|
||||
val context = LocalContext.current
|
||||
val navController = rememberNavController()
|
||||
val darkMode = isSystemInDarkTheme()
|
||||
|
||||
// Snackbar
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
// Initialize Nostr and SecretStore
|
||||
val nostr = remember { Nostr() }
|
||||
val secretStore = remember { SecretStore(context) }
|
||||
val viewModel: NostrViewModel = viewModel { NostrViewModel(nostr, secretStore) }
|
||||
|
||||
// Dynamic color scheme
|
||||
val darkMode = isSystemInDarkTheme()
|
||||
|
||||
// Enabled the dynamic color scheme
|
||||
val colorScheme = when {
|
||||
android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.S -> {
|
||||
if (darkMode) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
@@ -58,13 +68,12 @@ fun App(dbPath: String) {
|
||||
else -> expressiveLightColorScheme()
|
||||
}
|
||||
|
||||
// Snackbar
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.initAndConnect(dbPath)
|
||||
viewModel.startNotificationHandler()
|
||||
viewModel.getChatRooms()
|
||||
|
||||
// Collect error events from the ViewModel
|
||||
viewModel.errorEvents.collect { message ->
|
||||
snackbarHostState.showSnackbar(message)
|
||||
}
|
||||
@@ -76,9 +85,8 @@ fun App(dbPath: String) {
|
||||
CompositionLocalProvider(
|
||||
LocalNostrViewModel provides viewModel,
|
||||
LocalSnackbarHostState provides snackbarHostState,
|
||||
LocalNavController provides navController,
|
||||
) {
|
||||
rememberCoroutineScope()
|
||||
val navController = rememberNavController()
|
||||
val emptySecret by viewModel.emptySecret.collectAsState(initial = null)
|
||||
|
||||
LaunchedEffect(emptySecret) {
|
||||
@@ -136,7 +144,8 @@ fun App(dbPath: String) {
|
||||
}
|
||||
composable<Screen.Home> { backStackEntry ->
|
||||
HomeScreen(
|
||||
onOpenChat = { id -> navController.navigate(Screen.Chat(id)) }
|
||||
onOpenChat = { id -> navController.navigate(Screen.Chat(id)) },
|
||||
onNewChat = { navController.navigate(Screen.NewChat) }
|
||||
)
|
||||
}
|
||||
composable<Screen.Chat> { backStackEntry ->
|
||||
@@ -146,6 +155,16 @@ fun App(dbPath: String) {
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
composable<Screen.NewChat> { backStackEntry ->
|
||||
NewChatScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
composable<Screen.Scan> { backStackEntry ->
|
||||
ScanScreen(
|
||||
onBack = { navController.popBackStack() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ sealed interface Screen {
|
||||
@Serializable
|
||||
data class Chat(val id: Long) : Screen
|
||||
|
||||
@Serializable
|
||||
data object NewChat : Screen
|
||||
|
||||
@Serializable
|
||||
data object Onboarding : Screen
|
||||
|
||||
@@ -17,4 +20,7 @@ sealed interface Screen {
|
||||
|
||||
@Serializable
|
||||
data object NewIdentity : Screen
|
||||
|
||||
@Serializable
|
||||
data object Scan : Screen
|
||||
}
|
||||
|
||||
@@ -13,9 +13,11 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.ExtendedFloatingActionButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.ListItem
|
||||
@@ -24,17 +26,23 @@ import androidx.compose.material3.MaterialShapes
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SegmentedListItem
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TooltipAnchorPosition
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.material3.toShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -47,6 +55,8 @@ import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.toClipEntry
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coop.composeapp.generated.resources.Res
|
||||
import coop.composeapp.generated.resources.ic_new_chat
|
||||
import coop.composeapp.generated.resources.ic_scanner
|
||||
import coop.composeapp.generated.resources.ic_search
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
@@ -61,7 +71,10 @@ import su.reya.coop.short
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(onOpenChat: (Long) -> Unit) {
|
||||
fun HomeScreen(
|
||||
onOpenChat: (Long) -> Unit,
|
||||
onNewChat: () -> Unit,
|
||||
) {
|
||||
val clipboard = LocalClipboard.current
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
@@ -74,6 +87,8 @@ fun HomeScreen(onOpenChat: (Long) -> Unit) {
|
||||
val chatRooms by viewModel.chatRooms.collectAsState(initial = emptyList())
|
||||
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
val listState = rememberLazyListState()
|
||||
val expandedFab by remember { derivedStateOf { listState.firstVisibleItemIndex == 0 } }
|
||||
var showBottomSheet by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
@@ -98,6 +113,13 @@ fun HomeScreen(onOpenChat: (Long) -> Unit) {
|
||||
contentDescription = "Search"
|
||||
)
|
||||
}
|
||||
// QR Scanner
|
||||
IconButton(onClick = { /* TODO: Open search */ }) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_scanner),
|
||||
contentDescription = "Scanner"
|
||||
)
|
||||
}
|
||||
// User
|
||||
IconButton(onClick = { showBottomSheet = true }) {
|
||||
Avatar(
|
||||
@@ -109,6 +131,32 @@ fun HomeScreen(onOpenChat: (Long) -> Unit) {
|
||||
}
|
||||
)
|
||||
},
|
||||
floatingActionButton = {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(
|
||||
TooltipAnchorPosition.Above,
|
||||
spacingBetweenTooltipAndAnchor = 8.dp,
|
||||
),
|
||||
tooltip = {
|
||||
if (!expandedFab) {
|
||||
PlainTooltip { Text("New Chat") }
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState(),
|
||||
) {
|
||||
ExtendedFloatingActionButton(
|
||||
onClick = onNewChat,
|
||||
expanded = expandedFab,
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_new_chat),
|
||||
contentDescription = "New Chat"
|
||||
)
|
||||
},
|
||||
text = { Text("New Chat") },
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
@@ -137,6 +185,7 @@ fun HomeScreen(onOpenChat: (Long) -> Unit) {
|
||||
}
|
||||
} else {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
items(chatRooms.toList(), key = { it.id }) { room ->
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
package su.reya.coop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.InputChip
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.SegmentedListItem
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coop.composeapp.generated.resources.Res
|
||||
import coop.composeapp.generated.resources.ic_arrow_back
|
||||
import coop.composeapp.generated.resources.ic_close_small
|
||||
import coop.composeapp.generated.resources.ic_scanner
|
||||
import kotlinx.coroutines.delay
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import rust.nostr.sdk.PublicKey
|
||||
import su.reya.coop.LocalNavController
|
||||
import su.reya.coop.LocalNostrViewModel
|
||||
import su.reya.coop.LocalSnackbarHostState
|
||||
import su.reya.coop.Screen
|
||||
import su.reya.coop.shared.Avatar
|
||||
import su.reya.coop.short
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun NewChatScreen(
|
||||
onBack: () -> Unit,
|
||||
) {
|
||||
val snackbarHostState = LocalSnackbarHostState.current
|
||||
val navController = LocalNavController.current
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
|
||||
val selectedReceivers = remember { mutableStateListOf<PublicKey>() }
|
||||
var query by remember { mutableStateOf("") }
|
||||
|
||||
val savedStateHandle = navController.currentBackStackEntry?.savedStateHandle
|
||||
val qrResult by savedStateHandle?.getStateFlow<String?>("qr_result", null)?.collectAsState()
|
||||
?: remember { mutableStateOf(null) }
|
||||
|
||||
LaunchedEffect(query) {
|
||||
if (query.length >= 3) {
|
||||
delay(500) // 500ms debounce
|
||||
// TODO: Implement search
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(qrResult) {
|
||||
qrResult?.let {
|
||||
println("QR result: $it")
|
||||
navController.currentBackStackEntry?.savedStateHandle?.remove<String>("qr_result")
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("New Chat") },
|
||||
colors = TopAppBarDefaults.topAppBarColors(
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainer,
|
||||
),
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_arrow_back),
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { navController.navigate(Screen.Scan) }) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_scanner),
|
||||
contentDescription = "Scanner"
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding)
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
shape = RoundedCornerShape(28.dp),
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "To:",
|
||||
modifier = Modifier.align(Alignment.Top),
|
||||
style = MaterialTheme.typography.labelMediumEmphasized,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
selectedReceivers.forEach { receiver ->
|
||||
ReceiverChip(
|
||||
pubkey = receiver,
|
||||
onRemove = { selectedReceivers.remove(receiver) }
|
||||
)
|
||||
}
|
||||
BasicTextField(
|
||||
value = query,
|
||||
onValueChange = { query = it },
|
||||
modifier = Modifier
|
||||
.widthIn(min = 50.dp)
|
||||
.align(Alignment.CenterVertically),
|
||||
textStyle = MaterialTheme.typography.bodyMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSurface
|
||||
),
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.primary),
|
||||
decorationBox = { innerTextField ->
|
||||
Box(contentAlignment = Alignment.CenterStart) {
|
||||
if (query.isEmpty() && selectedReceivers.isEmpty()) {
|
||||
Text(
|
||||
"Type a npub or address",
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(
|
||||
alpha = 0.5f
|
||||
)
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
) {
|
||||
// TODO: add result list
|
||||
ContactList(
|
||||
selectedReceivers = selectedReceivers,
|
||||
onContactClick = { pubkey ->
|
||||
val roomId = viewModel.createChatRoom(listOf(pubkey))
|
||||
navController.navigate(Screen.Chat(roomId))
|
||||
}
|
||||
)
|
||||
Spacer(modifier = Modifier.size(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ReceiverChip(
|
||||
pubkey: PublicKey,
|
||||
onRemove: () -> Unit
|
||||
) {
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
|
||||
val metadata by metadataFlow.collectAsState(initial = null)
|
||||
|
||||
val profile = metadata?.asRecord()
|
||||
val displayName = profile?.name ?: profile?.displayName ?: pubkey.short()
|
||||
val picture = profile?.picture
|
||||
|
||||
InputChip(
|
||||
selected = true,
|
||||
onClick = onRemove,
|
||||
label = { Text(displayName) },
|
||||
avatar = {
|
||||
Avatar(
|
||||
picture = picture,
|
||||
description = displayName,
|
||||
size = 24.dp
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_close_small),
|
||||
contentDescription = "Close"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun ContactList(
|
||||
selectedReceivers: SnapshotStateList<PublicKey>,
|
||||
onContactClick: (PublicKey) -> Unit
|
||||
) {
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
val contactList by viewModel.contactList.collectAsState(initial = emptySet())
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalArrangement = Arrangement.spacedBy(ListItemDefaults.SegmentedGap),
|
||||
) {
|
||||
Text(
|
||||
text = "Contacts",
|
||||
style = MaterialTheme.typography.titleLargeEmphasized,
|
||||
)
|
||||
Spacer(modifier = Modifier.size(8.dp))
|
||||
contactList.forEachIndexed { index, item ->
|
||||
ContactListItem(
|
||||
pubkey = item,
|
||||
index = index,
|
||||
total = contactList.size,
|
||||
isSelected = selectedReceivers.contains(item),
|
||||
onClick = { onContactClick(item) },
|
||||
onLongClick = {
|
||||
if (!selectedReceivers.contains(item)) {
|
||||
selectedReceivers.add(item)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun ContactListItem(
|
||||
pubkey: PublicKey,
|
||||
index: Int,
|
||||
total: Int = 0,
|
||||
isSelected: Boolean,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
) {
|
||||
val viewModel = LocalNostrViewModel.current
|
||||
val metadataFlow = remember(pubkey) { viewModel.getMetadata(pubkey) }
|
||||
val metadata by metadataFlow.collectAsState(initial = null)
|
||||
|
||||
val profile = metadata?.asRecord()
|
||||
val displayName = profile?.name ?: profile?.displayName ?: pubkey.short()
|
||||
val picture = profile?.picture
|
||||
|
||||
SegmentedListItem(
|
||||
selected = isSelected,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
shapes = ListItemDefaults.segmentedShapes(
|
||||
index = index,
|
||||
count = total
|
||||
),
|
||||
leadingContent = {
|
||||
Avatar(
|
||||
picture = picture,
|
||||
description = displayName,
|
||||
size = 36.dp
|
||||
)
|
||||
},
|
||||
supportingContent = { Text(text = pubkey.short()) },
|
||||
content = {
|
||||
Text(
|
||||
text = displayName,
|
||||
style = MaterialTheme.typography.titleMediumEmphasized,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package su.reya.coop.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import coop.composeapp.generated.resources.Res
|
||||
import coop.composeapp.generated.resources.ic_arrow_back
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
import su.reya.coop.LocalNavController
|
||||
|
||||
@Composable
|
||||
fun ScanScreen(
|
||||
onBack: () -> Unit
|
||||
) {
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val onResult: (String) -> Unit = { result ->
|
||||
navController.previousBackStackEntry
|
||||
?.savedStateHandle
|
||||
?.set("qr_result", result)
|
||||
navController.popBackStack()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("Scan QR") },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
painter = painterResource(Res.drawable.ic_arrow_back),
|
||||
contentDescription = "Back"
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
) { innerPadding ->
|
||||
Box(modifier = Modifier.padding(innerPadding)) {
|
||||
Text("Scan QR")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user