Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: extract send message to viemodel #2824

Merged
merged 7 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ import com.wire.android.ui.home.conversations.banner.ConversationBanner
import com.wire.android.ui.home.conversations.banner.ConversationBannerViewModel
import com.wire.android.ui.home.conversations.call.ConversationCallViewModel
import com.wire.android.ui.home.conversations.call.ConversationCallViewState
import com.wire.android.ui.home.conversations.composer.MessageComposerViewModel
import com.wire.android.ui.home.conversations.delete.DeleteMessageDialog
import com.wire.android.ui.home.conversations.details.GroupConversationDetailsNavBackArgs
import com.wire.android.ui.home.conversations.edit.EditMessageMenuItems
Expand All @@ -130,6 +131,7 @@ import com.wire.android.ui.home.conversations.model.ExpirationStatus
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMapper.toSelfDeletionDuration
import com.wire.android.ui.home.conversations.selfdeletion.SelfDeletionMenuItems
import com.wire.android.ui.home.conversations.sendmessage.SendMessageViewModel
import com.wire.android.ui.home.gallery.MediaGalleryActionType
import com.wire.android.ui.home.gallery.MediaGalleryNavBackArgs
import com.wire.android.ui.home.messagecomposer.MessageComposer
Expand Down Expand Up @@ -188,6 +190,7 @@ fun ConversationScreen(
conversationCallViewModel: ConversationCallViewModel = hiltViewModel(),
conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel(),
messageComposerViewModel: MessageComposerViewModel = hiltViewModel(),
sendMessageViewModel: SendMessageViewModel = hiltViewModel(),
conversationMigrationViewModel: ConversationMigrationViewModel = hiltViewModel(),
messageDraftViewModel: MessageDraftViewModel = hiltViewModel(),
groupDetailsScreenResultRecipient: ResultRecipient<GroupConversationDetailsScreenDestination, GroupConversationDetailsNavBackArgs>,
Expand Down Expand Up @@ -335,7 +338,7 @@ fun ConversationScreen(
NavigationCommand(MessageDetailsScreenDestination(conversationInfoViewModel.conversationId, messageId, isSelfMessage))
)
},
onSendMessage = messageComposerViewModel::trySendMessage,
onSendMessage = sendMessageViewModel::trySendMessage,
onDeleteMessage = conversationMessagesViewModel::showDeleteMessageDialog,
onAssetItemClicked = conversationMessagesViewModel::downloadOrFetchAssetAndShowDialog,
onImageFullScreenMode = { message, isSelfMessage ->
Expand Down Expand Up @@ -389,14 +392,14 @@ fun ConversationScreen(
}
},
onBackButtonClick = { conversationScreenOnBackButtonClick(messageComposerViewModel, focusManager, navigator) },
composerMessages = messageComposerViewModel.infoMessage,
composerMessages = sendMessageViewModel.infoMessage,
conversationMessages = conversationMessagesViewModel.infoMessage,
conversationMessagesViewModel = conversationMessagesViewModel,
onSelfDeletingMessageRead = messageComposerViewModel::startSelfDeletion,
onNewSelfDeletingMessagesStatus = messageComposerViewModel::updateSelfDeletingMessages,
tempWritableImageUri = messageComposerViewModel.tempWritableImageUri,
tempWritableVideoUri = messageComposerViewModel.tempWritableVideoUri,
onFailedMessageRetryClicked = messageComposerViewModel::retrySendingMessage,
onFailedMessageRetryClicked = sendMessageViewModel::retrySendingMessage,
requestMentions = messageComposerViewModel::searchMembersToMention,
onClearMentionSearchResult = messageComposerViewModel::clearMentionSearchResult,
onPermissionPermanentlyDenied = {
Expand Down Expand Up @@ -470,8 +473,8 @@ fun ConversationScreen(
}
)
AssetTooLargeDialog(
dialogState = messageComposerViewModel.assetTooLargeDialogState,
hideDialog = messageComposerViewModel::hideAssetTooLargeError
dialogState = sendMessageViewModel.assetTooLargeDialogState,
hideDialog = sendMessageViewModel::hideAssetTooLargeError
)
VisitLinkDialog(
dialogState = messageComposerViewModel.visitLinkDialogState,
Expand All @@ -489,15 +492,15 @@ fun ConversationScreen(
)

SureAboutMessagingInDegradedConversationDialog(
dialogState = messageComposerViewModel.sureAboutMessagingDialogState,
sendAnyway = messageComposerViewModel::acceptSureAboutSendingMessage,
hideDialog = messageComposerViewModel::dismissSureAboutSendingMessage
dialogState = sendMessageViewModel.sureAboutMessagingDialogState,
sendAnyway = sendMessageViewModel::acceptSureAboutSendingMessage,
hideDialog = sendMessageViewModel::dismissSureAboutSendingMessage
)

(messageComposerViewModel.sureAboutMessagingDialogState as? SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold)?.let {
(sendMessageViewModel.sureAboutMessagingDialogState as? SureAboutMessagingDialogState.Visible.ConversationUnderLegalHold)?.let {
LegalHoldSubjectMessageDialog(
dialogDismissed = messageComposerViewModel::dismissSureAboutSendingMessage,
sendAnywayClicked = messageComposerViewModel::acceptSureAboutSendingMessage,
dialogDismissed = sendMessageViewModel::dismissSureAboutSendingMessage,
sendAnywayClicked = sendMessageViewModel::acceptSureAboutSendingMessage,
)
}

Expand Down Expand Up @@ -852,7 +855,7 @@ private fun ConversationScreenContent(
}

@Composable
fun SnackBarMessage(
private fun SnackBarMessage(
composerMessages: SharedFlow<SnackBarMessage>,
conversationMessages: SharedFlow<SnackBarMessage>
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/

package com.wire.android.ui.home.conversations.composer

import android.net.Uri
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.wire.android.mapper.ContactMapper
import com.wire.android.navigation.SavedStateViewModel
import com.wire.android.ui.home.conversations.ConversationNavArgs
import com.wire.android.ui.home.conversations.InvalidLinkDialogState
import com.wire.android.ui.home.conversations.MessageComposerViewState
import com.wire.android.ui.home.conversations.VisitLinkDialogState
import com.wire.android.ui.home.conversations.model.UIMessage
import com.wire.android.ui.navArgs
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.kalium.logic.configuration.FileSharingStatus
import com.wire.kalium.logic.data.conversation.Conversation.TypingIndicatorMode
import com.wire.kalium.logic.data.id.QualifiedID
import com.wire.kalium.logic.data.message.SelfDeletionTimer
import com.wire.kalium.logic.data.message.draft.MessageDraft
import com.wire.kalium.logic.data.user.OtherUser
import com.wire.kalium.logic.feature.conversation.InteractionAvailability
import com.wire.kalium.logic.feature.conversation.IsInteractionAvailableResult
import com.wire.kalium.logic.feature.conversation.MembersToMentionUseCase
import com.wire.kalium.logic.feature.conversation.ObserveConversationInteractionAvailabilityUseCase
import com.wire.kalium.logic.feature.conversation.SendTypingEventUseCase
import com.wire.kalium.logic.feature.conversation.UpdateConversationReadDateUseCase
import com.wire.kalium.logic.feature.message.draft.SaveMessageDraftUseCase
import com.wire.kalium.logic.feature.message.ephemeral.EnqueueMessageSelfDeletionUseCase
import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTimerSettingsForConversationUseCase
import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase
import com.wire.kalium.logic.feature.user.IsFileSharingEnabledUseCase
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import kotlinx.datetime.Instant
import javax.inject.Inject

@Suppress("LongParameterList", "TooManyFunctions")
@HiltViewModel
class MessageComposerViewModel @Inject constructor(
override val savedStateHandle: SavedStateHandle,
private val dispatchers: DispatcherProvider,
private val isFileSharingEnabled: IsFileSharingEnabledUseCase,
private val observeConversationInteractionAvailability: ObserveConversationInteractionAvailabilityUseCase,
private val updateConversationReadDate: UpdateConversationReadDateUseCase,
private val contactMapper: ContactMapper,
private val membersToMention: MembersToMentionUseCase,
private val enqueueMessageSelfDeletion: EnqueueMessageSelfDeletionUseCase,
private val observeSelfDeletingMessages: ObserveSelfDeletionTimerSettingsForConversationUseCase,
private val persistNewSelfDeletingStatus: PersistNewSelfDeletionTimerUseCase,
private val sendTypingEvent: SendTypingEventUseCase,
private val saveMessageDraft: SaveMessageDraftUseCase
) : SavedStateViewModel(savedStateHandle) {

var messageComposerViewState = mutableStateOf(MessageComposerViewState())
private set

var tempWritableVideoUri: Uri? = null

Check warning on line 78 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L78

Added line #L78 was not covered by tests
private set

var tempWritableImageUri: Uri? = null

Check warning on line 81 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L81

Added line #L81 was not covered by tests
private set

private val conversationNavArgs: ConversationNavArgs = savedStateHandle.navArgs()
val conversationId: QualifiedID = conversationNavArgs.conversationId

var visitLinkDialogState: VisitLinkDialogState by mutableStateOf(
VisitLinkDialogState.Hidden
)

var invalidLinkDialogState: InvalidLinkDialogState by mutableStateOf(
InvalidLinkDialogState.Hidden
)

init {
observeIsTypingAvailable()
observeSelfDeletingMessagesStatus()
setFileSharingStatus()
}

private fun observeIsTypingAvailable() = viewModelScope.launch {
observeConversationInteractionAvailability(conversationId).collect { result ->
messageComposerViewState.value = messageComposerViewState.value.copy(
interactionAvailability = when (result) {
is IsInteractionAvailableResult.Failure -> InteractionAvailability.DISABLED
is IsInteractionAvailableResult.Success -> result.interactionAvailability
}
)
}
}

private fun observeSelfDeletingMessagesStatus() = viewModelScope.launch {
observeSelfDeletingMessages(
conversationId,
considerSelfUserSettings = true
).collect { selfDeletingStatus ->
messageComposerViewState.value =
messageComposerViewState.value.copy(selfDeletionTimer = selfDeletingStatus)
}
}

fun searchMembersToMention(searchQuery: String) {
viewModelScope.launch(dispatchers.io()) {
val members = membersToMention(conversationId, searchQuery).map {
contactMapper.fromOtherUser(it.user as OtherUser)

Check warning on line 125 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L123-L125

Added lines #L123 - L125 were not covered by tests
}

messageComposerViewState.value =
messageComposerViewState.value.copy(mentionSearchResult = members)

Check warning on line 129 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L128-L129

Added lines #L128 - L129 were not covered by tests
}
}

fun clearMentionSearchResult() {
messageComposerViewState.value =
messageComposerViewState.value.copy(mentionSearchResult = emptyList())

Check warning on line 135 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L134-L135

Added lines #L134 - L135 were not covered by tests
}

private fun setFileSharingStatus() {
// TODO: handle restriction when sending assets
viewModelScope.launch {
messageComposerViewState.value = when (isFileSharingEnabled().state) {
FileSharingStatus.Value.Disabled,
is FileSharingStatus.Value.EnabledSome ->
messageComposerViewState.value.copy(isFileSharingEnabled = false)

Check warning on line 144 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L144

Added line #L144 was not covered by tests

FileSharingStatus.Value.EnabledAll ->
messageComposerViewState.value.copy(isFileSharingEnabled = true)
}
}
}

fun updateConversationReadDate(utcISO: String) {
viewModelScope.launch(dispatchers.io()) {
updateConversationReadDate(conversationId, Instant.parse(utcISO))

Check warning on line 154 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L153-L154

Added lines #L153 - L154 were not covered by tests
}
}

fun startSelfDeletion(uiMessage: UIMessage) {
enqueueMessageSelfDeletion(conversationId, uiMessage.header.messageId)

Check warning on line 159 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L159

Added line #L159 was not covered by tests
}

fun updateSelfDeletingMessages(newSelfDeletionTimer: SelfDeletionTimer) =
viewModelScope.launch {
messageComposerViewState.value =
messageComposerViewState.value.copy(selfDeletionTimer = newSelfDeletionTimer)
persistNewSelfDeletingStatus(conversationId, newSelfDeletionTimer)
}

fun hideVisitLinkDialog() {
visitLinkDialogState = VisitLinkDialogState.Hidden

Check warning on line 170 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L170

Added line #L170 was not covered by tests
}

fun hideInvalidLinkError() {
invalidLinkDialogState = InvalidLinkDialogState.Hidden

Check warning on line 174 in app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/wire/android/ui/home/conversations/composer/MessageComposerViewModel.kt#L174

Added line #L174 was not covered by tests
}

fun sendTypingEvent(typingIndicatorMode: TypingIndicatorMode) {
viewModelScope.launch {
sendTypingEvent(conversationId, typingIndicatorMode)
}
}

fun saveDraft(messageDraft: MessageDraft) {
viewModelScope.launch {
saveMessageDraft(conversationId, messageDraft)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootNavGraph
import com.wire.android.R
import com.wire.android.media.audiomessage.AudioState
import com.wire.android.model.SnackBarMessage
import com.wire.android.navigation.NavigationCommand
import com.wire.android.navigation.Navigator
import com.wire.android.navigation.style.PopUpNavigationAnimation
Expand All @@ -54,22 +56,22 @@ import com.wire.android.ui.common.calculateCurrentTab
import com.wire.android.ui.common.colorsScheme
import com.wire.android.ui.common.dialogs.PermissionPermanentlyDeniedDialog
import com.wire.android.ui.common.scaffold.WireScaffold
import com.wire.android.ui.common.snackbar.LocalSnackbarHostState
import com.wire.android.ui.common.topBarElevation
import com.wire.android.ui.common.topappbar.NavigationIconType
import com.wire.android.ui.common.topappbar.WireCenterAlignedTopAppBar
import com.wire.android.ui.common.visbility.rememberVisibilityState
import com.wire.android.ui.destinations.MediaGalleryScreenDestination
import com.wire.android.ui.home.conversations.DownloadedAssetDialog
import com.wire.android.ui.home.conversations.MessageComposerViewModel
import com.wire.android.ui.home.conversations.PermissionPermanentlyDeniedDialogState
import com.wire.android.ui.home.conversations.SnackBarMessage
import com.wire.android.ui.home.conversations.messages.ConversationMessagesViewModel
import com.wire.android.ui.theme.WireTheme
import com.wire.android.ui.theme.wireDimensions
import com.wire.android.util.ui.PreviewMultipleThemes
import com.wire.kalium.logic.data.id.ConversationId
import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch

@RootNavGraph
Expand All @@ -81,8 +83,7 @@ import kotlinx.coroutines.launch
fun ConversationMediaScreen(
navigator: Navigator,
conversationAssetMessagesViewModel: ConversationAssetMessagesViewModel = hiltViewModel(),
conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel(),
messageComposerViewModel: MessageComposerViewModel = hiltViewModel()
conversationMessagesViewModel: ConversationMessagesViewModel = hiltViewModel()
) {
val permissionPermanentlyDeniedDialogState =
rememberVisibilityState<PermissionPermanentlyDeniedDialogState>()
Expand Down Expand Up @@ -129,10 +130,7 @@ fun ConversationMediaScreen(
hideDialog = permissionPermanentlyDeniedDialogState::dismiss
)

SnackBarMessage(
messageComposerViewModel.infoMessage,
conversationMessagesViewModel.infoMessage
)
SnackBarMessage(conversationMessagesViewModel.infoMessage)
}

@OptIn(ExperimentalFoundationApi::class)
Expand Down Expand Up @@ -207,6 +205,20 @@ private fun Content(
}
}

@Composable
private fun SnackBarMessage(infoMessages: SharedFlow<SnackBarMessage>) {
val context = LocalContext.current
val snackbarHostState = LocalSnackbarHostState.current

LaunchedEffect(Unit) {
infoMessages.collect {
snackbarHostState.showSnackbar(
message = it.uiText.asString(context.resources)
)
}
}
}

enum class ConversationMediaScreenTabItem(@StringRes override val titleResId: Int) : TabItem {
PICTURES(R.string.label_conversation_pictures),
FILES(R.string.label_conversation_files);
Expand Down
Loading
Loading