Send message to existing chat

This commit is contained in:
Kgothatso Ngako
2026-07-05 22:03:51 +02:00
parent 5103588519
commit 49eb22f146
10 changed files with 247 additions and 177 deletions

View File

@@ -3,16 +3,17 @@ package at.torch.compose.database.dao
import androidx.room3.Dao
import androidx.room3.Query
import androidx.room3.Upsert
import at.torch.compose.database.model.Participant
@Dao
interface ParticipantDao {
@Query("SELECT * FROM Participant WHERE participantPublicKey = :participantPublicKey")
fun findParticipantByPublicKey(participantPublicKey: String): at.torch.compose.database.model.Participant?
fun findParticipantByPublicKey(participantPublicKey: String): List<Participant>
@Query("SELECT * FROM Participant WHERE chatRoomId = :chatRoomId")
fun findParticipantsByChatRoomId(chatRoomId: String): List<at.torch.compose.database.model.Participant>
fun findParticipantsByChatRoomId(chatRoomId: String): List<Participant>
@Upsert
suspend fun upsert(participants: List<at.torch.compose.database.model.Participant>)
suspend fun upsert(participants: List<Participant>)
}

View File

@@ -3,6 +3,7 @@ package at.torch.compose.database.repository
import at.torch.compose.database.model.ChatMessage
import at.torch.compose.database.model.GiftWrapPayload
import at.torch.compose.database.model.MarmotInnerEvent
import at.torch.compose.database.model.Participant
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
@@ -38,10 +39,13 @@ class DatabaseChatRepository(
return database.chatRoomDao().getChatRoomListByUserPublicKey(publicKey)
}
override suspend fun getChatRoomByIdentifier(id: String): at.torch.compose.database.model.intermdiate.LocalChatRoom? {
override suspend fun getChatRoomByIdentifier(id: HexKey): at.torch.compose.database.model.intermdiate.LocalChatRoom? {
return database.chatRoomDao().findChatRoomById(id)
}
override suspend fun getAllParticipantsWithPubKey(publicKey: HexKey): List<Participant> {
return database.participantDao().findParticipantByPublicKey(publicKey)
}
override suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<at.torch.compose.database.model.intermdiate.LocalChatMessage> {
return database.chatMessageDao().getChatMessagesByChatRoomId(chatRoomId)
}

View File

@@ -1,5 +1,8 @@
package at.torch.compose.repository
import at.torch.compose.database.model.Participant
import at.torch.compose.database.model.intermdiate.LocalChatMessage
import at.torch.compose.database.model.intermdiate.LocalChatRoom
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.core.Kind
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
@@ -9,15 +12,17 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
interface ChatRepository {
suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<at.torch.compose.database.model.intermdiate.LocalChatRoom>>
suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>>
suspend fun getChatRoomListByPublicKey(publicKey: String): List<at.torch.compose.database.model.intermdiate.LocalChatRoom>
suspend fun getChatRoomListByPublicKey(publicKey: String): List<LocalChatRoom>
suspend fun getChatRoomByIdentifier(id: String): at.torch.compose.database.model.intermdiate.LocalChatRoom?
suspend fun getChatRoomByIdentifier(id: HexKey): LocalChatRoom?
suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<at.torch.compose.database.model.intermdiate.LocalChatMessage>
suspend fun getAllParticipantsWithPubKey(publicKey: HexKey): List<Participant>
suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<at.torch.compose.database.model.intermdiate.LocalChatMessage>>
suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<LocalChatMessage>
suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<LocalChatMessage>>
suspend fun getOrCreateChatRoom(
chatRoomId: String,
@@ -25,13 +30,13 @@ interface ChatRepository {
relayHint: String?,
defaultSubject: String?,
mlsGroupState: String?
): at.torch.compose.database.model.intermdiate.LocalChatRoom?
): LocalChatRoom?
suspend fun getChatMessageRelayForPublicKey(publicKey: HexKey): ChatMessageRelayListEvent?
suspend fun sendChatMessage(
text: String,
localChatRoom: at.torch.compose.database.model.intermdiate.LocalChatRoom,
localChatRoom: LocalChatRoom,
messageType: Kind = ChatMessageEvent.KIND
)
@@ -43,23 +48,27 @@ interface ChatRepository {
companion object {
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
override suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<at.torch.compose.database.model.intermdiate.LocalChatRoom>> {
override suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
return flow { }
}
override suspend fun getChatRoomListByPublicKey(publicKey: String): List<at.torch.compose.database.model.intermdiate.LocalChatRoom> {
override suspend fun getChatRoomListByPublicKey(publicKey: String): List<LocalChatRoom> {
return emptyList()
}
override suspend fun getChatRoomByIdentifier(id: String): at.torch.compose.database.model.intermdiate.LocalChatRoom? {
override suspend fun getChatRoomByIdentifier(id: HexKey): LocalChatRoom? {
return null
}
override suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<at.torch.compose.database.model.intermdiate.LocalChatMessage> {
override suspend fun getAllParticipantsWithPubKey(publicKey: HexKey): List<Participant> {
TODO("Not yet implemented")
}
override suspend fun getChatMessageListByChatRoomId(chatRoomId: String): List<LocalChatMessage> {
return emptyList()
}
override suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<at.torch.compose.database.model.intermdiate.LocalChatMessage>> {
override suspend fun observeChatMessageListByChatRoomId(chatRoomId: String): Flow<List<LocalChatMessage>> {
return flow {
}
}
@@ -70,7 +79,7 @@ interface ChatRepository {
relayHint: String?,
defaultSubject: String?,
mlsGroupState: String?
): at.torch.compose.database.model.intermdiate.LocalChatRoom? {
): LocalChatRoom? {
return null
}
@@ -80,7 +89,7 @@ interface ChatRepository {
override suspend fun sendChatMessage(
text: String,
localChatRoom: at.torch.compose.database.model.intermdiate.LocalChatRoom,
localChatRoom: LocalChatRoom,
messageType: Kind
) {
// TODO("Not yet implemented")

View File

@@ -37,7 +37,12 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import at.torch.compose.repository.ChatRepository
import at.torch.compose.repository.NostrRepository
import at.torch.compose.ui.composable.navigation.routes.Route
import at.torch.compose.ui.composable.widgets.LoadingDataIndicator
import at.torch.compose.ui.view.model.ChatRoomDetailViewModel
import at.torch.compose.ui.view.state.ChatRoomDetailUIState
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@@ -46,9 +51,10 @@ fun ChatRoomDetailScreen(
activeUserPublicKey: HexKey,
chatRoomId: String,
relayHint: String?,
initialChatRoomDetailUIState: at.torch.compose.ui.view.state.ChatRoomDetailUIState = at.torch.compose.ui.view.state.ChatRoomDetailUIState.Loading,
nostrRepository: at.torch.compose.repository.NostrRepository,
chatRepository: at.torch.compose.repository.ChatRepository
initialChatRoomDetailUIState: ChatRoomDetailUIState = ChatRoomDetailUIState.Loading,
nostrRepository: NostrRepository,
chatRepository: ChatRepository,
onNavigateToChat: (Route) -> Unit,
) {
val chatRoomDetailViewModel: ChatRoomDetailViewModel = viewModel(
factory = ChatRoomDetailViewModel.factory(
@@ -62,7 +68,7 @@ fun ChatRoomDetailScreen(
)
when (val chatRoomDetailUIState = chatRoomDetailViewModel.chatRoomDetailUIState) {
at.torch.compose.ui.view.state.ChatRoomDetailUIState.Error -> {
ChatRoomDetailUIState.Error -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -75,7 +81,7 @@ fun ChatRoomDetailScreen(
)
}
}
is at.torch.compose.ui.view.state.ChatRoomDetailUIState.Loaded -> {
is ChatRoomDetailUIState.Loaded -> {
val textFieldState = rememberTextFieldState()
val chatRoomDetailMessageListViewModel: at.torch.compose.ui.view.model.ChatRoomDetailMessageListViewModel = viewModel(
@@ -228,7 +234,7 @@ fun ChatRoomDetailScreen(
}
}
}
at.torch.compose.ui.view.state.ChatRoomDetailUIState.Loading -> {
ChatRoomDetailUIState.Loading -> {
Column(
modifier = Modifier.fillMaxWidth().padding(
20.dp
@@ -252,7 +258,7 @@ fun ChatRoomDetailScreen(
textAlign = TextAlign.Center
)
_root_ide_package_.at.torch.compose.ui.composable.widgets.LoadingDataIndicator(
LoadingDataIndicator(
fillScreen = false
)
@@ -261,10 +267,43 @@ fun ChatRoomDetailScreen(
)
}
}
ChatRoomDetailUIState.InitiatingNewChat -> {
Column(
modifier = Modifier.fillMaxWidth().padding(
20.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
) {
Spacer(
modifier = Modifier.weight(1f)
)
Text(
text = "Creating new chat.",
style = MaterialTheme.typography.bodySmall,
textAlign = TextAlign.Center
)
LoadingDataIndicator(
fillScreen = false
)
Spacer(
modifier = Modifier.weight(2f)
)
LaunchedEffect(true) {
chatRoomDetailViewModel.initiateNewChat(
onNavigateToChat
)
}
}
}
}
LaunchedEffect(true) {
if (initialChatRoomDetailUIState == at.torch.compose.ui.view.state.ChatRoomDetailUIState.Loading) {
if (initialChatRoomDetailUIState == ChatRoomDetailUIState.Loading) {
chatRoomDetailViewModel.initiateChatRoomDetail()
}
}
@@ -281,7 +320,7 @@ private fun ChatRoomDetailScreenPreview() {
activeUserPublicKey = "",
chatRoomId = "publicKey",
relayHint = null,
initialChatRoomDetailUIState = at.torch.compose.ui.view.state.ChatRoomDetailUIState.Loaded(
initialChatRoomDetailUIState = ChatRoomDetailUIState.Loaded(
localChatRoom = _root_ide_package_.at.torch.compose.database.model.intermdiate.LocalChatRoom(
chatRoom = _root_ide_package_.at.torch.compose.database.model.ChatRoom(
id = "",
@@ -292,8 +331,9 @@ private fun ChatRoomDetailScreenPreview() {
),
)
),
nostrRepository = at.torch.compose.repository.NostrRepository.NO_OP_NOSTR_REPOSITORY,
chatRepository = at.torch.compose.repository.ChatRepository.NO_OP_CHAT_REPOSITORY
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY,
chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY,
onNavigateToChat = {}
)
}
}

View File

@@ -15,6 +15,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import at.torch.compose.ui.composable.navigation.routes.Route
import at.torch.compose.ui.composable.widgets.detail.MetadataEventDetail
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
@@ -28,8 +30,8 @@ fun NostrEventDetailScreen(
nostrEventId: HexKey,
nostrRepository: at.torch.compose.repository.NostrRepository,
onNavigateBack: () -> Unit,
onNavigateToEvent: (at.torch.compose.ui.composable.navigation.routes.Route) -> Unit,
onNavigateToDirectMessage: (at.torch.compose.ui.composable.navigation.routes.Route) -> Unit,
onNavigateToEvent: (Route) -> Unit,
onNavigateToDirectMessage: (Route) -> Unit,
onNavigateToEditProfile: () -> Unit,
onNavigateToWriteAReply: (String) -> Unit,
onNavigateToQuoteNostrEvent: (String) -> Unit,
@@ -57,7 +59,7 @@ fun NostrEventDetailScreen(
when(feedListUIState.localNostrEvent.nostrEvent.kind) {
MetadataEvent.KIND -> {
_root_ide_package_.at.torch.compose.ui.composable.widgets.detail.MetadataEventDetail(
MetadataEventDetail(
activeUserPublicKey = activeUserPublicKey,
localNostrEvent = feedListUIState.localNostrEvent,
onNavigateBack = onNavigateBack,
@@ -68,7 +70,7 @@ fun NostrEventDetailScreen(
)
}
TextNoteEvent.KIND -> {
_root_ide_package_.at.torch.compose.ui.composable.widgets.detail.TextNoteEventDetail(
at.torch.compose.ui.composable.widgets.detail.TextNoteEventDetail(
feedListUIState.localNostrEvent,
onNavigateBack = onNavigateBack,
onNavigateToWriteAReply = onNavigateToWriteAReply,
@@ -108,7 +110,7 @@ fun NostrEventDetailScreen(
}
}
at.torch.compose.ui.view.state.NostrEventDetailUIState.Loading -> {
_root_ide_package_.at.torch.compose.ui.composable.widgets.LoadingDataIndicator()
at.torch.compose.ui.composable.widgets.LoadingDataIndicator()
LaunchedEffect(true) {
nostrEventDetailViewModel.loadNostrFeed()
@@ -129,14 +131,14 @@ fun NostrEventDetailScreen(
@Preview
@Composable
private fun NostrEventDetailScreenPreview() {
_root_ide_package_.at.torch.compose.ui.theme.TorchTheme {
at.torch.compose.ui.theme.TorchTheme {
Surface(
modifier = Modifier.padding(20.dp)
) {
NostrEventDetailScreen(
initialNostrEventDetailUIState = at.torch.compose.ui.view.state.NostrEventDetailUIState.Loaded(
localNostrEvent = _root_ide_package_.at.torch.compose.database.model.intermdiate.LocalNostrEvent(
nostrEvent = _root_ide_package_.at.torch.compose.database.model.NostrEvent(
localNostrEvent = at.torch.compose.database.model.intermdiate.LocalNostrEvent(
nostrEvent = at.torch.compose.database.model.NostrEvent(
id = "eventId",
pubKey = "publicKey",
content = """Lorem Ipsum is simply dummy text of the printing and typesetting industry.
@@ -148,7 +150,7 @@ It has survived not only five centuries, but also the leap into electronic types
sig = "",
kind = TextNoteEvent.KIND
),
profile = _root_ide_package_.at.torch.compose.database.model.Profile(
profile = at.torch.compose.database.model.Profile(
publicKey = "pubKey",
displayName = "John Doe",
nostrEventId = "nostrEventId"

View File

@@ -23,6 +23,7 @@ import at.torch.compose.ui.composable.ShareProfileScreen
import at.torch.compose.ui.composable.UnsyncedProfileScreen
import at.torch.compose.ui.composable.navigation.routes.ActiveProfileRoute
import at.torch.compose.ui.composable.navigation.routes.ChatRoomCreationRoute
import at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute
import at.torch.compose.ui.composable.navigation.routes.KeyPackageManagementRoute
import at.torch.compose.ui.composable.navigation.routes.LoadingRoute
import at.torch.compose.ui.composable.navigation.routes.NostrEventDetailRoute
@@ -510,15 +511,24 @@ fun TorchNavHost(
}
)
}
composable<at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute>()
composable<ChatRoomDetailRoute> { backStackEntry ->
val route = backStackEntry.toRoute<ChatRoomDetailRoute>()
ChatRoomDetailScreen(
activeUserPublicKey = route.activeUserPublicKey,
chatRoomId = route.chatRoomId,
relayHint = route.relayHint,
nostrRepository = databaseNostrRepository,
chatRepository = databaseChatRepository
chatRepository = databaseChatRepository,
onNavigateToChat = { chatRoomDetailRoute ->
navController.navigate(
route = chatRoomDetailRoute
) {
popUpTo(route) {
inclusive = true
}
}
},
)
}
composable<at.torch.compose.ui.composable.navigation.routes.SearchRoute> { backStackEntry ->
@@ -598,10 +608,14 @@ fun TorchNavHost(
)
)
},
onNavigateToDirectMessage = { route ->
onNavigateToDirectMessage = { chatRoom ->
navController.navigate(
route = route
)
route = chatRoom
) {
popUpTo(route) {
inclusive = true
}
}
},
)
}

View File

@@ -8,13 +8,16 @@ 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.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBackIosNew
import androidx.compose.material.icons.filled.Block
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.Error
import androidx.compose.material.icons.filled.GroupAdd
import androidx.compose.material.icons.filled.Mail
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
@@ -29,6 +32,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Tab
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
@@ -42,6 +46,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute
import at.torch.compose.ui.composable.navigation.routes.ImplementationPendingRoute
import com.vitorpamplona.quartz.nip01Core.core.HexKey
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
import kotlinx.coroutines.launch
@@ -125,7 +131,9 @@ fun MetadataEventDetail(
) {
LazyColumn(
modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
item {
Column(
@@ -207,24 +215,6 @@ fun MetadataEventDetail(
LoadingIndicator()
}
}
IconButton(
onClick = {
onNavigateToChatRoom.invoke(
_root_ide_package_.at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute(
activeUserPublicKey = activeUserPublicKey,
chatRoomId = localNostrEvent.nostrEvent.pubKey,
relayHint = localNostrEvent.nostrEvent.relayUrl,
)
)
}
) {
Icon(
Icons.Default.Mail,
contentDescription = "Direct Message"
)
}
}
Text(
modifier = Modifier.fillMaxWidth().padding(
@@ -238,117 +228,69 @@ fun MetadataEventDetail(
}
}
stickyHeader {
PrimaryScrollableTabRow(
modifier = Modifier.padding(
horizontal = 10.dp
),
edgePadding = 10.dp,
selectedTabIndex = metadataEventDetailViewModel.pagerState.currentPage,
) {
at.torch.compose.ui.view.model.MetadataEventDetailType.entries.forEachIndexed { index, destination ->
Tab(
selected = metadataEventDetailViewModel.pagerState.currentPage == index,
onClick = {
scope.launch {
metadataEventDetailViewModel.pagerState.animateScrollToPage(destination.ordinal)
}
},
text = {
Text(
text = destination.name,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
item {
TextButton(
onClick = {
onNavigateToEvent.invoke(
ImplementationPendingRoute("Add to Group")
)
}
) {
Icon(
Icons.Default.GroupAdd,
contentDescription = "Add to Group"
)
Spacer(
modifier = Modifier.width(10.dp)
)
Text("Add To Group")
}
}
item {
Column(
modifier = Modifier.fillParentMaxSize()
) {
HorizontalPager(
state = metadataEventDetailViewModel.pagerState,
) { pageIndex ->
val metadataEventDetailType = at.torch.compose.ui.view.model.MetadataEventDetailType.entries[pageIndex]
when (metadataEventDetailType) {
at.torch.compose.ui.view.model.MetadataEventDetailType.Posts, at.torch.compose.ui.view.model.MetadataEventDetailType.Replies,
at.torch.compose.ui.view.model.MetadataEventDetailType.Articles, at.torch.compose.ui.view.model.MetadataEventDetailType.Zaps,
at.torch.compose.ui.view.model.MetadataEventDetailType.Photos, at.torch.compose.ui.view.model.MetadataEventDetailType.Shorts, at.torch.compose.ui.view.model.MetadataEventDetailType.Videos,
at.torch.compose.ui.view.model.MetadataEventDetailType.Bookmarks, at.torch.compose.ui.view.model.MetadataEventDetailType.Reports, at.torch.compose.ui.view.model.MetadataEventDetailType.Relays -> {
val feedListViewModel: at.torch.compose.ui.view.model.FeedListViewModel = viewModel(
key = metadataEventDetailType.name,
factory = at.torch.compose.ui.view.model.FeedListViewModel.factory(
initialFeedListUIState = at.torch.compose.ui.view.state.FeedListUIState.Loading,
synchronizationFilter = metadataEventDetailViewModel.getSynchronizationFilter(metadataEventDetailType),
nostrRepository = nostrRepository
),
)
key(feedListViewModel.feedListUIState) {
feedListViewModel.RenderFeed(
activeUserPublicKey = activeUserPublicKey,
onNavigateToEvent = onNavigateToEvent
)
}
key(true) {
feedListViewModel.initiate()
}
}
// TODO: Support gallery feeds...
at.torch.compose.ui.view.model.MetadataEventDetailType.Following -> {
val followingListViewModel: at.torch.compose.ui.view.model.FollowingListViewModel = viewModel(
key = metadataEventDetailType.name,
factory = at.torch.compose.ui.view.model.FollowingListViewModel.factory(
initialFeedListUIState = at.torch.compose.ui.view.state.FollowingListUIState.Loading,
publicKey = localNostrEvent.nostrEvent.pubKey,
synchronizationFilter = metadataEventDetailViewModel.getSynchronizationFilter(metadataEventDetailType),
nostrRepository = nostrRepository
),
)
key(followingListViewModel.followingListUIState) {
followingListViewModel.RenderFeed(
activeUserPublicKey = activeUserPublicKey,
onNavigateToEvent = onNavigateToEvent
)
}
key(true) {
followingListViewModel.initiate()
}
}
at.torch.compose.ui.view.model.MetadataEventDetailType.Followers -> {
val followersListViewModel: at.torch.compose.ui.view.model.FollowersListViewModel = viewModel(
key = metadataEventDetailType.name,
factory = at.torch.compose.ui.view.model.FollowersListViewModel.factory(
initialFeedListUIState = at.torch.compose.ui.view.state.FollowersListUIState.Loading,
publicKey = localNostrEvent.nostrEvent.pubKey,
synchronizationFilter = metadataEventDetailViewModel.getSynchronizationFilter(metadataEventDetailType),
nostrRepository = nostrRepository
),
)
key(followersListViewModel.followersListUIState) {
followersListViewModel.RenderFeed(
activeUserPublicKey = activeUserPublicKey,
onNavigateToEvent = onNavigateToEvent
)
}
key(true) {
followersListViewModel.initiate()
}
}
}
TextButton(
onClick = {
onNavigateToEvent.invoke(
ImplementationPendingRoute("Block User")
)
}
) {
Icon(
Icons.Default.Block,
contentDescription = "Block"
)
Spacer(
modifier = Modifier.width(10.dp)
)
Text("Block User")
}
}
item {
Button(
onClick = {
onNavigateToChatRoom.invoke(
ChatRoomDetailRoute(
activeUserPublicKey = activeUserPublicKey,
chatRoomId = localNostrEvent.nostrEvent.pubKey,
relayHint = localNostrEvent.nostrEvent.relayUrl,
)
)
}
) {
Icon(
Icons.Default.Mail,
contentDescription = "Direct Message"
)
Spacer(
modifier = Modifier.width(10.dp)
)
Text("Send Message")
}
}
}

View File

@@ -43,6 +43,7 @@ import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import at.torch.compose.extensions.toFormattedTimeAndDateString
import at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
@@ -52,13 +53,13 @@ import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
class ChatRoomDetailMessageListViewModel(
initialChatRoomDetailMessageListUIState: at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState,
initialChatRoomDetailMessageListUIState: ChatRoomDetailMessageListUIState,
val localChatRoom: at.torch.compose.database.model.intermdiate.LocalChatRoom,
val nostrRepository: at.torch.compose.repository.NostrRepository,
val chatRepository: at.torch.compose.repository.ChatRepository
): ViewModel() {
val logger = Logger.withTag(TAG)
var chatRoomDetailMessageListUIState: at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState by mutableStateOf(initialChatRoomDetailMessageListUIState)
var chatRoomDetailMessageListUIState: ChatRoomDetailMessageListUIState by mutableStateOf(initialChatRoomDetailMessageListUIState)
private set
val isReceiverChatMessageRelayListMissing: MutableState<Boolean> = mutableStateOf(false)
@@ -76,7 +77,7 @@ class ChatRoomDetailMessageListViewModel(
localChatRoom.chatRoom.id
).distinctUntilChanged().collect { chatMessages ->
logger.d("getChatMessageListByChatRoomId: $chatMessages")
chatRoomDetailMessageListUIState = at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState.Loaded(
chatRoomDetailMessageListUIState = ChatRoomDetailMessageListUIState.Loaded(
chatMessageList = chatMessages
)
}
@@ -161,7 +162,7 @@ class ChatRoomDetailMessageListViewModel(
horizontalAlignment = Alignment.CenterHorizontally
) {
when (val chatRoomDetailMessageListUIState = this@ChatRoomDetailMessageListViewModel.chatRoomDetailMessageListUIState) {
at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState.Error -> {
ChatRoomDetailMessageListUIState.Error -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -174,7 +175,7 @@ class ChatRoomDetailMessageListViewModel(
)
}
}
is at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState.Loaded -> {
is ChatRoomDetailMessageListUIState.Loaded -> {
Spacer(
modifier = Modifier.weight(1f)
)
@@ -342,7 +343,7 @@ class ChatRoomDetailMessageListViewModel(
}
}
}
at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState.Loading -> {
ChatRoomDetailMessageListUIState.Loading -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -384,7 +385,7 @@ class ChatRoomDetailMessageListViewModel(
const val TAG = "ChatRoomDetailMessageListViewModel"
fun factory(
initialChatRoomDetailMessageListUIState: at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState = at.torch.compose.ui.view.state.ChatRoomDetailMessageListUIState.Loading,
initialChatRoomDetailMessageListUIState: ChatRoomDetailMessageListUIState = ChatRoomDetailMessageListUIState.Loading,
localChatRoom: at.torch.compose.database.model.intermdiate.LocalChatRoom,
nostrRepository: at.torch.compose.repository.NostrRepository,
chatRepository: at.torch.compose.repository.ChatRepository

View File

@@ -9,6 +9,9 @@ import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import at.torch.compose.repository.ChatRepository
import at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute
import at.torch.compose.ui.composable.navigation.routes.Route
import at.torch.compose.ui.view.state.ChatRoomDetailUIState
import co.touchlab.kermit.Logger
import com.vitorpamplona.quartz.nip01Core.core.HexKey
@@ -22,7 +25,7 @@ class ChatRoomDetailViewModel(
val relayHint: String?,
initialChatRoomDetailUIState: ChatRoomDetailUIState,
val nostrRepository: at.torch.compose.repository.NostrRepository,
val chatRepository: at.torch.compose.repository.ChatRepository
val chatRepository: ChatRepository
): ViewModel() {
var chatRoomDetailUIState: ChatRoomDetailUIState by mutableStateOf(initialChatRoomDetailUIState)
@@ -41,7 +44,8 @@ class ChatRoomDetailViewModel(
val localChatRoom = chatRepository.getChatRoomByIdentifier(chatRoomId)
chatRoomDetailUIState = if (localChatRoom == null) {
ChatRoomDetailUIState.Error
// Initiate new chat... and navigate to new chat...
ChatRoomDetailUIState.InitiatingNewChat
} else {
ChatRoomDetailUIState.Loaded(
localChatRoom = localChatRoom
@@ -50,6 +54,58 @@ class ChatRoomDetailViewModel(
}
}
fun initiateNewChat(
onNavigateToChat: (Route) -> Unit
) {
logger.d("initiateNewChat: $chatRoomId")
// Get ChatRoomById
viewModelScope.launch(Dispatchers.IO) {
// Check if we have a chat with activeUserPublicKey and chatRoomId (pubkey) as participants
val participants = chatRepository.getAllParticipantsWithPubKey(chatRoomId)
participants.forEach { participant ->
chatRepository.getChatRoomByIdentifier(participant.chatRoomId)?.let { localChatRoom ->
// TODO: Check that chatRoom is not a group chat...
if (localChatRoom.localParticipants.size == 1) {
// This is a chat with self...
// TODO: navigateToTheChat...
viewModelScope.launch(Dispatchers.Main) {
onNavigateToChat.invoke(
ChatRoomDetailRoute(
activeUserPublicKey = activeUserPublicKey,
chatRoomId = localChatRoom.chatRoom.id,
relayHint = relayHint
)
)
}
return@launch
} else if (localChatRoom.localParticipants.size == 2) {
val activeUserParticipant = localChatRoom.localParticipants.find { localParticipant -> localParticipant.participant.participantPublicKey == activeUserPublicKey }
if (activeUserParticipant != null) {
// We have found the chat we are looking for...
viewModelScope.launch(Dispatchers.Main) {
onNavigateToChat.invoke(
ChatRoomDetailRoute(
activeUserPublicKey = activeUserPublicKey,
chatRoomId = localChatRoom.chatRoom.id,
relayHint = relayHint
)
)
}
return@launch
}
}
}
}
logger.d("We should be creating a new chat...")
chatRoomDetailUIState = ChatRoomDetailUIState.Error
}
}
companion object {
private const val TAG = "ChatRoomDetailViewModel"
@@ -60,7 +116,7 @@ class ChatRoomDetailViewModel(
relayHint: String?,
initialChatRoomDetailUIState: ChatRoomDetailUIState = ChatRoomDetailUIState.Loading,
nostrRepository: at.torch.compose.repository.NostrRepository,
chatRepository: at.torch.compose.repository.ChatRepository
chatRepository: ChatRepository
): ViewModelProvider.Factory = viewModelFactory {
initializer {
ChatRoomDetailViewModel(

View File

@@ -7,5 +7,6 @@ sealed interface ChatRoomDetailUIState {
data object Error: ChatRoomDetailUIState
data object Loading: ChatRoomDetailUIState
data object InitiatingNewChat: ChatRoomDetailUIState
}