mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-28 05:24:44 +00:00
Merge branch 'stable'
This commit is contained in:
@@ -1248,34 +1248,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
|
||||
)
|
||||
}
|
||||
|
||||
var userCanSend: Bool {
|
||||
switch chatInfo {
|
||||
case .direct: return true
|
||||
case let .group(groupInfo, groupChatScope):
|
||||
let m = groupInfo.membership
|
||||
return (m.memberActive && m.memberRole >= .member && !m.memberPending) || groupChatScope != nil
|
||||
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 userIsPending: Bool {
|
||||
switch chatInfo {
|
||||
case let .group(groupInfo, _): groupInfo.membership.memberPending
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
var unreadTag: Bool {
|
||||
switch chatInfo.chatSettings?.enableNtfs {
|
||||
case .all: chatStats.unreadChat || chatStats.unreadCount > 0
|
||||
|
||||
@@ -1689,8 +1689,8 @@ func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
|
||||
func filterMembersToAdd(_ ms: [GMember]) -> [Contact] {
|
||||
let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil }
|
||||
return ChatModel.shared.chats
|
||||
.compactMap{ $0.chatInfo.contact }
|
||||
.filter{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
|
||||
.compactMap{ c in c.chatInfo.sendMsgEnabled ? c.chatInfo.contact : nil }
|
||||
.filter{ c in !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
|
||||
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
|
||||
}
|
||||
|
||||
|
||||
@@ -116,15 +116,25 @@ struct ChatView: View {
|
||||
}
|
||||
connectingText()
|
||||
if selectedChatItems == nil {
|
||||
let reason = chat.chatInfo.userCantSendReason
|
||||
ComposeView(
|
||||
chat: chat,
|
||||
im: im,
|
||||
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(
|
||||
im: im,
|
||||
|
||||
@@ -328,6 +328,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
|
||||
@@ -406,7 +407,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)
|
||||
@@ -453,36 +454,17 @@ struct ComposeView: View {
|
||||
keyboardVisible: $keyboardVisible,
|
||||
keyboardHiddenDate: $keyboardHiddenDate,
|
||||
sendButtonColor: chat.chatInfo.incognito
|
||||
? .indigo.opacity(colorScheme == .dark ? 1 : 0.7)
|
||||
: theme.colors.primary
|
||||
? .indigo.opacity(colorScheme == .dark ? 1 : 0.7)
|
||||
: theme.colors.primary
|
||||
)
|
||||
.padding(.trailing, 12)
|
||||
.disabled(!chat.userCanSend)
|
||||
.disabled(!chat.chatInfo.sendMsgEnabled)
|
||||
|
||||
if im.secondaryIMFilter == nil {
|
||||
if chat.userIsPending {
|
||||
Text("reviewed by admins")
|
||||
.italic()
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.horizontal, 12)
|
||||
.onTapGesture {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "You can't send messages!",
|
||||
message: "Please contact group admin."
|
||||
)
|
||||
}
|
||||
} else if chat.userIsObserver {
|
||||
Text("you are observer")
|
||||
.italic()
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.horizontal, 12)
|
||||
.onTapGesture {
|
||||
AlertManager.shared.showAlertMsg(
|
||||
title: "You can't send messages!",
|
||||
message: "Please contact group admin."
|
||||
)
|
||||
}
|
||||
}
|
||||
if let disabledText {
|
||||
Text(disabledText)
|
||||
.italic()
|
||||
.foregroundColor(theme.colors.secondary)
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -508,8 +490,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,19 +1333,47 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
}
|
||||
}
|
||||
|
||||
public var sendMsgEnabled: Bool {
|
||||
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
|
||||
get {
|
||||
switch self {
|
||||
case let .direct(contact): return contact.sendMsgEnabled
|
||||
case let .group(groupInfo, _): return groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): return noteFolder.sendMsgEnabled
|
||||
case let .contactRequest(contactRequest): return contactRequest.sendMsgEnabled
|
||||
case let .contactConnection(contactConnection): return contactConnection.sendMsgEnabled
|
||||
case .invalidJSON: return false
|
||||
case let .direct(contact):
|
||||
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
|
||||
if contact.nextSendGrpInv { return nil }
|
||||
if !contact.active { return ("contact deleted", nil) }
|
||||
if !contact.sndReady { return ("contact not ready", nil) }
|
||||
if contact.activeConn?.connectionStats?.ratchetSyncSendProhibited ?? false { return ("not synchronized", nil) }
|
||||
if contact.activeConn?.connDisabled ?? true { return ("contact disabled", nil) }
|
||||
return nil
|
||||
case let .group(groupInfo, groupChatScope):
|
||||
if groupInfo.membership.memberActive {
|
||||
if groupChatScope == nil {
|
||||
if groupInfo.membership.memberPending { return ("reviewed by admins", "Please contact group admin.") }
|
||||
if groupInfo.membership.memberRole == .observer { return ("you are observer", "Please contact group admin.") }
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
switch groupInfo.membership.memberStatus {
|
||||
case .memRejected: return ("request to join rejected", nil)
|
||||
case .memGroupDeleted: return ("group is deleted", nil)
|
||||
case .memRemoved: return ("removed from group", nil)
|
||||
case .memLeft: return ("you left", nil)
|
||||
default: return ("can't send messages", nil)
|
||||
}
|
||||
}
|
||||
case .local:
|
||||
return nil
|
||||
case .contactRequest:
|
||||
return ("can't send messages", nil)
|
||||
case .contactConnection:
|
||||
return ("can't send messages", nil)
|
||||
case .invalidJSON:
|
||||
return ("can't send messages", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public var sendMsgEnabled: Bool { userCantSendReason == nil }
|
||||
|
||||
public var incognito: Bool {
|
||||
get {
|
||||
switch self {
|
||||
@@ -1681,15 +1709,6 @@ 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 nextSendGrpInv: Bool { get { contactGroupMemberId != nil && !contactGrpInvSent } }
|
||||
public var displayName: String { localAlias == "" ? profile.displayName : localAlias }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
@@ -1868,7 +1887,6 @@ 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 sendMsgEnabled: Bool { get { false } }
|
||||
public var displayName: String { get { profile.displayName } }
|
||||
public var fullName: String { get { profile.fullName } }
|
||||
public var image: String? { get { profile.image } }
|
||||
@@ -1900,7 +1918,6 @@ 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 sendMsgEnabled: Bool { get { false } }
|
||||
var localDisplayName: String {
|
||||
get { String.localizedStringWithFormat(NSLocalizedString("connection:%@", comment: "connection information"), pccConnId) }
|
||||
}
|
||||
@@ -2030,7 +2047,6 @@ 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 displayName: String { localAlias == "" ? groupProfile.displayName : localAlias }
|
||||
public var fullName: String { get { groupProfile.fullName } }
|
||||
public var image: String? { get { groupProfile.image } }
|
||||
@@ -2469,7 +2485,6 @@ 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 sendMsgEnabled: Bool { get { true } }
|
||||
public var displayName: String { get { ChatInfo.privateNotesChatName } }
|
||||
public var fullName: String { get { "" } }
|
||||
public var image: String? { get { nil } }
|
||||
|
||||
@@ -82,9 +82,9 @@ public func foundChat(_ chat: ChatLike, _ searchStr: String) -> Bool {
|
||||
|
||||
private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
|
||||
switch cInfo {
|
||||
case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case let .group(groupInfo, _): groupInfo.sendMsgEnabled
|
||||
case let .local(noteFolder): noteFolder.sendMsgEnabled
|
||||
case let .direct(contact): cInfo.sendMsgEnabled && !contact.nextSendGrpInv
|
||||
case .group: cInfo.sendMsgEnabled
|
||||
case .local: cInfo.sendMsgEnabled
|
||||
case .contactRequest: false
|
||||
case .contactConnection: false
|
||||
case .invalidJSON: false
|
||||
|
||||
+6
-10
@@ -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,11 +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,
|
||||
userIsPending: Boolean,
|
||||
placeholder: String,
|
||||
showVoiceButton: Boolean,
|
||||
onMessageChange: (ComposeMessage) -> Unit,
|
||||
@@ -198,18 +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 (userIsPending) {
|
||||
ComposeOverlay(MR.strings.reviewed_by_admins, 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)
|
||||
|
||||
+46
-41
@@ -1272,7 +1272,6 @@ interface SomeChat {
|
||||
val apiId: Long
|
||||
val ready: Boolean
|
||||
val chatDeleted: Boolean
|
||||
val sendMsgEnabled: Boolean
|
||||
val incognito: Boolean
|
||||
fun featureEnabled(feature: ChatFeature): Boolean
|
||||
val timedMessagesTTL: Int?
|
||||
@@ -1296,19 +1295,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 userIsPending: Boolean get() = when(chatInfo) {
|
||||
is ChatInfo.Group -> chatInfo.groupInfo.membership.memberPending
|
||||
else -> false
|
||||
}
|
||||
|
||||
val unreadTag: Boolean get() = when (chatInfo.chatSettings?.enableNtfs) {
|
||||
All -> chatStats.unreadChat || chatStats.unreadCount > 0
|
||||
Mentions -> chatStats.unreadChat || chatStats.unreadMentions > 0
|
||||
@@ -1365,7 +1351,6 @@ 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 sendMsgEnabled get() = contact.sendMsgEnabled
|
||||
override val incognito get() = contact.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contact.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
|
||||
@@ -1390,7 +1375,6 @@ 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 sendMsgEnabled get() = groupInfo.sendMsgEnabled
|
||||
override val incognito get() = groupInfo.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = groupInfo.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
|
||||
@@ -1414,7 +1398,6 @@ 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 sendMsgEnabled get() = noteFolder.sendMsgEnabled
|
||||
override val incognito get() = noteFolder.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = noteFolder.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
|
||||
@@ -1438,7 +1421,6 @@ 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 sendMsgEnabled get() = contactRequest.sendMsgEnabled
|
||||
override val incognito get() = contactRequest.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contactRequest.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
|
||||
@@ -1462,7 +1444,6 @@ 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 sendMsgEnabled get() = contactConnection.sendMsgEnabled
|
||||
override val incognito get() = contactConnection.incognito
|
||||
override fun featureEnabled(feature: ChatFeature) = contactConnection.featureEnabled(feature)
|
||||
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
|
||||
@@ -1491,7 +1472,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
override val id get() = "?$apiId"
|
||||
override val ready get() = false
|
||||
override val chatDeleted get() = false
|
||||
override val sendMsgEnabled get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -1506,6 +1486,52 @@ sealed class ChatInfo: SomeChat, NamedChat {
|
||||
}
|
||||
}
|
||||
|
||||
val userCantSendReason: Pair<String, String?>?
|
||||
get() {
|
||||
when (this) {
|
||||
is Direct -> {
|
||||
// TODO [short links] this will have additional statuses for pending contact requests before they are accepted
|
||||
if (contact.nextSendGrpInv) return null
|
||||
if (!contact.active) return generalGetString(MR.strings.cant_send_message_contact_deleted) to null
|
||||
if (!contact.sndReady) return generalGetString(MR.strings.cant_send_message_contact_not_ready) to null
|
||||
if (contact.activeConn?.connectionStats?.ratchetSyncSendProhibited == true) return generalGetString(MR.strings.cant_send_message_contact_not_synchronized) to null
|
||||
if (contact.activeConn?.connDisabled == true) return generalGetString(MR.strings.cant_send_message_contact_disabled) to null
|
||||
return null
|
||||
}
|
||||
is Group -> {
|
||||
if (groupInfo.membership.memberActive) {
|
||||
if (groupChatScope == null) {
|
||||
if (groupInfo.membership.memberPending) {
|
||||
return generalGetString(MR.strings.reviewed_by_admins) to generalGetString(MR.strings.observer_cant_send_message_desc)
|
||||
}
|
||||
if (groupInfo.membership.memberRole == GroupMemberRole.Observer) {
|
||||
return generalGetString(MR.strings.observer_cant_send_message_title) to generalGetString(MR.strings.observer_cant_send_message_desc)
|
||||
}
|
||||
}
|
||||
return null
|
||||
} else {
|
||||
return when (groupInfo.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
|
||||
}
|
||||
}
|
||||
}
|
||||
is Local ->
|
||||
return null
|
||||
is ContactRequest ->
|
||||
return generalGetString(MR.strings.cant_send_message_generic) to null
|
||||
is ContactConnection ->
|
||||
return generalGetString(MR.strings.cant_send_message_generic) to null
|
||||
is InvalidJSON ->
|
||||
return generalGetString(MR.strings.cant_send_message_generic) to null
|
||||
}
|
||||
}
|
||||
|
||||
val sendMsgEnabled get() = userCantSendReason == null
|
||||
|
||||
fun groupChatScope(): GroupChatScope? = when (this) {
|
||||
is Group -> groupChatScope?.toChatScope()
|
||||
else -> null
|
||||
@@ -1538,16 +1564,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 && !groupInfo.membership.memberPending)
|
||||
|| groupChatScope != null
|
||||
is ChatInfo.Local -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val chatTags: List<Long>?
|
||||
get() = when (this) {
|
||||
is Direct -> contact.chatTags
|
||||
@@ -1624,13 +1640,6 @@ 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
|
||||
val nextSendGrpInv get() = contactGroupMemberId != null && !contactGrpInvSent
|
||||
override val incognito get() = contactConnIncognito
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
@@ -1865,7 +1874,6 @@ 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 incognito get() = membership.memberIncognito
|
||||
override fun featureEnabled(feature: ChatFeature) = when (feature) {
|
||||
ChatFeature.TimedMessages -> fullGroupPreferences.timedMessages.on
|
||||
@@ -2287,7 +2295,6 @@ class NoteFolder(
|
||||
override val apiId get() = noteFolderId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = true
|
||||
override val sendMsgEnabled get() = true
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -2323,7 +2330,6 @@ class UserContactRequest (
|
||||
override val apiId get() = contactRequestId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = true
|
||||
override val sendMsgEnabled get() = false
|
||||
override val incognito get() = false
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
@@ -2362,7 +2368,6 @@ class PendingContactConnection(
|
||||
override val apiId get() = pccConnId
|
||||
override val chatDeleted get() = false
|
||||
override val ready get() = false
|
||||
override val sendMsgEnabled get() = false
|
||||
override val incognito get() = customUserProfileId != null
|
||||
override fun featureEnabled(feature: ChatFeature) = false
|
||||
override val timedMessagesTTL: Int? get() = null
|
||||
|
||||
+1
-2
@@ -12,11 +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,
|
||||
userIsPending: Boolean,
|
||||
placeholder: String,
|
||||
showVoiceButton: Boolean,
|
||||
onMessageChange: (ComposeMessage) -> Unit,
|
||||
|
||||
+1
-3
@@ -99,13 +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,
|
||||
userIsPending = false,
|
||||
userCanSend = true,
|
||||
allowVoiceToContact = {},
|
||||
placeholder = "",
|
||||
sendMessage = { sendCommand() },
|
||||
|
||||
+1
-1
@@ -784,7 +784,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 = {
|
||||
|
||||
+4
-9
@@ -1010,10 +1010,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 userIsPending = rememberUpdatedState(chat.userIsPending)
|
||||
val userCantSendReason = rememberUpdatedState(chat.chatInfo.userCantSendReason)
|
||||
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
|
||||
|
||||
Column {
|
||||
@@ -1086,7 +1084,6 @@ fun ComposeView(
|
||||
val attachmentEnabled =
|
||||
!composeState.value.attachmentDisabled
|
||||
&& sendMsgEnabled.value
|
||||
&& userCanSend.value
|
||||
&& !isGroupAndProhibitedFiles
|
||||
&& !nextSendGrpInv.value
|
||||
IconButton(
|
||||
@@ -1132,8 +1129,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()
|
||||
}
|
||||
@@ -1189,14 +1186,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 = if (chatsCtx.secondaryContextFilter == null) userIsObserver.value else false,
|
||||
userIsPending = if (chatsCtx.secondaryContextFilter == null) userIsPending.value else false,
|
||||
userCanSend = userCanSend.value,
|
||||
sendButtonColor = sendButtonColor,
|
||||
timedMessageAllowed = timedMessageAllowed,
|
||||
customDisappearingMessageTimePref = chatModel.controller.appPrefs.customDisappearingMessageTime,
|
||||
|
||||
+31
-34
@@ -39,13 +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,
|
||||
userIsPending: Boolean,
|
||||
userCanSend: Boolean,
|
||||
sendButtonColor: Color = MaterialTheme.colors.primary,
|
||||
allowVoiceToContact: () -> Unit,
|
||||
timedMessageAllowed: Boolean = false,
|
||||
@@ -81,15 +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,
|
||||
userIsPending,
|
||||
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,13 +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,
|
||||
userIsPending = false,
|
||||
userCanSend = true,
|
||||
allowVoiceToContact = {},
|
||||
timedMessageAllowed = false,
|
||||
placeholder = "",
|
||||
@@ -613,13 +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,
|
||||
userIsPending = false,
|
||||
userCanSend = true,
|
||||
allowVoiceToContact = {},
|
||||
timedMessageAllowed = false,
|
||||
placeholder = "",
|
||||
@@ -650,13 +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,
|
||||
userIsPending = false,
|
||||
userCanSend = true,
|
||||
allowVoiceToContact = {},
|
||||
timedMessageAllowed = false,
|
||||
placeholder = "",
|
||||
|
||||
+2
-1
@@ -103,8 +103,9 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List<Contact> {
|
||||
.asSequence()
|
||||
.map { it.chatInfo }
|
||||
.filterIsInstance<ChatInfo.Direct>()
|
||||
.filter { it.sendMsgEnabled }
|
||||
.map { it.contact }
|
||||
.filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
|
||||
.filter { c -> !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
|
||||
}
|
||||
.sortedBy { it.displayName.lowercase() }
|
||||
.toList()
|
||||
|
||||
@@ -491,10 +491,6 @@
|
||||
<string name="image_decoding_exception_title">Decoding error</string>
|
||||
<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="reviewed_by_admins">reviewed by admins</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>
|
||||
@@ -516,6 +512,21 @@
|
||||
<string name="report_sent_alert_title">Report sent to moderators</string>
|
||||
<string name="report_sent_alert_msg_view_in_support_chat">You can view your reports in Chat with admins.</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>
|
||||
<string name="you_are_observer">you are observer</string>
|
||||
<string name="reviewed_by_admins">reviewed by admins</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>
|
||||
|
||||
+6
-9
@@ -44,11 +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,
|
||||
userIsPending: Boolean,
|
||||
placeholder: String,
|
||||
showVoiceButton: Boolean,
|
||||
onMessageChange: (ComposeMessage) -> Unit,
|
||||
@@ -204,18 +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 (userIsPending) {
|
||||
ComposeOverlay(MR.strings.reviewed_by_admins, 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)
|
||||
|
||||
Reference in New Issue
Block a user