diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt index 2e77a98b3..33320f2b0 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationFragment.kt @@ -61,6 +61,7 @@ import org.linphone.core.ChatMessage import org.linphone.core.tools.Log import org.linphone.databinding.ChatBubbleLongPressMenuBinding import org.linphone.databinding.ChatConversationFragmentBinding +import org.linphone.ui.main.MainActivity import org.linphone.ui.main.chat.adapter.ChatMessageBottomSheetAdapter import org.linphone.ui.main.chat.adapter.ConversationEventAdapter import org.linphone.ui.main.chat.model.ChatMessageDeliveryModel @@ -231,7 +232,8 @@ class ConversationFragment : GenericFragment() { (view.parent as? ViewGroup)?.doOnPreDraw { Log.e("$TAG Failed to find chat room, going back") goBack() - // TODO: show toast + val message = getString(R.string.toast_cant_find_conversation_to_display) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) } } else { sendMessageViewModel.configureChatRoom(viewModel.chatRoom) @@ -343,6 +345,14 @@ class ConversationFragment : GenericFragment() { } } + sendMessageViewModel.showRedToastEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val message = pair.first + val icon = pair.second + (requireActivity() as MainActivity).showRedToast(message, icon) + } + } + viewModel.searchFilter.observe(viewLifecycleOwner) { filter -> viewModel.applyFilter(filter.trim()) } @@ -432,6 +442,13 @@ class ConversationFragment : GenericFragment() { } } + sharedViewModel.forceRefreshConversationInfo.observe(viewLifecycleOwner) { + it.consume { + Log.i("$TAG Force refreshing conversation info") + viewModel.refresh() + } + } + sharedViewModel.forceRefreshConversationEvent.observe(viewLifecycleOwner) { it.consume { Log.i("$TAG Force refreshing chat messages list") diff --git a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt index b16a857ff..3dd8c43b3 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/fragment/ConversationInfoFragment.kt @@ -36,6 +36,7 @@ import org.linphone.R import org.linphone.core.tools.Log import org.linphone.databinding.ChatInfoFragmentBinding import org.linphone.databinding.ChatParticipantAdminPopupMenuBinding +import org.linphone.ui.main.MainActivity import org.linphone.ui.main.chat.adapter.ConversationParticipantsAdapter import org.linphone.ui.main.chat.model.ConversationEditSubjectDialogModel import org.linphone.ui.main.chat.model.ParticipantModel @@ -113,7 +114,8 @@ class ConversationInfoFragment : GenericFragment() { (view.parent as? ViewGroup)?.doOnPreDraw { Log.e("$TAG Failed to find chat room, going back") goBack() - // TODO: show toast ? + val message = getString(R.string.toast_cant_find_conversation_to_display) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) } } } @@ -129,18 +131,26 @@ class ConversationInfoFragment : GenericFragment() { viewModel.groupLeftEvent.observe(viewLifecycleOwner) { it.consume { - // TODO: show toast ? Log.i("$TAG Group has been left, leaving conversation info...") goBack() + val message = getString(R.string.toast_group_conversation_left) + (requireActivity() as MainActivity).showGreenToast( + message, + R.drawable.chat_teardrop_text + ) } } viewModel.historyDeletedEvent.observe(viewLifecycleOwner) { it.consume { - // TODO: show toast ? Log.i("$TAG History has been deleted, leaving conversation info...") sharedViewModel.forceRefreshConversationEvent.value = Event(true) goBack() + val message = getString(R.string.toast_conversation_history_deleted) + (requireActivity() as MainActivity).showGreenToast( + message, + R.drawable.chat_teardrop_text + ) } } @@ -160,6 +170,28 @@ class ConversationInfoFragment : GenericFragment() { } } + viewModel.infoChangedEvent.observe(viewLifecycleOwner) { + it.consume { + sharedViewModel.forceRefreshConversationInfo.postValue(Event(true)) + } + } + + viewModel.showGreenToastEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val message = pair.first + val icon = pair.second + (requireActivity() as MainActivity).showGreenToast(message, icon) + } + } + + viewModel.showRedToastEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val message = pair.first + val icon = pair.second + (requireActivity() as MainActivity).showRedToast(message, icon) + } + } + sharedViewModel.listOfSelectedSipUrisEvent.observe(viewLifecycleOwner) { it.consume { list -> Log.i("$TAG Found [${list.size}] new participants to add to the group, let's do it") @@ -223,7 +255,8 @@ class ConversationInfoFragment : GenericFragment() { sharedViewModel.showContactEvent.value = Event(refKey) } else { Log.e("$TAG Can't go to contact page, friend ref key is null or empty!") - // TODO: show toast + val message = getString(R.string.toast_cant_find_contact_to_display) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) } } @@ -236,7 +269,8 @@ class ConversationInfoFragment : GenericFragment() { sharedViewModel.showNewContactEvent.value = Event(true) } else { Log.e("$TAG Can't add empty/null SIP URI to contacts!") - // TODO: show toast + val message = getString(R.string.toast_no_address_to_add_to_contact) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) } } @@ -300,16 +334,24 @@ class ConversationInfoFragment : GenericFragment() { sharedViewModel.showContactEvent.value = Event(friendRefKey) } else { Log.e("$TAG Can't go to contact page, friend ref key is null or empty!") - // TODO: show toast + val message = getString(R.string.toast_cant_find_contact_to_display) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) } popupWindow.dismiss() } popupView.setAddToContactsClickListener { - Log.i("$TAG Trying to add participant [${participantModel.sipUri}] to contacts") - sharedViewModel.sipAddressToAddToNewContact = participantModel.sipUri - sharedViewModel.navigateToContactsEvent.value = Event(true) - sharedViewModel.showNewContactEvent.value = Event(true) + val sipUri = participantModel.sipUri + if (sipUri.isNotEmpty()) { + Log.i("$TAG Trying to add participant [${participantModel.sipUri}] to contacts") + sharedViewModel.sipAddressToAddToNewContact = sipUri + sharedViewModel.navigateToContactsEvent.value = Event(true) + sharedViewModel.showNewContactEvent.value = Event(true) + } else { + Log.e("$TAG Can't add empty/null SIP URI to contacts!") + val message = getString(R.string.toast_no_address_to_add_to_contact) + (requireActivity() as MainActivity).showRedToast(message, R.drawable.x) + } popupWindow.dismiss() } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt index acb2e41eb..8a1cbe172 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationInfoViewModel.kt @@ -25,6 +25,7 @@ import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R import org.linphone.core.Address import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub @@ -34,6 +35,7 @@ import org.linphone.core.Friend import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ParticipantModel import org.linphone.ui.main.contacts.model.ContactAvatarModel +import org.linphone.utils.AppUtils import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils @@ -74,6 +76,10 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val infoChangedEvent: MutableLiveData> by lazy { + MutableLiveData>() + } + val showParticipantAdminPopupMenuEvent: MutableLiveData>> by lazy { MutableLiveData>>() } @@ -82,21 +88,39 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { MutableLiveData>>() } + val showGreenToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + + val showRedToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + private lateinit var chatRoom: ChatRoom private val chatRoomListener = object : ChatRoomListenerStub() { @WorkerThread override fun onParticipantAdded(chatRoom: ChatRoom, eventLog: EventLog) { Log.i("$TAG A participant has been added to the group [${chatRoom.subject}]") - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_participant_added_to_conversation + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + computeParticipantsList() + infoChangedEvent.postValue(Event(true)) } @WorkerThread override fun onParticipantRemoved(chatRoom: ChatRoom, eventLog: EventLog) { Log.i("$TAG A participant has been removed from the group [${chatRoom.subject}]") - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_participant_removed_from_conversation + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + computeParticipantsList() + infoChangedEvent.postValue(Event(true)) } @WorkerThread @@ -104,7 +128,17 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { Log.i( "$TAG A participant has been given/removed administration rights for group [${chatRoom.subject}]" ) - // TODO: show toast + val message = if (eventLog.type == EventLog.Type.ConferenceParticipantSetAdmin) { + AppUtils.getString( + R.string.toast_participant_has_been_granted_admin_rights + ) + } else { + AppUtils.getString( + R.string.toast_participant_no_longer_has_admin_rights + ) + } + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.user_circle))) + // TODO FIXME: list doesn't have the changes... computeParticipantsList() } @@ -114,14 +148,36 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { Log.i( "$TAG Chat room [${LinphoneUtils.getChatRoomId(chatRoom)}] has a new subject [${chatRoom.subject}]" ) - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_conversation_subject_changed + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check))) + subject.postValue(chatRoom.subject) + infoChangedEvent.postValue(Event(true)) } @WorkerThread override fun onEphemeralEvent(chatRoom: ChatRoom, eventLog: EventLog) { Log.i("$TAG Ephemeral event [${eventLog.type}]") - // TODO: show toast + val message = when (eventLog.type) { + EventLog.Type.ConferenceEphemeralMessageEnabled -> { + AppUtils.getString( + R.string.toast_conversation_ephemeral_messages_enabled + ) + } + EventLog.Type.ConferenceEphemeralMessageDisabled -> { + AppUtils.getString( + R.string.toast_conversation_ephemeral_messages_disabled + ) + } + else -> { + AppUtils.getString( + R.string.toast_conversation_ephemeral_messages_lifetime_changed + ) + } + } + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.clock_countdown))) } } @@ -360,7 +416,10 @@ class ConversationInfoViewModel @UiThread constructor() : ViewModel() { val ok = chatRoom.addParticipants(participantsToAdd) if (!ok) { Log.w("$TAG Failed to add some/all participants to the group!") - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_failed_to_add_participant_to_group_conversation + ) + showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) } } } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt index 6977c11a6..2d929d147 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationViewModel.kt @@ -302,6 +302,14 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { } } + @UiThread + fun refresh() { + coreContext.postOnCoreThread { + Log.i("$TAG Refreshing conversation info (subject, participants, etc...)") + computeConversationInfo() + } + } + @UiThread fun applyFilter(filter: String) { coreContext.postOnCoreThread { @@ -340,6 +348,14 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { Log.w("$TAG Chat room with subject [${chatRoom.subject}] is read only!") } + computeConversationInfo() + + computeEvents() + chatRoom.markAsRead() + } + + @WorkerThread + private fun computeConversationInfo() { val group = LinphoneUtils.isChatRoomAGroup(chatRoom) isGroup.postValue(group) @@ -369,9 +385,6 @@ class ConversationViewModel @UiThread constructor() : ViewModel() { coreContext.contactsManager.getContactAvatarModelForAddress(address) } avatarModel.postValue(avatar) - - computeEvents() - chatRoom.markAsRead() } @WorkerThread diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt index ed0929962..85619572b 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/ConversationsListViewModel.kt @@ -23,6 +23,7 @@ import androidx.annotation.UiThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import org.linphone.LinphoneApplication.Companion.coreContext +import org.linphone.R import org.linphone.contacts.ContactsManager import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom @@ -33,6 +34,8 @@ import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ConversationModel import org.linphone.ui.main.model.isInSecureMode import org.linphone.ui.main.viewmodel.AbstractTopBarViewModel +import org.linphone.utils.AppUtils +import org.linphone.utils.Event import org.linphone.utils.LinphoneUtils class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewModel() { @@ -44,6 +47,10 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod val fetchInProgress = MutableLiveData() + val showGreenToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + private val coreListener = object : CoreListenerStub() { @WorkerThread override fun onChatRoomStateChanged( @@ -56,9 +63,16 @@ class ConversationsListViewModel @UiThread constructor() : AbstractTopBarViewMod ) when (state) { - ChatRoom.State.Created, ChatRoom.State.Instantiated, ChatRoom.State.Deleted -> { + ChatRoom.State.Created, ChatRoom.State.Instantiated -> { computeChatRoomsList(currentFilter) - // TODO: show toast + } + ChatRoom.State.Deleted -> { + computeChatRoomsList(currentFilter) + + val message = AppUtils.getString(R.string.toast_conversation_deleted) + showGreenToastEvent.postValue( + Event(Pair(message, R.drawable.chat_teardrop_text)) + ) } else -> {} } diff --git a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt index ae058bab2..bf39eec09 100644 --- a/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/chat/viewmodel/SendMessageInConversationViewModel.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.linphone.LinphoneApplication.Companion.coreContext import org.linphone.LinphoneApplication.Companion.corePreferences +import org.linphone.R import org.linphone.core.ChatMessage import org.linphone.core.ChatRoom import org.linphone.core.ChatRoomListenerStub @@ -49,6 +50,7 @@ import org.linphone.core.tools.Log import org.linphone.ui.main.chat.model.ChatMessageModel import org.linphone.ui.main.chat.model.FileModel import org.linphone.ui.main.chat.model.ParticipantModel +import org.linphone.utils.AppUtils import org.linphone.utils.AudioRouteUtils import org.linphone.utils.Event import org.linphone.utils.FileUtils @@ -112,6 +114,10 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val showRedToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + lateinit var chatRoom: ChatRoom private var chatMessageToReplyTo: ChatMessage? = null @@ -483,7 +489,10 @@ class SendMessageInConversationViewModel @UiThread constructor() : ViewModel() { "$TAG Max duration for voice recording exceeded (${maxVoiceRecordDuration}ms), stopping." ) stopVoiceRecorder() - // TOOD: show toast + val message = AppUtils.getString( + R.string.toast_voice_recording_max_duration_reached + ) + showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) } } }.launchIn(viewModelScope) diff --git a/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt b/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt index f3c24e743..243fd6a6c 100644 --- a/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt +++ b/app/src/main/java/org/linphone/ui/main/viewer/fragment/FileViewerFragment.kt @@ -12,6 +12,7 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import org.linphone.core.tools.Log import org.linphone.databinding.FileViewerFragmentBinding +import org.linphone.ui.main.MainActivity import org.linphone.ui.main.fragment.GenericFragment import org.linphone.ui.main.viewer.adapter.PdfPagesListAdapter import org.linphone.ui.main.viewer.viewmodel.FileViewModel @@ -96,6 +97,22 @@ class FileViewerFragment : GenericFragment() { } } } + + viewModel.showGreenToastEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val message = pair.first + val icon = pair.second + (requireActivity() as MainActivity).showGreenToast(message, icon) + } + } + + viewModel.showRedToastEvent.observe(viewLifecycleOwner) { + it.consume { pair -> + val message = pair.first + val icon = pair.second + (requireActivity() as MainActivity).showRedToast(message, icon) + } + } } override fun onPause() { diff --git a/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt index b509e10a5..28822ad67 100644 --- a/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewer/viewmodel/FileViewModel.kt @@ -51,6 +51,14 @@ class FileViewModel @UiThread constructor() : ViewModel() { MutableLiveData>() } + val showGreenToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + + val showRedToastEvent: MutableLiveData>> by lazy { + MutableLiveData>>() + } + // Below are required for PDF viewer private lateinit var pdfRenderer: PdfRenderer @@ -157,12 +165,19 @@ class FileViewModel @UiThread constructor() : ViewModel() { viewModelScope.launch { withContext(Dispatchers.IO) { Log.i("$TAG Export file [$filePath] to Android's MediaStore") - if (addContentToMediaStore(filePath)) { + val mediaStorePath = addContentToMediaStore(filePath) + if (mediaStorePath.isNotEmpty()) { Log.i("$TAG File [$filePath] has been successfully exported to MediaStore") - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_file_successfully_exported_to_media_store + ) + showGreenToastEvent.postValue(Event(Pair(message, R.drawable.check))) } else { Log.e("$TAG Failed to export file [$filePath] to MediaStore!") - // TODO: show toast + val message = AppUtils.getString( + R.string.toast_export_file_to_media_store_error + ) + showRedToastEvent.postValue(Event(Pair(message, R.drawable.x))) } } } @@ -174,10 +189,10 @@ class FileViewModel @UiThread constructor() : ViewModel() { @UiThread private suspend fun addContentToMediaStore( path: String - ): Boolean { + ): String { if (path.isEmpty()) { Log.e("$TAG No file path to export to MediaStore!") - return false + return "" } val isImage = FileUtils.isExtensionImage(path) @@ -254,10 +269,10 @@ class FileViewModel @UiThread constructor() : ViewModel() { if (mediaStoreFilePath.isNotEmpty()) { Log.i("$TAG Exported file path to MediaStore is: $mediaStoreFilePath") - return true + return mediaStoreFilePath } - return false + return "" } @UiThread diff --git a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt index bc87ac43e..7d83120b2 100644 --- a/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt +++ b/app/src/main/java/org/linphone/ui/main/viewmodel/SharedMainViewModel.kt @@ -110,6 +110,10 @@ class SharedMainViewModel @UiThread constructor() : ViewModel() { // When using keyboard to share gif or other, see RichContentReceiver & RichEditText classes val richContentUri = MutableLiveData>() + val forceRefreshConversationInfo: MutableLiveData> by lazy { + MutableLiveData>() + } + val forceRefreshConversationEvent: MutableLiveData> by lazy { MutableLiveData>() } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3e9132ad1..24f092e41 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -133,6 +133,24 @@ Cellular signal is no longer low This call can be trusted Connection error! + File has been exported to native gallery + Error trying to export file to native gallery + Someone joined the conversation + Someone left the conversation + Someone is now admin + Someone is no longer admin + Conversation subject has changed + Ephemeral messages have been enabled + Ephemeral messages have been disabled + Ephemeral messages lifetime changed + Contact was not found + No address to add to contact + Conversation was not found + Max duration reached + Failed to add participant(s) to conversation + Conversation was successfully deleted + History has been successfully deleted + You have left the group Login Scan QR code