ui: label in compose when user cannot send messages (#5922)

* ui: label in compose when user cannot send messages

* gray buttons when user cannot send messages

* improve

* kotlin

* fix order

* fix alert

---------

Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com>
This commit is contained in:
Evgeny 2025-05-19 15:50:33 +01:00 committed by GitHub
parent 26e5742354
commit 7b362ff655
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 164 additions and 128 deletions

View file

@ -1152,27 +1152,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
)
}
var userCanSend: Bool {
switch chatInfo {
case .direct: return true
case let .group(groupInfo):
let m = groupInfo.membership
return m.memberActive && m.memberRole >= .member
case .local:
return true
default: return false
}
}
var userIsObserver: Bool {
switch chatInfo {
case let .group(groupInfo):
let m = groupInfo.membership
return m.memberActive && m.memberRole == .observer
default: return false
}
}
var unreadTag: Bool {
switch chatInfo.chatSettings?.enableNtfs {
case .all: chatStats.unreadChat || chatStats.unreadCount > 0

View file

@ -98,14 +98,24 @@ struct ChatView: View {
}
connectingText()
if selectedChatItems == nil {
let reason = chat.chatInfo.userCantSendReason
ComposeView(
chat: chat,
composeState: $composeState,
keyboardVisible: $keyboardVisible,
keyboardHiddenDate: $keyboardHiddenDate,
selectedRange: $selectedRange
selectedRange: $selectedRange,
disabledText: reason?.composeLabel
)
.disabled(!cInfo.sendMsgEnabled)
.if(!cInfo.sendMsgEnabled) { v in
v.disabled(true).onTapGesture {
AlertManager.shared.showAlertMsg(
title: "You can't send messages!",
message: reason?.alertMessage
)
}
}
} else {
SelectedItemsBottomToolbar(
chatItems: ItemsModel.shared.reversedChatItems,

View file

@ -327,6 +327,7 @@ struct ComposeView: View {
@Binding var keyboardVisible: Bool
@Binding var keyboardHiddenDate: Date
@Binding var selectedRange: NSRange
var disabledText: LocalizedStringKey? = nil
@State var linkUrl: URL? = nil
@State var hasSimplexLink: Bool = false
@ -391,7 +392,7 @@ struct ComposeView: View {
Image(systemName: "paperclip")
.resizable()
}
.disabled(composeState.attachmentDisabled || !chat.userCanSend || (chat.chatInfo.contact?.nextSendGrpInv ?? false))
.disabled(composeState.attachmentDisabled || !chat.chatInfo.sendMsgEnabled || (chat.chatInfo.contact?.nextSendGrpInv ?? false))
.frame(width: 25, height: 25)
.padding(.bottom, 16)
.padding(.leading, 12)
@ -441,19 +442,13 @@ struct ComposeView: View {
: theme.colors.primary
)
.padding(.trailing, 12)
.disabled(!chat.userCanSend)
.disabled(!chat.chatInfo.sendMsgEnabled)
if chat.userIsObserver {
Text("you are observer")
if let disabledText {
Text(disabledText)
.italic()
.foregroundColor(theme.colors.secondary)
.padding(.horizontal, 12)
.onTapGesture {
AlertManager.shared.showAlertMsg(
title: "You can't send messages!",
message: "Please contact group admin."
)
}
}
}
}
@ -479,8 +474,8 @@ struct ComposeView: View {
hasSimplexLink = false
}
}
.onChange(of: chat.userCanSend) { canSend in
if !canSend {
.onChange(of: chat.chatInfo.sendMsgEnabled) { sendEnabled in
if !sendEnabled {
cancelCurrentVoiceRecording()
clearCurrentDraft()
clearState()

View file

@ -15,6 +15,7 @@ struct SendMessageView: View {
@Binding var composeState: ComposeState
@Binding var selectedRange: NSRange
@EnvironmentObject var theme: AppTheme
@Environment(\.isEnabled) var isEnabled
var sendMessage: (Int?) -> Void
var sendLiveMessage: (() async -> Void)? = nil
var updateLiveMessage: (() async -> Void)? = nil
@ -255,6 +256,7 @@ struct SendMessageView: View {
}
private struct RecordVoiceMessageButton: View {
@Environment(\.isEnabled) var isEnabled
@EnvironmentObject var theme: AppTheme
var startVoiceMessageRecording: (() -> Void)?
var finishVoiceMessageRecording: (() -> Void)?
@ -263,11 +265,11 @@ struct SendMessageView: View {
@State private var pressed: TimeInterval? = nil
var body: some View {
Image(systemName: "mic.fill")
Image(systemName: isEnabled ? "mic.fill" : "mic")
.resizable()
.scaledToFit()
.frame(width: 20, height: 20)
.foregroundColor(theme.colors.primary)
.foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary)
.opacity(holdingVMR ? 0.7 : 1)
.disabled(disabled)
.frame(width: 31, height: 31)
@ -352,7 +354,7 @@ struct SendMessageView: View {
Image(systemName: "bolt.fill")
.resizable()
.scaledToFit()
.foregroundColor(theme.colors.primary)
.foregroundColor(isEnabled ? theme.colors.primary : theme.colors.secondary)
.frame(width: 20, height: 20)
}
.frame(width: 29, height: 29)

View file

@ -1333,6 +1333,19 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
}
}
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
get {
switch self {
case let .direct(contact): return contact.userCantSendReason
case let .group(groupInfo): return groupInfo.userCantSendReason
case let .local(noteFolder): return noteFolder.userCantSendReason
case let .contactRequest(contactRequest): return contactRequest.userCantSendReason
case let .contactConnection(contactConnection): return contactConnection.userCantSendReason
case .invalidJSON: return ("can't send messages", nil)
}
}
}
public var sendMsgEnabled: Bool {
get {
switch self {
@ -1642,15 +1655,16 @@ public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
public var ready: Bool { get { activeConn?.connStatus == .ready } }
public var sndReady: Bool { get { ready || activeConn?.connStatus == .sndReady } }
public var active: Bool { get { contactStatus == .active } }
public var sendMsgEnabled: Bool { get {
(
sndReady
&& active
&& !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false)
&& !(activeConn?.connDisabled ?? true)
)
|| nextSendGrpInv
} }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if nextSendGrpInv { return nil }
if !active { return ("contact deleted", nil) }
if !sndReady { return ("contact not ready", nil) }
if activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) }
if activeConn?.connDisabled ?? true { return ("contact disabled", nil) }
return nil
}
public var sendMsgEnabled: Bool { userCantSendReason == nil }
public var nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
public var fullName: String { get { profile.fullName } }
@ -1829,6 +1843,7 @@ public struct UserContactRequest: Decodable, NamedChat, Hashable {
public var id: ChatId { get { "<@\(contactRequestId)" } }
public var apiId: Int64 { get { contactRequestId } }
var ready: Bool { get { true } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) }
public var sendMsgEnabled: Bool { get { false } }
public var displayName: String { get { profile.displayName } }
public var fullName: String { get { profile.fullName } }
@ -1861,6 +1876,7 @@ public struct PendingContactConnection: Decodable, NamedChat, Hashable {
public var id: ChatId { get { ":\(pccConnId)" } }
public var apiId: Int64 { get { pccConnId } }
var ready: Bool { get { false } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { ("can't send messages", nil) }
public var sendMsgEnabled: Bool { get { false } }
var localDisplayName: String {
get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) }
@ -1990,7 +2006,20 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
public var id: ChatId { get { "#\(groupId)" } }
public var apiId: Int64 { get { groupId } }
public var ready: Bool { get { true } }
public var sendMsgEnabled: Bool { get { membership.memberActive } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
return if membership.memberActive {
membership.memberRole == .observer ? ("you are observer", "Please contact group admin.") : nil
} else {
switch membership.memberStatus {
case .memRejected: ("request to join rejected", nil)
case .memGroupDeleted: ("group is deleted", nil)
case .memRemoved: ("removed from group", nil)
case .memLeft: ("you left", nil)
default: ("can't send messages", nil)
}
}
}
public var sendMsgEnabled: Bool { userCantSendReason == nil }
public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias }
public var fullName: String { get { groupProfile.fullName } }
public var image: String? { get { groupProfile.image } }
@ -2357,6 +2386,7 @@ public struct NoteFolder: Identifiable, Decodable, NamedChat, Hashable {
public var id: ChatId { get { "*\(noteFolderId)" } }
public var apiId: Int64 { get { noteFolderId } }
public var ready: Bool { get { true } }
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? { nil }
public var sendMsgEnabled: Bool { get { true } }
public var displayName: String { get { ChatInfo.privateNotesChatName } }
public var fullName: String { get { "" } }

View file

@ -42,7 +42,6 @@ import chat.simplex.common.views.helpers.*
import chat.simplex.res.MR
import dev.icerock.moko.resources.StringResource
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.filter
import java.lang.reflect.Field
import java.net.URI
@ -51,10 +50,10 @@ import java.net.URI
actual fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
disabledText: String?,
sendMsgButtonDisabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
placeholder: String,
showVoiceButton: Boolean,
onMessageChange: (ComposeMessage) -> Unit,
@ -197,16 +196,16 @@ actual fun PlatformTextField(
showDeleteTextButton.value = it.lineCount >= 4 && !cs.inProgress
}
if (composeState.value.preview is ComposePreview.VoicePreview) {
ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(MR.strings.you_are_observer, textStyle, padding)
ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding)
} else if (disabledText != null) {
ComposeOverlay(disabledText, textStyle, padding)
}
}
@Composable
private fun ComposeOverlay(textId: StringResource, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
private fun ComposeOverlay(text: String, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
Text(
generalGetString(textId),
text,
Modifier.padding(padding),
color = MaterialTheme.colors.secondary,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)

View file

@ -1204,6 +1204,7 @@ interface SomeChat {
val apiId: Long
val ready: Boolean
val chatDeleted: Boolean
val userCantSendReason: Pair<String, String?>?
val sendMsgEnabled: Boolean
val incognito: Boolean
fun featureEnabled(feature: ChatFeature): Boolean
@ -1228,14 +1229,6 @@ data class Chat(
else -> false
}
val userIsObserver: Boolean get() = when(chatInfo) {
is ChatInfo.Group -> {
val m = chatInfo.groupInfo.membership
m.memberActive && m.memberRole == GroupMemberRole.Observer
}
else -> false
}
val unreadTag: Boolean get() = when (chatInfo.chatSettings?.enableNtfs) {
All -> chatStats.unreadChat || chatStats.unreadCount > 0
Mentions -> chatStats.unreadChat || chatStats.unreadMentions > 0
@ -1282,6 +1275,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contact.apiId
override val ready get() = contact.ready
override val chatDeleted get() = contact.chatDeleted
override val userCantSendReason get() = contact.userCantSendReason
override val sendMsgEnabled get() = contact.sendMsgEnabled
override val incognito get() = contact.incognito
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
@ -1307,6 +1301,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = groupInfo.apiId
override val ready get() = groupInfo.ready
override val chatDeleted get() = groupInfo.chatDeleted
override val userCantSendReason get() = groupInfo.userCantSendReason
override val sendMsgEnabled get() = groupInfo.sendMsgEnabled
override val incognito get() = groupInfo.incognito
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
@ -1331,6 +1326,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = noteFolder.apiId
override val ready get() = noteFolder.ready
override val chatDeleted get() = noteFolder.chatDeleted
override val userCantSendReason get() = noteFolder.userCantSendReason
override val sendMsgEnabled get() = noteFolder.sendMsgEnabled
override val incognito get() = noteFolder.incognito
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
@ -1355,6 +1351,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contactRequest.apiId
override val ready get() = contactRequest.ready
override val chatDeleted get() = contactRequest.chatDeleted
override val userCantSendReason get() = contactRequest.userCantSendReason
override val sendMsgEnabled get() = contactRequest.sendMsgEnabled
override val incognito get() = contactRequest.incognito
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
@ -1379,6 +1376,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val apiId get() = contactConnection.apiId
override val ready get() = contactConnection.ready
override val chatDeleted get() = contactConnection.chatDeleted
override val userCantSendReason get() = contactConnection.userCantSendReason
override val sendMsgEnabled get() = contactConnection.sendMsgEnabled
override val incognito get() = contactConnection.incognito
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
@ -1408,6 +1406,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val id get() = "?$apiId"
override val ready get() = false
override val chatDeleted get() = false
override val userCantSendReason get() = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
@ -1450,14 +1449,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
is InvalidJSON -> updatedAt
}
val userCanSend: Boolean
get() = when (this) {
is ChatInfo.Direct -> true
is ChatInfo.Group -> groupInfo.membership.memberRole >= GroupMemberRole.Member
is ChatInfo.Local -> true
else -> false
}
val chatTags: List<Long>?
get() = when (this) {
is Direct -> contact.chatTags
@ -1528,13 +1519,17 @@ data class Contact(
override val ready get() = activeConn?.connStatus == ConnStatus.Ready
val sndReady get() = ready || activeConn?.connStatus == ConnStatus.SndReady
val active get() = contactStatus == ContactStatus.Active
override val sendMsgEnabled get() = (
sndReady
&& active
&& !(activeConn?.connectionStats?.ratchetSyncSendProhibited ?: false)
&& !(activeConn?.connDisabled ?: true)
)
|| nextSendGrpInv
override val userCantSendReason: Pair<String, String?>?
get() {
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
if (nextSendGrpInv) return null
if (!active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null
if (!sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null
if (activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
if (activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
return null
}
override val sendMsgEnabled get() = userCantSendReason == null
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
override val incognito get() = contactConnIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
@ -1768,7 +1763,23 @@ data class GroupInfo (
override val apiId get() = groupId
override val ready get() = membership.memberActive
override val chatDeleted get() = false
override val sendMsgEnabled get() = membership.memberActive
override val userCantSendReason: Pair<String, String?>? get() =
if (membership.memberActive) {
if (membership.memberRole == GroupMemberRole.Observer) {
generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
} else {
null
}
} else {
when (membership.memberStatus) {
GroupMemberStatus.MemRejected -> generalGetString(MR.strings.cant_send_message_rejected) to null
GroupMemberStatus.MemGroupDeleted -> generalGetString(MR.strings.cant_send_message_group_deleted) to null
GroupMemberStatus.MemRemoved -> generalGetString(MR.strings.cant_send_message_mem_removed) to null
GroupMemberStatus.MemLeft -> generalGetString(MR.strings.cant_send_message_you_left) to null
else -> generalGetString(MR.strings.cant_send_message_generic) to null
}
}
override val sendMsgEnabled get() = userCantSendReason == null
override val incognito get() = membership.memberIncognito
override fun featureEnabled(feature: ChatFeature) = when (feature) {
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
@ -2144,6 +2155,7 @@ class NoteFolder(
override val apiId get() = noteFolderId
override val chatDeleted get() = false
override val ready get() = true
override val userCantSendReason: Pair<String, String?>? = null
override val sendMsgEnabled get() = true
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
@ -2180,6 +2192,7 @@ class UserContactRequest (
override val apiId get() = contactRequestId
override val chatDeleted get() = false
override val ready get() = true
override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = false
@ -2219,6 +2232,7 @@ class PendingContactConnection(
override val apiId get() = pccConnId
override val chatDeleted get() = false
override val ready get() = false
override val userCantSendReason = generalGetString(MR.strings.cant_send_message_generic) to null
override val sendMsgEnabled get() = false
override val incognito get() = customUserProfileId != null
override fun featureEnabled(feature: ChatFeature) = false

View file

@ -12,10 +12,10 @@ import java.net.URI
expect fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
disabledText: String?,
sendMsgButtonDisabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
placeholder: String,
showVoiceButton: Boolean,
onMessageChange: (ComposeMessage) -> Unit,

View file

@ -99,12 +99,11 @@ fun TerminalLayout(
isDirectChat = false,
liveMessageAlertShown = SharedPreference(get = { false }, set = {}),
sendMsgEnabled = true,
userCantSendReason = null,
sendButtonEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = false,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
placeholder = "",
sendMessage = { sendCommand() },

View file

@ -723,7 +723,7 @@ fun ChatLayout(
Modifier
.fillMaxWidth()
.desktopOnExternalDrag(
enabled = remember(attachmentDisabled.value, chatInfo.value?.userCanSend) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.userCanSend == true) }.value,
enabled = remember(attachmentDisabled.value, chatInfo.value?.sendMsgEnabled) { mutableStateOf(!attachmentDisabled.value && chatInfo.value?.sendMsgEnabled == true) }.value,
onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) },
onImage = { file -> CoroutineScope(Dispatchers.IO).launch { composeState.processPickedMedia(listOf(file.toURI()), null) } },
onText = {

View file

@ -999,9 +999,8 @@ fun ComposeView(
chatModel.sharedContent.value = null
}
val userCanSend = rememberUpdatedState(chat.chatInfo.userCanSend)
val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled)
val userIsObserver = rememberUpdatedState(chat.userIsObserver)
val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason)
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
Column {
@ -1056,7 +1055,6 @@ fun ComposeView(
val attachmentEnabled =
!composeState.value.attachmentDisabled
&& sendMsgEnabled.value
&& userCanSend.value
&& !isGroupAndProhibitedFiles
&& !nextSendGrpInv.value
IconButton(
@ -1102,8 +1100,8 @@ fun ComposeView(
}
}
LaunchedEffect(rememberUpdatedState(chat.chatInfo.userCanSend).value) {
if (!chat.chatInfo.userCanSend) {
LaunchedEffect(rememberUpdatedState(chat.chatInfo.sendMsgEnabled).value) {
if (!chat.chatInfo.sendMsgEnabled) {
clearCurrentDraft()
clearState()
}
@ -1159,13 +1157,12 @@ fun ComposeView(
chat.chatInfo is ChatInfo.Direct,
liveMessageAlertShown = chatModel.controller.appPrefs.liveMessageAlertShown,
sendMsgEnabled = sendMsgEnabled.value,
userCantSendReason = userCantSendReason.value,
sendButtonEnabled = sendMsgEnabled.value && !(simplexLinkProhibited || fileProhibited || voiceProhibited),
nextSendGrpInv = nextSendGrpInv.value,
needToAllowVoiceToContact,
allowedVoiceByPrefs,
allowVoiceToContact = ::allowVoiceToContact,
userIsObserver = userIsObserver.value,
userCanSend = userCanSend.value,
sendButtonColor = sendButtonColor,
timedMessageAllowed = timedMessageAllowed,
customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime,

View file

@ -15,9 +15,7 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.*
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.unit.*
import chat.simplex.common.model.*
@ -41,12 +39,11 @@ fun SendMsgView(
isDirectChat: Boolean,
liveMessageAlertShown: SharedPreference<Boolean>,
sendMsgEnabled: Boolean,
userCantSendReason: Pair<String, String?>?,
sendButtonEnabled: Boolean,
nextSendGrpInv: Boolean,
needToAllowVoiceToContact: Boolean,
allowedVoiceByPrefs: Boolean,
userIsObserver: Boolean,
userCanSend: Boolean,
sendButtonColor: Color = MaterialTheme.colors.primary,
allowVoiceToContact: () -> Unit,
timedMessageAllowed: Boolean = false,
@ -82,14 +79,14 @@ fun SendMsgView(
(!allowedVoiceByPrefs && cs.preview is ComposePreview.VoicePreview) ||
cs.endLiveDisabled ||
!sendButtonEnabled
val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress
val clicksOnTextFieldDisabled = !sendMsgEnabled || cs.preview is ComposePreview.VoicePreview || cs.inProgress
PlatformTextField(
composeState,
sendMsgEnabled,
disabledText = userCantSendReason?.first,
sendMsgButtonDisabled,
textStyle,
showDeleteTextButton,
userIsObserver,
if (clicksOnTextFieldDisabled) "" else placeholder,
showVoiceButton,
onMessageChange,
@ -102,16 +99,23 @@ fun SendMsgView(
}
}
if (clicksOnTextFieldDisabled) {
Box(
Modifier
.matchParentSize()
.clickable(enabled = !userCanSend, indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.observer_cant_send_message_title),
text = generalGetString(MR.strings.observer_cant_send_message_desc)
)
})
)
if (userCantSendReason != null) {
Box(
Modifier
.matchParentSize()
.clickable(indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.cant_send_message_alert_title),
text = userCantSendReason.second
)
})
)
} else {
Box(
Modifier
.matchParentSize()
)
}
}
if (showDeleteTextButton.value) {
DeleteTextButton(composeState)
@ -135,11 +139,11 @@ fun SendMsgView(
Row(verticalAlignment = Alignment.CenterVertically) {
val stopRecOnNextClick = remember { mutableStateOf(false) }
when {
needToAllowVoiceToContact || !allowedVoiceByPrefs || !userCanSend -> {
DisallowedVoiceButton(userCanSend) {
needToAllowVoiceToContact || !allowedVoiceByPrefs -> {
DisallowedVoiceButton {
if (needToAllowVoiceToContact) {
showNeedToAllowVoiceAlert(allowVoiceToContact)
} else if (!allowedVoiceByPrefs) {
} else {
showDisabledVoiceAlert(isDirectChat)
}
}
@ -155,7 +159,7 @@ fun SendMsgView(
&& cs.contextItem is ComposeContextItem.NoContextItem
) {
Spacer(Modifier.width(12.dp))
StartLiveMessageButton(userCanSend) {
StartLiveMessageButton {
if (composeState.value.preview is ComposePreview.NoPreview) {
startLiveMessage(scope, sendLiveMessage, updateLiveMessage, sendButtonSize, sendButtonAlpha, composeState, liveMessageAlertShown)
}
@ -343,8 +347,8 @@ private fun RecordVoiceView(recState: MutableState<RecordingState>, stopRecOnNex
}
@Composable
private fun DisallowedVoiceButton(enabled: Boolean, onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp), enabled = enabled) {
private fun DisallowedVoiceButton(onClick: () -> Unit) {
IconButton(onClick, Modifier.size(36.dp)) {
Icon(
painterResource(MR.images.ic_keyboard_voice),
stringResource(MR.strings.icon_descr_record_voice_message),
@ -460,14 +464,13 @@ private fun SendMsgButton(
}
@Composable
private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
private fun StartLiveMessageButton(onClick: () -> Unit) {
val interactionSource = remember { MutableInteractionSource() }
val ripple = remember { ripple(bounded = false, radius = 24.dp) }
Box(
modifier = Modifier.requiredSize(36.dp)
.clickable(
onClick = onClick,
enabled = enabled,
role = Role.Button,
interactionSource = interactionSource,
indication = ripple
@ -477,7 +480,7 @@ private fun StartLiveMessageButton(enabled: Boolean, onClick: () -> Unit) {
Icon(
BoltFilled,
stringResource(MR.strings.icon_descr_send_message),
tint = if (enabled) MaterialTheme.colors.primary else MaterialTheme.colors.secondary,
tint = MaterialTheme.colors.primary,
modifier = Modifier
.size(36.dp)
.padding(4.dp)
@ -576,12 +579,11 @@ fun PreviewSendMsgView() {
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
userCantSendReason = null,
sendButtonEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
timedMessageAllowed = false,
placeholder = "",
@ -612,12 +614,11 @@ fun PreviewSendMsgViewEditing() {
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
userCantSendReason = null,
sendButtonEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
timedMessageAllowed = false,
placeholder = "",
@ -648,12 +649,11 @@ fun PreviewSendMsgViewInProgress() {
isDirectChat = true,
liveMessageAlertShown = SharedPreference(get = { true }, set = { }),
sendMsgEnabled = true,
userCantSendReason = null,
sendButtonEnabled = true,
nextSendGrpInv = false,
needToAllowVoiceToContact = false,
allowedVoiceByPrefs = true,
userIsObserver = false,
userCanSend = true,
allowVoiceToContact = {},
timedMessageAllowed = false,
placeholder = "",

View file

@ -487,8 +487,6 @@
<string name="image_decoding_exception_desc">The image cannot be decoded. Please, try a different image or contact developers.</string>
<string name="video_decoding_exception_desc">The video cannot be decoded. Please, try a different video or contact developers.</string>
<string name="you_are_observer">you are observer</string>
<string name="observer_cant_send_message_title">You can\'t send messages!</string>
<string name="observer_cant_send_message_desc">Please contact group admin.</string>
<string name="files_and_media_prohibited">Files and media prohibited!</string>
<string name="only_owners_can_enable_files_and_media">Only group owners can enable files and media.</string>
<string name="compose_send_direct_message_to_connect">Send direct message to connect</string>
@ -508,6 +506,19 @@
<string name="report_compose_reason_header_illegal">Report content: only group moderators will see it.</string>
<string name="report_compose_reason_header_other">Report other: only group moderators will see it.</string>
<string name="cant_send_message_alert_title">You can\'t send messages!</string>
<string name="cant_send_message_contact_not_ready">contact not ready</string>
<string name="cant_send_message_contact_deleted">contact deleted</string>
<string name="cant_send_message_contact_not_synchronized">not synchronized</string>
<string name="cant_send_message_contact_disabled">contact disabled</string>
<string name="observer_cant_send_message_title">you are observer</string>
<string name="observer_cant_send_message_desc">Please contact group admin.</string>
<string name="cant_send_message_rejected">request to join rejected</string>
<string name="cant_send_message_group_deleted">group is deleted</string>
<string name="cant_send_message_mem_removed">removed from group</string>
<string name="cant_send_message_you_left">you left</string>
<string name="cant_send_message_generic">can\'t send messages</string>
<!-- Images - chat.simplex.app.views.chat.item.CIImageView.kt -->
<string name="image_descr">Image</string>
<string name="icon_descr_waiting_for_image">Waiting for image</string>

View file

@ -44,10 +44,10 @@ import kotlin.text.substring
actual fun PlatformTextField(
composeState: MutableState<ComposeState>,
sendMsgEnabled: Boolean,
disabledText: String?,
sendMsgButtonDisabled: Boolean,
textStyle: MutableState<TextStyle>,
showDeleteTextButton: MutableState<Boolean>,
userIsObserver: Boolean,
placeholder: String,
showVoiceButton: Boolean,
onMessageChange: (ComposeMessage) -> Unit,
@ -203,16 +203,16 @@ actual fun PlatformTextField(
)
showDeleteTextButton.value = cs.message.text.split("\n").size >= 4 && !cs.inProgress
if (composeState.value.preview is ComposePreview.VoicePreview) {
ComposeOverlay(MR.strings.voice_message_send_text, textStyle, padding)
} else if (userIsObserver) {
ComposeOverlay(MR.strings.you_are_observer, textStyle, padding)
ComposeOverlay(generalGetString(MR.strings.voice_message_send_text), textStyle, padding)
} else if (disabledText != null) {
ComposeOverlay(disabledText, textStyle, padding)
}
}
@Composable
private fun ComposeOverlay(textId: StringResource, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
private fun ComposeOverlay(text: String, textStyle: MutableState<TextStyle>, padding: PaddingValues) {
Text(
generalGetString(textId),
text,
Modifier.padding(padding),
color = MaterialTheme.colors.secondary,
style = textStyle.value.copy(fontStyle = FontStyle.Italic)