diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d998e886f0e..897d7058766 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -141,6 +141,12 @@ + + + + + + @@ -165,6 +171,12 @@ + + + + + + diff --git a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaAuthenticatedViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaAuthenticatedViewModel.kt index bbe021f761a..f56d19c5dd1 100644 --- a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaAuthenticatedViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaAuthenticatedViewModel.kt @@ -45,6 +45,7 @@ import com.wire.kalium.logic.feature.asset.GetAssetSizeLimitUseCase import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageResult import com.wire.kalium.logic.feature.asset.ScheduleNewAssetMessageUseCase import com.wire.kalium.logic.feature.conversation.ObserveConversationListDetailsUseCase +import com.wire.kalium.logic.feature.message.SendTextMessageUseCase import com.wire.kalium.logic.feature.selfDeletingMessages.ObserveSelfDeletionTimerSettingsForConversationUseCase import com.wire.kalium.logic.feature.selfDeletingMessages.PersistNewSelfDeletionTimerUseCase import com.wire.kalium.logic.feature.selfDeletingMessages.SelfDeletionTimer @@ -78,6 +79,7 @@ class ImportMediaAuthenticatedViewModel @Inject constructor( private val observeConversationListDetails: ObserveConversationListDetailsUseCase, private val fileManager: FileManager, private val sendAssetMessage: ScheduleNewAssetMessageUseCase, + private val sendTextMessage: SendTextMessageUseCase, private val kaliumFileSystem: KaliumFileSystem, private val getAssetSizeLimit: GetAssetSizeLimitUseCase, private val persistNewSelfDeletionTimerUseCase: PersistNewSelfDeletionTimerUseCase, @@ -260,8 +262,7 @@ class ImportMediaAuthenticatedViewModel @Inject constructor( appLogger.e("Received data from sharing intent ${incomingIntent.streamCount}") importMediaState = importMediaState.copy(isImporting = true) if (incomingIntent.streamCount == 0) { - // if stream count is 0 the type will be text, we check the type to double check if it is text - // todo : handle the text , we can get the text from incomingIntent.text + handleSharedText(incomingIntent.text.toString()) } else { if (incomingIntent.isSingleShare) { // ACTION_SEND @@ -274,6 +275,10 @@ class ImportMediaAuthenticatedViewModel @Inject constructor( importMediaState = importMediaState.copy(isImporting = false) } + private fun handleSharedText(text: String) { + importMediaState = importMediaState.copy(importedText = text) + } + private suspend fun handleSingleIntent( incomingIntent: ShareCompat.IntentReader, activity: AppCompatActivity @@ -303,45 +308,58 @@ class ImportMediaAuthenticatedViewModel @Inject constructor( importMediaState = importMediaState.copy(importedAssets = importedMediaAssets) } - fun checkRestrictionsAndSendImportedMedia(onSent: (ConversationId) -> Unit) = viewModelScope.launch(dispatchers.default()) { - val conversation = importMediaState.selectedConversationItem.firstOrNull() ?: return@launch - val assetsToSend = importMediaState.importedAssets + fun checkRestrictionsAndSendImportedMedia(onSent: (ConversationId) -> Unit) = + viewModelScope.launch(dispatchers.default()) { + val conversation = + importMediaState.selectedConversationItem.firstOrNull() ?: return@launch + val assetsToSend = importMediaState.importedAssets + val textToSend = importMediaState.importedText - if (assetsToSend.size > MAX_LIMIT_MEDIA_IMPORT) { - onSnackbarMessage(ImportMediaSnackbarMessages.MaxAmountOfAssetsReached) - } else { - val jobs: MutableCollection = mutableListOf() - assetsToSend.forEach { importedAsset -> - val isImage = importedAsset is ImportedMediaAsset.Image - val job = viewModelScope.launch { - sendAssetMessage( + if (assetsToSend.size > MAX_LIMIT_MEDIA_IMPORT) { + onSnackbarMessage(ImportMediaSnackbarMessages.MaxAmountOfAssetsReached) + } else { + val jobs: MutableCollection = mutableListOf() + + textToSend?.let { + sendTextMessage( conversationId = conversation.conversationId, - assetDataPath = importedAsset.dataPath, - assetName = importedAsset.name, - assetDataSize = importedAsset.size, - assetMimeType = importedAsset.mimeType, - assetWidth = if (isImage) (importedAsset as ImportedMediaAsset.Image).width else 0, - assetHeight = if (isImage) (importedAsset as ImportedMediaAsset.Image).height else 0, - audioLengthInMs = getAudioLengthInMs( - dataPath = importedAsset.dataPath, - mimeType = importedAsset.mimeType - ) - ).also { - if (it is ScheduleNewAssetMessageResult.Failure) { - appLogger.e("Failed to import asset message to conversationId=${conversation.conversationId.toLogString()} ") - } else { - appLogger.d("Success importing asset message to conversationId=${conversation.conversationId.toLogString()}") + text = it + ) + } ?: assetsToSend.forEach { importedAsset -> + val isImage = importedAsset is ImportedMediaAsset.Image + val job = viewModelScope.launch { + sendAssetMessage( + conversationId = conversation.conversationId, + assetDataPath = importedAsset.dataPath, + assetName = importedAsset.name, + assetDataSize = importedAsset.size, + assetMimeType = importedAsset.mimeType, + assetWidth = if (isImage) (importedAsset as ImportedMediaAsset.Image).width else 0, + assetHeight = if (isImage) (importedAsset as ImportedMediaAsset.Image).height else 0, + audioLengthInMs = getAudioLengthInMs( + dataPath = importedAsset.dataPath, + mimeType = importedAsset.mimeType + ) + ).also { + val logConversationId = conversation.conversationId.toLogString() + if (it is ScheduleNewAssetMessageResult.Failure) { + appLogger.e("Failed to import asset message to " + + "conversationId=$logConversationId") + } else { + appLogger.d("Success importing asset message to " + + "conversationId=$logConversationId") + } } } + jobs.add(job) + } + + jobs.joinAll() + withContext(dispatchers.main()) { + onSent(conversation.conversationId) } - jobs.add(job) - } - jobs.joinAll() - withContext(dispatchers.main()) { - onSent(conversation.conversationId) } } - } fun onNewConversationPicked(conversationId: ConversationId) = viewModelScope.launch { importMediaState = importMediaState.copy( @@ -455,6 +473,7 @@ class ImportMediaAuthenticatedViewModel @Inject constructor( data class ImportMediaAuthenticatedState( val avatarAsset: ImageAsset.UserAvatarAsset? = null, val importedAssets: List = emptyList(), + val importedText: String? = null, val isImporting: Boolean = false, val shareableConversationListState: ShareableConversationListState = ShareableConversationListState(), val selectedConversationItem: List = emptyList(), diff --git a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt index 07dd39d782b..43dcbe81036 100644 --- a/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/sharing/ImportMediaScreen.kt @@ -14,6 +14,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.pager.HorizontalPager +import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold @@ -34,8 +36,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import androidx.compose.foundation.pager.HorizontalPager -import androidx.compose.foundation.pager.rememberPagerState import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootNavGraph import com.wire.android.R @@ -69,8 +69,8 @@ import com.wire.android.util.CustomTabsHelper import com.wire.android.util.extension.getActivity import com.wire.android.util.ui.LinkText import com.wire.android.util.ui.LinkTextData -import com.wire.kalium.logic.util.isPositiveNotNull import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.logic.util.isPositiveNotNull import kotlinx.collections.immutable.persistentMapOf import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow @@ -84,7 +84,8 @@ fun ImportMediaScreen( ) { featureFlagNotificationViewModel.loadInitialSync() - when (val fileSharingRestrictedState = featureFlagNotificationViewModel.featureFlagState.fileSharingRestrictedState) { + when (val fileSharingRestrictedState = + featureFlagNotificationViewModel.featureFlagState.fileSharingRestrictedState) { FeatureFlagState.SharingRestrictedState.NO_USER -> { ImportMediaLoggedOutContent( fileSharingRestrictedState = fileSharingRestrictedState, @@ -109,7 +110,12 @@ fun ImportMediaScreen( onConversationClicked = importMediaViewModel::onConversationClicked, checkRestrictionsAndSendImportedMedia = { importMediaViewModel.checkRestrictionsAndSendImportedMedia { - navigator.navigate(NavigationCommand(ConversationScreenDestination(it), BackStackMode.CLEAR_TILL_START)) + navigator.navigate( + NavigationCommand( + ConversationScreenDestination(it), + BackStackMode.CLEAR_TILL_START + ) + ) } }, onNewSelfDeletionTimerPicked = importMediaViewModel::onNewSelfDeletionTimerPicked, @@ -119,7 +125,8 @@ fun ImportMediaScreen( val context = LocalContext.current LaunchedEffect(importMediaViewModel.importMediaState.importedAssets) { if (importMediaViewModel.importMediaState.importedAssets.isEmpty()) { - context.getActivity()?.let { importMediaViewModel.handleReceivedDataFromSharingIntent(it) } + context.getActivity() + ?.let { importMediaViewModel.handleReceivedDataFromSharingIntent(it) } } } } @@ -271,11 +278,12 @@ fun FileSharingRestrictedContent( .padding(internalPadding) .padding(horizontal = dimensions().spacing48x) ) { - val textRes = if (sharingRestrictedState == FeatureFlagState.SharingRestrictedState.NO_USER) { - R.string.file_sharing_restricted_description_no_users - } else { - R.string.file_sharing_restricted_description_by_team - } + val textRes = + if (sharingRestrictedState == FeatureFlagState.SharingRestrictedState.NO_USER) { + R.string.file_sharing_restricted_description_no_users + } else { + R.string.file_sharing_restricted_description_by_team + } Text( text = stringResource(textRes), textAlign = TextAlign.Center, @@ -318,9 +326,11 @@ private fun ImportMediaBottomBar( } else { stringResource(id = R.string.import_media_send_button_title) } + val buttonCount = + if (state.importedAssets.isNotEmpty() || state.importedText != null) state.selectedConversationItem.size else 0 SendContentButton( mainButtonText = mainButtonText, - count = if (state.importedAssets.isNotEmpty()) state.selectedConversationItem.size else 0, + count = buttonCount, onMainButtonClick = checkRestrictionsAndSendImportedMedia, selfDeletionTimer = selfDeletionTimer, onSelfDeletionTimerClicked = importMediaScreenState::showBottomSheetMenu, @@ -348,9 +358,13 @@ private fun ImportMediaContent( ) { val horizontalPadding = dimensions().spacing8x val screenWidth = LocalConfiguration.current.screenWidthDp.dp - val itemWidth = if (isMultipleImport) dimensions().importedMediaAssetSize + horizontalPadding.times(2) - else screenWidth - (horizontalPadding * 2) - val contentPadding = PaddingValues(start = horizontalPadding, end = (screenWidth - itemWidth + horizontalPadding)) + val itemWidth = + if (isMultipleImport) dimensions().importedMediaAssetSize + horizontalPadding.times(2) + else screenWidth - (horizontalPadding * 2) + val contentPadding = PaddingValues( + start = horizontalPadding, + end = (screenWidth - itemWidth + horizontalPadding) + ) val lazyListState = rememberLazyListState() if (state.isImporting) { Box( @@ -379,7 +393,11 @@ private fun ImportMediaContent( } } } - Divider(color = colorsScheme().outline, thickness = 1.dp, modifier = Modifier.padding(top = dimensions().spacing12x)) + Divider( + color = colorsScheme().outline, + thickness = 1.dp, + modifier = Modifier.padding(top = dimensions().spacing12x) + ) Box(Modifier.padding(dimensions().spacing6x)) { SearchTopBar( isSearchActive = searchBarState.isSearchActive, @@ -420,7 +438,10 @@ private fun ImportMediaContent( } @Composable -private fun SnackBarMessage(infoMessages: SharedFlow, snackbarHostState: SnackbarHostState) { +private fun SnackBarMessage( + infoMessages: SharedFlow, + snackbarHostState: SnackbarHostState +) { val context = LocalContext.current LaunchedEffect(Unit) { infoMessages.collect { message -> @@ -438,13 +459,23 @@ fun PreviewImportMediaScreenLoggedOut() { @Preview(showBackground = true) @Composable fun PreviewImportMediaScreenRestricted() { - ImportMediaRestrictedContent(FeatureFlagState.SharingRestrictedState.RESTRICTED_IN_TEAM, ImportMediaAuthenticatedState()) {} + ImportMediaRestrictedContent( + FeatureFlagState.SharingRestrictedState.RESTRICTED_IN_TEAM, + ImportMediaAuthenticatedState() + ) {} } @Preview(showBackground = true) @Composable fun PreviewImportMediaScreenRegular() { - ImportMediaRegularContent(ImportMediaAuthenticatedState(), {}, {}, {}, {}, MutableSharedFlow()) {} + ImportMediaRegularContent( + ImportMediaAuthenticatedState(), + {}, + {}, + {}, + {}, + MutableSharedFlow() + ) {} } @Preview(showBackground = true)