diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml new file mode 100644 index 0000000..8c7d915 --- /dev/null +++ b/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/constants/Constants.kt b/app/src/main/java/com/chatgptlite/wanted/constants/Constants.kt index 3372de9..a4c0e9f 100644 --- a/app/src/main/java/com/chatgptlite/wanted/constants/Constants.kt +++ b/app/src/main/java/com/chatgptlite/wanted/constants/Constants.kt @@ -8,4 +8,4 @@ const val urlToAvatarGPT = "https://gptapk.com/wp-content/uploads/2023/02/chatgp const val urlToGithub = "https://github.com/lambiengcode" const val matchResultString = "\"text\":" -const val matchResultTurboString = "\"content\":" \ No newline at end of file +const val matchResultTurboString = "\"content\":" diff --git a/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepository.kt b/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepository.kt index 771af81..20f49e3 100644 --- a/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepository.kt +++ b/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepository.kt @@ -5,5 +5,5 @@ import com.chatgptlite.wanted.models.ConversationModel interface ConversationRepository { suspend fun fetchConversations() : MutableList fun newConversation(conversation: ConversationModel) : ConversationModel - fun deleteConversation() + suspend fun deleteConversation(conversationId: String) } \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepositoryImpl.kt b/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepositoryImpl.kt index 0ccd169..1c017c3 100644 --- a/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepositoryImpl.kt +++ b/app/src/main/java/com/chatgptlite/wanted/data/remote/ConversationRepositoryImpl.kt @@ -1,7 +1,11 @@ package com.chatgptlite.wanted.data.remote +import android.content.ContentValues +import android.util.Log import com.chatgptlite.wanted.constants.conversationCollection +import com.chatgptlite.wanted.helpers.DataHolder import com.chatgptlite.wanted.models.ConversationModel +import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot @@ -11,12 +15,11 @@ import javax.inject.Inject class ConversationRepositoryImpl @Inject constructor( private val fsInstance: FirebaseFirestore, ) : ConversationRepository { + override suspend fun fetchConversations(): MutableList { - val result: QuerySnapshot = fsInstance.collection(conversationCollection) - .orderBy("createdAt", Query.Direction.DESCENDING).get().await() - if (result.documents.isNotEmpty()) { - val documents = result.documents + if (getFireBaseSnapShot().documents.isNotEmpty()) { + val documents = getFireBaseSnapShot().documents return documents.map { it.toObject(ConversationModel::class.java) @@ -31,8 +34,45 @@ class ConversationRepositoryImpl @Inject constructor( return conversation } - override fun deleteConversation() { - TODO("Not yet implemented") + override suspend fun deleteConversation(conversationId: String) { + var desiredKey: String? = null + + getFireBaseSnapShot().documents.map { documentSnapshot -> + val id = documentSnapshot.getString("id") + if (id == conversationId) { + desiredKey = documentSnapshot.id + } else { + null + } + } + DataHolder.docPath = desiredKey.toString() + + val docRef = fsInstance + .collection("conversations") + .document(DataHolder.docPath) + + // Remove the 'capital' field from the document + val updates = hashMapOf( + "id" to FieldValue.delete(), + "title" to FieldValue.delete(), + "createdAt" to FieldValue.delete() + ) + docRef.update(updates) + .addOnSuccessListener { + Log.d( + ContentValues.TAG, + "DocumentSnapshot successfully deleted from message!" + ) + } + .addOnFailureListener { e -> + Log.w( + ContentValues.TAG, + "Error deleting document", e + ) + } } + private suspend fun getFireBaseSnapShot() = + fsInstance.collection(conversationCollection) + .orderBy("createdAt", Query.Direction.DESCENDING).get().await() } \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepository.kt b/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepository.kt index 6c8d508..28437ca 100644 --- a/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepository.kt +++ b/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepository.kt @@ -6,5 +6,5 @@ import kotlinx.coroutines.flow.Flow interface MessageRepository { fun fetchMessages(conversationId: String): Flow> fun createMessage(message: MessageModel): MessageModel - fun deleteMessage(message: MessageModel) + fun deleteMessage() } \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepositoryImpl.kt b/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepositoryImpl.kt index 64b9a9d..74dfcc2 100644 --- a/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepositoryImpl.kt +++ b/app/src/main/java/com/chatgptlite/wanted/data/remote/MessageRepositoryImpl.kt @@ -1,7 +1,11 @@ package com.chatgptlite.wanted.data.remote +import android.content.ContentValues +import android.util.Log import com.chatgptlite.wanted.constants.messageCollection +import com.chatgptlite.wanted.helpers.DataHolder import com.chatgptlite.wanted.models.MessageModel +import com.google.firebase.firestore.FieldValue import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.Query import com.google.firebase.firestore.QuerySnapshot @@ -14,9 +18,13 @@ import javax.inject.Inject class MessageRepositoryImpl @Inject constructor( private val fsInstance: FirebaseFirestore, ) : MessageRepository { - override fun fetchMessages(conversationId: String): Flow> = callbackFlow { - val result: QuerySnapshot = - fsInstance.collection(messageCollection).whereEqualTo("conversationId", conversationId) + private lateinit var result: QuerySnapshot + override fun fetchMessages(conversationId: String): Flow> = + callbackFlow { + result = + fsInstance + .collection(messageCollection) + .whereEqualTo("conversationId", conversationId) .orderBy("createdAt", Query.Direction.DESCENDING).get().await() if (result.documents.isNotEmpty()) { @@ -44,7 +52,32 @@ class MessageRepositoryImpl @Inject constructor( return message } - override fun deleteMessage(message: MessageModel) { - TODO("Not yet implemented") + override fun deleteMessage() { + val docRef = fsInstance + .collection("messages") + .document(DataHolder.docPath) + + // Remove the fields from the document + val updates = hashMapOf( + "answer" to FieldValue.delete(), + "conversationId" to FieldValue.delete(), + "createdAt" to FieldValue.delete(), + "id" to FieldValue.delete(), + "question" to FieldValue.delete() + ) + docRef.update(updates) + .addOnSuccessListener { + Log.d( + ContentValues.TAG, + "DocumentSnapshot successfully deleted from message!" + ) + } + .addOnFailureListener { e -> + Log.w( + ContentValues.TAG, + "Error deleting document", e + ) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/helpers/DataHolder.kt b/app/src/main/java/com/chatgptlite/wanted/helpers/DataHolder.kt new file mode 100644 index 0000000..8dd4955 --- /dev/null +++ b/app/src/main/java/com/chatgptlite/wanted/helpers/DataHolder.kt @@ -0,0 +1,5 @@ +package com.chatgptlite.wanted.helpers + +object DataHolder { + var docPath: String = "" +} \ No newline at end of file diff --git a/app/src/main/java/com/chatgptlite/wanted/ui/common/AppDrawer.kt b/app/src/main/java/com/chatgptlite/wanted/ui/common/AppDrawer.kt index a7b1d5c..3908a80 100644 --- a/app/src/main/java/com/chatgptlite/wanted/ui/common/AppDrawer.kt +++ b/app/src/main/java/com/chatgptlite/wanted/ui/common/AppDrawer.kt @@ -22,10 +22,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit -import androidx.compose.material.icons.filled.Message -import androidx.compose.material.icons.filled.Settings -import androidx.compose.material.icons.filled.WbSunny +import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.AddComment import androidx.compose.material3.Divider import androidx.compose.material3.Icon @@ -159,7 +156,7 @@ private fun ColumnScope.HistoryConversations( .weight(1f, false), ) { items(conversations.size) { index -> - ChatItem( + RecycleChatItem( text = conversations[index].title, Icons.Filled.Message, selected = conversations[index].id == conversationId, @@ -170,6 +167,12 @@ private fun ColumnScope.HistoryConversations( conversationViewModel.onConversation(conversations[index]) } }, + onDeleteClicked = { + scope.launch { + conversationViewModel.deleteConversation(conversations[index].id) + conversationViewModel.deleteMessages(conversations[index].id) + } + } ) } } @@ -240,6 +243,67 @@ private fun ChatItem( ) } } +@Composable +private fun RecycleChatItem( + text: String, + icon: ImageVector = Icons.Filled.Edit, + selected: Boolean, + onChatClicked: () -> Unit, + onDeleteClicked: () -> Unit +) { + val background = if (selected) { + Modifier.background(MaterialTheme.colorScheme.primaryContainer) + } else { + Modifier + } + Row( + modifier = Modifier + .height(56.dp) + .fillMaxWidth() + .padding(horizontal = 34.dp) + .clip(CircleShape) + .then(background) + .clickable(onClick = onChatClicked), + verticalAlignment = CenterVertically + ) { + val iconTint = if (selected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurfaceVariant + } + Icon( + icon, + tint = iconTint, + modifier = Modifier + .padding(start = 16.dp, top = 16.dp, bottom = 16.dp) + .size(25.dp), + contentDescription = null, + ) + Text( + text, + style = MaterialTheme.typography.bodyMedium, + color = if (selected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface + }, + modifier = Modifier.padding(start = 12.dp).fillMaxWidth(0.85f), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + Spacer(Modifier.weight(0.9f, true)) + Icon( + imageVector = Icons.Filled.Delete, + contentDescription = "Delete", + tint = if (selected) { + MaterialTheme.colorScheme.primary + } else { + MaterialTheme.colorScheme.onSurface + }, + modifier = Modifier.clickable { onDeleteClicked() } + ) + } +} @Composable private fun ProfileItem(text: String, urlToImage: String?, onProfileClicked: () -> Unit) { diff --git a/app/src/main/java/com/chatgptlite/wanted/ui/conversations/ConversationViewModel.kt b/app/src/main/java/com/chatgptlite/wanted/ui/conversations/ConversationViewModel.kt index 7fbd6a2..8430a14 100644 --- a/app/src/main/java/com/chatgptlite/wanted/ui/conversations/ConversationViewModel.kt +++ b/app/src/main/java/com/chatgptlite/wanted/ui/conversations/ConversationViewModel.kt @@ -134,9 +134,11 @@ class ConversationViewModel @Inject constructor( var response: String = "" for (message in messagesMap[conversationId]!!.reversed()) { - response += """ -Human:${message.question.trim()} -Bot:${if (message.answer == "Let me thinking...") "" else message.answer.trim()}""" + response += """Human:${message.question.trim()} + |Bot:${ + if (message.answer == "Let me thinking...") "" + else message.answer.trim() + }""".trimMargin() } return response @@ -150,8 +152,7 @@ Bot:${if (message.answer == "Let me thinking...") "" else message.answer.trim()} val response: MutableList = mutableListOf( MessageTurbo( - role = TurboRole.system, - content = "Markdown style if exists code" + role = TurboRole.system, content = "Markdown style if exists code" ) ) @@ -166,8 +167,24 @@ Bot:${if (message.answer == "Let me thinking...") "" else message.answer.trim()} return response.toList() } + fun deleteMessages(conversationId: String) { + + val conversations: MutableList = _conversations.value.toMutableList() + val conversationToRemove = conversations.find { it.id == conversationId } + + if (conversationToRemove != null) { + conversations.remove(conversationToRemove) + _conversations.value = conversations + } + messageRepo.deleteMessage() + } + + suspend fun deleteConversation(conversationId: String) = + conversationRepo.deleteConversation(conversationId) + private suspend fun fetchMessages() { - if (_currentConversation.value.isEmpty() || _messages.value[_currentConversation.value] != null) return + if (_currentConversation.value.isEmpty() || + _messages.value[_currentConversation.value] != null) return val flow: Flow> = messageRepo.fetchMessages(_currentConversation.value)