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>
This commit is contained in:
Stanislav Dmitrenko
2022-11-25 19:31:04 +03:00
committed by GitHub
parent 60fedbf5d2
commit f8cf35879f
12 changed files with 125 additions and 24 deletions

View File

@@ -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)) {

View File

@@ -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()

View File

@@ -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) },

View File

@@ -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
)
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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<ComposeState>,
allowVoiceRecord: Boolean,
showVoiceRecordIcon: Boolean,
allowedVoiceByPrefs: Boolean,
needToAllowVoiceToContact: Boolean,
sendMessage: () -> Unit,
onMessageChange: (String) -> Unit,
onAudioAdded: (String, Int, Boolean) -> Unit,
allowVoiceToContact: () -> Unit,
showDisabledVoiceAlert: () -> Unit,
textStyle: MutableState<TextStyle>
) {
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
)
}

View File

@@ -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)

View File

@@ -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 ->

View File

@@ -167,6 +167,7 @@
<string name="save_verb">Speichern</string>
<string name="edit_verb">Bearbeiten</string>
<string name="delete_verb">Löschen</string>
<string name="allow_verb">***Allow</string>
<string name="delete_message__question">Die Nachricht löschen?</string>
<string name="delete_message_cannot_be_undone_warning">Nachricht wird gelöscht - dies kann nicht rückgängig gemacht werden!</string>
<string name="for_me_only">Nur für mich</string>
@@ -249,6 +250,11 @@
<!-- Message Actions - SendMsgView.kt -->
<string name="icon_descr_send_message">Nachricht senden</string>
<string name="icon_descr_record_voice_message">***Record voice message</string>
<string name="allow_voice_messages_question">***Allow voice messages?</string>
<string name="you_need_to_allow_to_send_voice">***You need to allow your contact to send voice messages to be able to send them.</string>
<string name="voice_messages_prohibited">***Voice messages prohibited!</string>
<string name="ask_your_contact_to_enable_voice">***Please ask your contact to enable sending voice messages.</string>
<string name="only_group_owners_can_enable_voice">***Only group owners can enable voice messages.</string>
<!-- General Actions / Responses -->
<string name="back">Zurück</string>

View File

@@ -167,6 +167,7 @@
<string name="save_verb">Сохранить</string>
<string name="edit_verb">Редактировать</string>
<string name="delete_verb">Удалить</string>
<string name="allow_verb">Разрешить</string>
<string name="delete_message__question">Удалить сообщение?</string>
<string name="delete_message_cannot_be_undone_warning">Сообщение будет удалено это действие нельзя отменить!</string>
<string name="for_me_only">Только для меня</string>
@@ -249,6 +250,11 @@
<!-- Message Actions - SendMsgView.kt -->
<string name="icon_descr_send_message">Отправить сообщение</string>
<string name="icon_descr_record_voice_message">Записать голосовое сообщение</string>
<string name="allow_voice_messages_question">Разрешить голосовые сообщения?</string>
<string name="you_need_to_allow_to_send_voice">Чтобы включить отправку голосовых сообщений, разрешите их вашему контакту.</string>
<string name="voice_messages_prohibited">Голосовые сообщения запрещены!</string>
<string name="ask_your_contact_to_enable_voice">Попросите вашего контакта разрешить отправку голосовых сообщений.</string>
<string name="only_group_owners_can_enable_voice">Только владельцы группы могут разрешить голосовые сообщения.</string>
<!-- General Actions / Responses -->
<string name="back">Назад</string>

View File

@@ -167,6 +167,7 @@
<string name="save_verb">Save</string>
<string name="edit_verb">Edit</string>
<string name="delete_verb">Delete</string>
<string name="allow_verb">Allow</string>
<string name="delete_message__question">Delete message?</string>
<string name="delete_message_cannot_be_undone_warning">Message will be deleted - this cannot be undone!</string>
<string name="for_me_only">For me only</string>
@@ -249,6 +250,11 @@
<!-- Message Actions - SendMsgView.kt -->
<string name="icon_descr_send_message">Send Message</string>
<string name="icon_descr_record_voice_message">Record voice message</string>
<string name="allow_voice_messages_question">Allow voice messages?</string>
<string name="you_need_to_allow_to_send_voice">You need to allow your contact to send voice messages to be able to send them.</string>
<string name="voice_messages_prohibited">Voice messages prohibited!</string>
<string name="ask_your_contact_to_enable_voice">Please ask your contact to enable sending voice messages.</string>
<string name="only_group_owners_can_enable_voice">Only group owners can enable voice messages.</string>
<!-- General Actions / Responses -->
<string name="back">Back</string>