@@ -10,17 +10,23 @@ import androidx.paging.RemoteMediator
1010import com.getcode.db.AppDatabase
1111import com.getcode.db.Database
1212import com.getcode.manager.SessionManager
13+ import com.getcode.model.ChatMessage
1314import com.getcode.model.Conversation
1415import com.getcode.model.ConversationMessage
1516import com.getcode.model.ConversationMessageContent
1617import com.getcode.model.ID
1718import com.getcode.model.MessageStatus
19+ import com.getcode.network.client.ChatMessageStreamReference
20+ import com.getcode.network.client.Client
21+ import com.getcode.network.client.openChatStream
1822import com.getcode.network.exchange.Exchange
1923import com.getcode.network.repository.base58
24+ import com.getcode.network.repository.decodeBase64
2025import com.getcode.network.source.ConversationMockProvider
2126import com.getcode.vendor.Base58
2227import kotlinx.coroutines.CoroutineScope
2328import kotlinx.coroutines.Dispatchers
29+ import kotlinx.coroutines.GlobalScope
2430import kotlinx.coroutines.delay
2531import kotlinx.coroutines.flow.Flow
2632import kotlinx.coroutines.launch
@@ -29,96 +35,142 @@ import timber.log.Timber
2935import java.io.IOException
3036import java.util.UUID
3137import javax.inject.Inject
38+ import kotlin.jvm.Throws
3239
3340interface ConversationController {
3441 fun observeConversationForMessage (messageId : ID ): Flow <Conversation ?>
35- suspend fun getConversationForMessage (messageId : ID ): Conversation ?
36- suspend fun getConversation (conversationId : ID ): Conversation ?
37- suspend fun createConversation (messageId : ID )
42+ fun openChatStream (scope : CoroutineScope , messageId : ID )
43+ fun closeChatStream ()
3844 suspend fun hasThanked (messageId : ID ): Boolean
3945 suspend fun thankTipper (messageId : ID )
4046 suspend fun revealIdentity (messageId : ID )
4147 fun sendMessage (conversationId : ID , message : String )
42- fun conversationPagingData (conversationId : ID ): Flow <PagingData <ConversationMessage >>
48+ fun conversationPagingData (chatId : ID ): Flow <PagingData <ConversationMessage >>
4349}
4450
45- class ConversationMockController @Inject constructor(
51+ class ConversationStreamController @Inject constructor(
4652 private val historyController : HistoryController ,
4753 private val exchange : Exchange ,
48- ) : ConversationController, CoroutineScope by CoroutineScope( Dispatchers . IO ) {
49-
54+ private val client : Client
55+ ): ConversationController {
5056 private val pagingConfig = PagingConfig (pageSize = 20 )
5157
5258 private val db: AppDatabase by lazy { Database .requireInstance() }
5359
60+ private var stream: ChatMessageStreamReference ? = null
61+
5462 private fun conversationPagingSource (conversationId : ID ) =
5563 db.conversationMessageDao().observeConversationMessages(conversationId.base58)
5664
5765 override fun observeConversationForMessage (messageId : ID ): Flow <Conversation ?> {
5866 return db.conversationDao().observeConversationForMessage(messageId)
5967 }
60- override suspend fun getConversationForMessage (messageId : ID ): Conversation ? {
61- return db.conversationDao().findConversationForMessage(messageId)
68+
69+ @Throws(IllegalStateException ::class )
70+ override fun openChatStream (scope : CoroutineScope , messageId : ID ) {
71+ val chatId = " 468f158662880905e966f7c27f36b39e368837887aa5cf889cb55d91537d1a76" .decodeBase64().toList()
72+ val owner = SessionManager .getOrganizer()?.ownerKeyPair ? : throw IllegalStateException ()
73+ stream = client.openChatStream(scope, chatId, owner) { result ->
74+ if (result.isSuccess) {
75+ println (" chat messages: ${result.getOrNull()} " )
76+ }
77+ }
6278 }
6379
64- override suspend fun getConversation (conversationId : ID ): Conversation ? {
65- return db.conversationDao().findConversation(conversationId)
80+ override fun closeChatStream () {
81+ stream?.destroy()
82+ }
83+
84+ override suspend fun hasThanked (messageId : ID ): Boolean {
85+ val conversation = db.conversationDao().findConversationForMessage(messageId) ? : return false
86+ return db.conversationDao().hasThanked(conversation.messageId)
87+ }
88+
89+ override suspend fun thankTipper (messageId : ID ) {
90+ }
91+
92+ override suspend fun revealIdentity (messageId : ID ) {
93+ }
94+
95+ override fun sendMessage (conversationId : ID , message : String ) {
96+
6697 }
6798
6899 @OptIn(ExperimentalPagingApi ::class )
69- override fun conversationPagingData (conversationId : ID ) =
100+ override fun conversationPagingData (chatId : ID ) =
70101 Pager (
71102 config = pagingConfig,
72103 initialKey = null ,
73104 remoteMediator = ConversationMessagePageKeyedRemoteMediator (db)
74- ) { conversationPagingSource(conversationId) }.flow
75-
76- override suspend fun createConversation (messageId : ID ) {
77- Timber .d(" creating conversation: ${messageId.base58} " )
78- val message =
79- historyController.chats.value?.find {
80- Timber .d(" messages=${it.messages.joinToString { it.id.base58 }} " )
81- it.messages.firstOrNull { it.id == messageId } != null
82- }?.messages?.find { it.id == messageId }
83-
84- if (message == null ) {
85- Timber .e(" No message for ${messageId.base58} found" )
86- return
87- }
105+ ) { conversationPagingSource(chatId) }.flow
88106
89- val conversation = ConversationMockProvider .createConversation(exchange, message)
90- if (conversation == null ) {
91- Timber .e(" Failed to create conversation!" )
92- return
93- }
107+ }
108+ class ConversationMockController @Inject constructor(
109+ private val historyController : HistoryController ,
110+ private val exchange : Exchange ,
111+ ) : ConversationController {
112+
113+ private val pagingConfig = PagingConfig (pageSize = 20 )
94114
95- db.conversationDao().upsertConversations(conversation)
115+ private val db : AppDatabase by lazy { Database .requireInstance() }
96116
97- val tipMessage = ConversationMockProvider .createMessage(
98- conversation.messageId,
99- ConversationMessageContent .TipMessage
100- )
117+ private fun conversationPagingSource (conversationId : ID ) =
118+ db.conversationMessageDao().observeConversationMessages(conversationId.base58)
101119
102- Timber .d( " upserting tip message " )
103- db.conversationMessageDao ().upsertMessages(tipMessage )
120+ override fun observeConversationForMessage ( messageId : ID ): Flow < Conversation ?> {
121+ return db.conversationDao ().observeConversationForMessage(messageId )
104122 }
105123
124+ override fun openChatStream (scope : CoroutineScope , messageId : ID ) {
125+ scope.launch {
126+ Timber .d(" creating conversation: ${messageId.base58} " )
127+ val message =
128+ historyController.chats.value?.find {
129+ Timber .d(" messages=${it.messages.joinToString { it.id.base58 }} " )
130+ it.messages.firstOrNull { it.id == messageId } != null
131+ }?.messages?.find { it.id == messageId }
132+
133+ if (message == null ) {
134+ Timber .e(" No message for ${messageId.base58} found" )
135+ return @launch
136+ }
137+
138+ val conversation = ConversationMockProvider .createConversation(exchange, message)
139+ if (conversation == null ) {
140+ Timber .e(" Failed to create conversation!" )
141+ return @launch
142+ }
143+
144+ db.conversationDao().upsertConversations(conversation)
145+
146+ val tipMessage = ConversationMockProvider .createMessage(
147+ conversation.messageId,
148+ ConversationMessageContent .TipMessage
149+ )
150+
151+ Timber .d(" upserting tip message" )
152+ db.conversationMessageDao().upsertMessages(tipMessage)
153+ }
154+ }
155+
156+ override fun closeChatStream () {
157+
158+ }
159+
160+ @OptIn(ExperimentalPagingApi ::class )
161+ override fun conversationPagingData (chatId : ID ) =
162+ Pager (
163+ config = pagingConfig,
164+ initialKey = null ,
165+ remoteMediator = ConversationMessagePageKeyedRemoteMediator (db)
166+ ) { conversationPagingSource(chatId) }.flow
167+
106168 override suspend fun hasThanked (messageId : ID ): Boolean {
107169 val conversation = db.conversationDao().findConversationForMessage(messageId) ? : return false
108170 return db.conversationDao().hasThanked(conversation.messageId)
109171 }
110172
111173 override suspend fun thankTipper (messageId : ID ) {
112- val conversation = db.conversationDao().findConversationForMessage(messageId)
113- if (conversation == null ) {
114- Timber .d(" conversation doesn't exist.. creating" )
115- val message =
116- historyController.chats.value?.find { it.messages.firstOrNull { it.id == messageId } != null }
117- ?.messages?.find { it.id == messageId } ? : return
118-
119- createConversation(message.id)
120- }
121-
122174 val message = ConversationMockProvider .thankTipper(messageId) ? : return
123175 db.conversationMessageDao().upsertMessages(message)
124176 }
@@ -129,7 +181,7 @@ class ConversationMockController @Inject constructor(
129181 }
130182
131183 override fun sendMessage (conversationId : ID , message : String ) {
132- launch {
184+ GlobalScope . launch {
133185 val messageId = generateId()
134186
135187 val tipAddress = SessionManager .getOrganizer()?.primaryVault
0 commit comments