mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
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:
parent
26e5742354
commit
7b362ff655
14 changed files with 164 additions and 128 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 { "" } }
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() },
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = "",
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue