From f8cf35879fe16f0491bfad8b229f01eb5dd5d8fb Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Fri, 25 Nov 2022 19:31:04 +0300 Subject: [PATCH] android: Voice messages UI changes (#1422) * android: Voice messages UI changes * Alert for groups Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> --- .../java/chat/simplex/app/model/SimpleXAPI.kt | 2 +- .../chat/simplex/app/views/TerminalView.kt | 2 +- .../simplex/app/views/chat/ChatInfoView.kt | 2 +- .../simplex/app/views/chat/ComposeView.kt | 45 ++++++++++++++- .../app/views/chat/ComposeVoiceView.kt | 2 +- .../app/views/chat/ContactPreferences.kt | 12 ++-- .../simplex/app/views/chat/SendMsgView.kt | 57 ++++++++++++++++--- .../app/views/chat/item/FramedItemView.kt | 5 +- .../app/views/helpers/GestureDetector.kt | 4 +- .../app/src/main/res/values-de/strings.xml | 6 ++ .../app/src/main/res/values-ru/strings.xml | 6 ++ .../app/src/main/res/values/strings.xml | 6 ++ 12 files changed, 125 insertions(+), 24 deletions(-) diff --git a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt index e799c7459..34c25b9dc 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/model/SimpleXAPI.kt @@ -1052,7 +1052,7 @@ open class ChatController(var ctrl: ChatCtrl?, val ntfManager: NtfManager, val a val file = cItem.file if (cItem.content.msgContent is MsgContent.MCImage && file != null && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV && appPrefs.privacyAcceptImages.get()) { withApi { receiveFile(file.fileId) } - } else if (cItem.content.msgContent is MsgContent.MCVoice && file != null && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && appPrefs.privacyAcceptImages.get()) { + } else if (cItem.content.msgContent is MsgContent.MCVoice && file != null && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileSize > MAX_VOICE_SIZE_FOR_SENDING && appPrefs.privacyAcceptImages.get()) { withApi { receiveFile(file.fileId) } // TODO check inlineFileMode != IFMSent } if (!cItem.chatDir.sent && !cItem.isCall && !cItem.isMutedMemberEvent && (!isAppOnForeground(appContext) || chatModel.chatId.value != cInfo.id)) { diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt index 26bf53be2..c512df703 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/TerminalView.kt @@ -135,7 +135,7 @@ fun TerminalLayout( topBar = { CloseSheetBar(close) }, bottomBar = { Box(Modifier.padding(horizontal = 8.dp)) { - SendMsgView(composeState, false, sendCommand, ::onMessageChange, { _, _, _ -> }, textStyle) + SendMsgView(composeState, false, false, false, sendCommand, ::onMessageChange, { _, _, _ -> }, {}, {}, textStyle) } }, modifier = Modifier.navigationBarsWithImePadding() diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt index 045568b75..9500cd3be 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ChatInfoView.kt @@ -64,7 +64,7 @@ fun ChatInfoView( }, openPreferences = { ModalManager.shared.showModal(true) { - ContactPreferencesView(chatModel, chatModel.currentUser.value ?: return@showModal, contact) + ContactPreferencesView(chatModel, chatModel.currentUser.value ?: return@showModal, contact.contactId) } }, deleteContact = { deleteContactDialog(chat.chatInfo, chatModel, close) }, diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt index 7d207bac7..bf80d5d7f 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeView.kt @@ -455,6 +455,29 @@ fun ComposeView( composeState.value = composeState.value.copy(preview = ComposePreview.VoicePreview(filePath, durationMs, finished)) } + fun allowVoiceToContact() { + val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return + val featuresAllowed = contactUserPrefsToFeaturesAllowed(contact.mergedPreferences) + withApi { + val prefs = contactFeaturesAllowedToPrefs(featuresAllowed).copy(voice = ChatPreference(FeatureAllowed.YES)) + val toContact = chatModel.controller.apiSetContactPrefs(contact.contactId, prefs) + if (toContact != null) { + chatModel.updateContact(toContact) + } + } + } + fun showDisabledVoiceAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(R.string.voice_messages_prohibited), + text = generalGetString( + if (chat.chatInfo is ChatInfo.Direct) + R.string.ask_your_contact_to_enable_voice + else + R.string.only_group_owners_can_enable_voice + ) + ) + } + fun cancelLinkPreview() { val uri = composeState.value.linkPreview?.uri if (uri != null) { @@ -549,15 +572,35 @@ fun ComposeView( .clip(CircleShape) ) } + val allowedVoiceByPrefs = remember(chat.chatInfo) { + when (chat.chatInfo) { + is ChatInfo.Direct -> chat.chatInfo.contact.mergedPreferences.voice.enabled.forUser + is ChatInfo.Group -> chat.chatInfo.groupInfo.fullGroupPreferences.voice.enable == GroupFeatureEnabled.ON + else -> false + } + } + val needToAllowVoiceToContact = remember(chat.chatInfo) { + when (chat.chatInfo) { + is ChatInfo.Direct -> with(chat.chatInfo.contact.mergedPreferences.voice) { + ((userPreference as? ContactUserPref.User)?.preference?.allow == FeatureAllowed.NO || (userPreference as? ContactUserPref.Contact)?.preference?.allow == FeatureAllowed.NO) && + contactPreference.allow == FeatureAllowed.YES + } + else -> false + } + } SendMsgView( composeState, - allowVoiceRecord = true, + showVoiceRecordIcon = true, + allowedVoiceByPrefs = allowedVoiceByPrefs, + needToAllowVoiceToContact = needToAllowVoiceToContact, sendMessage = { sendMessage() resetLinkPreview() }, ::onMessageChange, ::onAudioAdded, + ::allowVoiceToContact, + ::showDisabledVoiceAlert, textStyle ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt index f9021a298..76f92c3de 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ComposeVoiceView.kt @@ -57,7 +57,7 @@ fun ComposeVoiceView( Modifier .requiredWidth(progressBarWidth.value.dp) .padding(top = 58.dp) - .height(2.dp) + .height(3.dp) .background(MaterialTheme.colors.primary) ) Row( diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt index 1f7a6e74d..03a9479f9 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/ContactPreferences.kt @@ -24,15 +24,17 @@ import chat.simplex.app.views.helpers.* fun ContactPreferencesView( m: ChatModel, user: User, - contact: Contact, + contactId: Long, ) { - var featuresAllowed by remember { mutableStateOf(contactUserPrefsToFeaturesAllowed(contact.mergedPreferences)) } - var currentFeaturesAllowed by remember { mutableStateOf(featuresAllowed) } + val contact = remember { derivedStateOf { (m.getContactChat(contactId)?.chatInfo as? ChatInfo.Direct)?.contact } } + val ct = contact.value ?: return + var featuresAllowed by remember(ct) { mutableStateOf(contactUserPrefsToFeaturesAllowed(ct.mergedPreferences)) } + var currentFeaturesAllowed by remember(ct) { mutableStateOf(featuresAllowed) } ContactPreferencesLayout( featuresAllowed, currentFeaturesAllowed, user, - contact, + ct, applyPrefs = { prefs -> featuresAllowed = prefs }, @@ -42,7 +44,7 @@ fun ContactPreferencesView( savePrefs = { withApi { val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) - val toContact = m.controller.apiSetContactPrefs(contact.contactId, prefs) + val toContact = m.controller.apiSetContactPrefs(ct.contactId, prefs) if (toContact != null) { m.updateContact(toContact) currentFeaturesAllowed = featuresAllowed diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt index 541889ed5..894152f4a 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/SendMsgView.kt @@ -17,7 +17,6 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.* import androidx.compose.material.icons.outlined.* -import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* @@ -25,7 +24,6 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* import androidx.compose.ui.res.stringResource -import androidx.compose.ui.semantics.Role import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.tooling.preview.Preview @@ -48,10 +46,14 @@ import java.io.* @Composable fun SendMsgView( composeState: MutableState, - allowVoiceRecord: Boolean, + showVoiceRecordIcon: Boolean, + allowedVoiceByPrefs: Boolean, + needToAllowVoiceToContact: Boolean, sendMessage: () -> Unit, onMessageChange: (String) -> Unit, onAudioAdded: (String, Int, Boolean) -> Unit, + allowVoiceToContact: () -> Unit, + showDisabledVoiceAlert: () -> Unit, textStyle: MutableState ) { Column(Modifier.padding(vertical = 8.dp)) { @@ -60,7 +62,7 @@ fun SendMsgView( val attachEnabled = !composeState.value.editing val filePath = rememberSaveable { mutableStateOf(null as String?) } var recordingTimeRange by rememberSaveable(saver = LongRange.saver) { mutableStateOf(0L..0L) } // since..to - val showVoiceButton = ((cs.message.isEmpty() || recordingTimeRange.first > 0L) && allowVoiceRecord && attachEnabled && cs.preview is ComposePreview.NoPreview) || filePath.value != null + val showVoiceButton = ((cs.message.isEmpty() || recordingTimeRange.first > 0L) && showVoiceRecordIcon && attachEnabled && cs.preview is ComposePreview.NoPreview) || filePath.value != null Box(if (recordingTimeRange.first == 0L) Modifier else @@ -112,6 +114,16 @@ fun SendMsgView( val startStopRecording: () -> Unit = { when { !permissionsState.allPermissionsGranted -> permissionsState.launchMultiplePermissionRequest() + needToAllowVoiceToContact -> { + AlertManager.shared.showAlertDialog( + title = generalGetString(R.string.allow_voice_messages_question), + text = generalGetString(R.string.you_need_to_allow_to_send_voice), + confirmText = generalGetString(R.string.allow_verb), + dismissText = generalGetString(R.string.cancel_verb), + onConfirm = allowVoiceToContact, + ) + } + !allowedVoiceByPrefs -> showDisabledVoiceAlert() recordingInProgress.value -> stopRecordingAndAddAudio() filePath.value == null -> { recordingTimeRange = System.currentTimeMillis()..0L @@ -149,10 +161,15 @@ fun SendMsgView( } } val interactionSource = interactionSourceWithTapDetection( + // It's just a key for triggering dropping a state in the compose function. Without it + // nothing will react on changed params like needToAllowVoiceToContact or allowedVoiceByPrefs + needToAllowVoiceToContact.toString() + allowedVoiceByPrefs.toString(), onPress = { if (filePath.value == null) startStopRecording() }, onClick = { + // Voice not allowed or not granted voice record permission for the app + if (!allowedVoiceByPrefs || !permissionsState.allPermissionsGranted) return@interactionSourceWithTapDetection if (!recordingInProgress.value && filePath.value != null) { sendMessage() cleanUp(false) @@ -173,9 +190,19 @@ fun SendMsgView( Modifier IconButton({}, Modifier.size(36.dp), enabled = !cs.inProgress, interactionSource = interactionSource) { Icon( - if (recordingTimeRange.last != 0L) Icons.Outlined.ArrowUpward else if (stopRecOnNextClick) Icons.Default.Stop else Icons.Default.Mic, + when { + recordingTimeRange.last != 0L -> Icons.Outlined.ArrowUpward + stopRecOnNextClick -> Icons.Filled.Stop + allowedVoiceByPrefs -> Icons.Filled.KeyboardVoice + else -> Icons.Outlined.KeyboardVoice + }, stringResource(R.string.icon_descr_record_voice_message), - tint = if (recordingTimeRange.last != 0L) Color.White else if (!cs.inProgress) MaterialTheme.colors.primary else HighOrLowlight, + tint = when { + recordingTimeRange.last != 0L -> Color.White + stopRecOnNextClick -> MaterialTheme.colors.primary + allowedVoiceByPrefs -> MaterialTheme.colors.primary + else -> HighOrLowlight + }, modifier = Modifier .size(36.dp) .padding(4.dp) @@ -301,10 +328,14 @@ fun PreviewSendMsgView() { SimpleXTheme { SendMsgView( composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, - allowVoiceRecord = false, + showVoiceRecordIcon = false, + allowedVoiceByPrefs = false, + needToAllowVoiceToContact = false, sendMessage = {}, onMessageChange = { _ -> }, onAudioAdded = { _, _, _ -> }, + allowVoiceToContact = {}, + showDisabledVoiceAlert = {}, textStyle = textStyle ) } @@ -324,10 +355,14 @@ fun PreviewSendMsgViewEditing() { SimpleXTheme { SendMsgView( composeState = remember { mutableStateOf(composeStateEditing) }, - allowVoiceRecord = false, + showVoiceRecordIcon = false, + allowedVoiceByPrefs = false, + needToAllowVoiceToContact = false, sendMessage = {}, onMessageChange = { _ -> }, onAudioAdded = { _, _, _ -> }, + allowVoiceToContact = {}, + showDisabledVoiceAlert = {}, textStyle = textStyle ) } @@ -347,10 +382,14 @@ fun PreviewSendMsgViewInProgress() { SimpleXTheme { SendMsgView( composeState = remember { mutableStateOf(composeStateInProgress) }, - allowVoiceRecord = false, + showVoiceRecordIcon = false, + allowedVoiceByPrefs = false, + needToAllowVoiceToContact = false, sendMessage = {}, onMessageChange = { _ -> }, onAudioAdded = { _, _, _ -> }, + allowVoiceToContact = {}, + showDisabledVoiceAlert = {}, textStyle = textStyle ) } diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt index 048490f72..c20d556e0 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/chat/item/FramedItemView.kt @@ -5,8 +5,7 @@ import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.InsertDriveFile -import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -95,7 +94,7 @@ fun FramedItemView( ciQuotedMsgView(qi) } Icon( - if (qi.content is MsgContent.MCFile) Icons.Filled.InsertDriveFile else Icons.Filled.PlayArrow, + if (qi.content is MsgContent.MCFile) Icons.Filled.InsertDriveFile else Icons.Filled.Mic, if (qi.content is MsgContent.MCFile) stringResource(R.string.icon_descr_file) else stringResource(R.string.voice_message), Modifier .padding(top = 6.dp, end = 4.dp) diff --git a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt index 41d6c7319..633d53c37 100644 --- a/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt +++ b/apps/android/app/src/main/java/chat/simplex/app/views/helpers/GestureDetector.kt @@ -214,8 +214,8 @@ fun interactionSourceWithDetection(onClick: () -> Unit, onLongClick: () -> Unit) } @Composable -fun interactionSourceWithTapDetection(onPress: () -> Unit, onClick: () -> Unit, onCancel: () -> Unit, onRelease: ()-> Unit): MutableInteractionSource { - val interactionSource = remember { MutableInteractionSource() } +fun interactionSourceWithTapDetection(key: Any, onPress: () -> Unit, onClick: () -> Unit, onCancel: () -> Unit, onRelease: ()-> Unit): MutableInteractionSource { + val interactionSource = remember(key) { MutableInteractionSource() } LaunchedEffect(interactionSource) { var firstTapTime = 0L interactionSource.interactions.collect { interaction -> diff --git a/apps/android/app/src/main/res/values-de/strings.xml b/apps/android/app/src/main/res/values-de/strings.xml index 39a660905..fed513f63 100644 --- a/apps/android/app/src/main/res/values-de/strings.xml +++ b/apps/android/app/src/main/res/values-de/strings.xml @@ -167,6 +167,7 @@ Speichern Bearbeiten Löschen + ***Allow Die Nachricht löschen? Nachricht wird gelöscht - dies kann nicht rückgängig gemacht werden! Nur für mich @@ -249,6 +250,11 @@ Nachricht senden ***Record voice message + ***Allow voice messages? + ***You need to allow your contact to send voice messages to be able to send them. + ***Voice messages prohibited! + ***Please ask your contact to enable sending voice messages. + ***Only group owners can enable voice messages. Zurück diff --git a/apps/android/app/src/main/res/values-ru/strings.xml b/apps/android/app/src/main/res/values-ru/strings.xml index ccdeaddc7..833d6b863 100644 --- a/apps/android/app/src/main/res/values-ru/strings.xml +++ b/apps/android/app/src/main/res/values-ru/strings.xml @@ -167,6 +167,7 @@ Сохранить Редактировать Удалить + Разрешить Удалить сообщение? Сообщение будет удалено – это действие нельзя отменить! Только для меня @@ -249,6 +250,11 @@ Отправить сообщение Записать голосовое сообщение + Разрешить голосовые сообщения? + Чтобы включить отправку голосовых сообщений, разрешите их вашему контакту. + Голосовые сообщения запрещены! + Попросите вашего контакта разрешить отправку голосовых сообщений. + Только владельцы группы могут разрешить голосовые сообщения. Назад diff --git a/apps/android/app/src/main/res/values/strings.xml b/apps/android/app/src/main/res/values/strings.xml index 48c521b50..c0e9613ab 100644 --- a/apps/android/app/src/main/res/values/strings.xml +++ b/apps/android/app/src/main/res/values/strings.xml @@ -167,6 +167,7 @@ Save Edit Delete + Allow Delete message? Message will be deleted - this cannot be undone! For me only @@ -249,6 +250,11 @@ Send Message Record voice message + Allow voice messages? + You need to allow your contact to send voice messages to be able to send them. + Voice messages prohibited! + Please ask your contact to enable sending voice messages. + Only group owners can enable voice messages. Back