Send message to existing chat
This commit is contained in:
@@ -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>)
|
||||
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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 = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -7,5 +7,6 @@ sealed interface ChatRoomDetailUIState {
|
||||
|
||||
data object Error: ChatRoomDetailUIState
|
||||
data object Loading: ChatRoomDetailUIState
|
||||
data object InitiatingNewChat: ChatRoomDetailUIState
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user