diff --git a/README.md b/README.md
index 40d552b84d..554c6068d9 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
-[
](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) [
](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) [
](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
+[
](http://simplex.chat/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html) [
](https://www.privacyguides.org/en/real-time-communication/#simplex-chat) [
](https://www.whonix.org/wiki/Chat#Recommendation) [
](https://www.kuketz-blog.de/simplex-eindruecke-vom-messenger-ohne-identifier/)
## Welcome to SimpleX Chat!
@@ -110,6 +110,15 @@ After you connect, you can [verify connection security code](./blog/20230103-sim
Read about the app features and settings in the new [User guide](./docs/guide/README.md).
+## Contribute
+
+We would love to have you join the development! You can help us with:
+
+- [share the color theme](./docs/THEMES.md) you use in Android app!
+- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
+- contributing to SimpleX Chat knowledge-base.
+- developing features - please connect to us via chat so we can help you get started.
+
## Help translating SimpleX Chat
Thanks to our users and [Weblate](https://hosted.weblate.org/engage/simplex-chat/), SimpleX Chat apps, website and documents are translated to many other languages.
@@ -141,15 +150,6 @@ Join our translators to help SimpleX grow!
Languages in progress: Arabic, Japanese, Korean, Portuguese and [others](https://hosted.weblate.org/projects/simplex-chat/#languages). We will be adding more languages as some of the already added are completed – please suggest new languages, review the [translation guide](./docs/TRANSLATIONS.md) and get in touch with us!
-## Contribute
-
-We would love to have you join the development! You can help us with:
-
-- [share the color theme](./docs/THEMES.md) you use in Android app!
-- writing a tutorial or recipes about hosting servers, chat bot automations, etc.
-- contributing to SimpleX Chat knowledge-base.
-- developing features - please connect to us via chat so we can help you get started.
-
## Please support us with your donations
Huge thank you to everybody who donated to SimpleX Chat!
diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift
index 2ddaf1d2af..3bf4cb7b56 100644
--- a/apps/ios/Shared/Model/AppAPITypes.swift
+++ b/apps/ios/Shared/Model/AppAPITypes.swift
@@ -39,9 +39,9 @@ enum ChatCommand: ChatCmdProtocol {
case apiGetSettings(settings: AppSettings)
case apiGetChatTags(userId: Int64)
case apiGetChats(userId: Int64)
- case apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag?, pagination: ChatPagination, search: String)
- case apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64)
- case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
+ case apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String)
+ case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64)
+ case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
case apiCreateChatTag(tag: ChatTagData)
case apiSetChatTags(type: ChatType, id: Int64, tagIds: [Int64])
case apiDeleteChatTag(tagId: Int64)
@@ -49,15 +49,15 @@ enum ChatCommand: ChatCmdProtocol {
case apiReorderChatTags(tagIds: [Int64])
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
case apiReportMessage(groupId: Int64, chatItemId: Int64, reportReason: ReportReason, reportText: String)
- case apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool)
- case apiDeleteChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode)
+ case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool)
+ case apiDeleteChatItem(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode)
case apiDeleteMemberChatItem(groupId: Int64, itemIds: [Int64])
case apiArchiveReceivedReports(groupId: Int64)
case apiDeleteReceivedReports(groupId: Int64, itemIds: [Int64], mode: CIDeleteMode)
- case apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction)
+ case apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction)
case apiGetReactionMembers(userId: Int64, groupId: Int64, itemId: Int64, reaction: MsgReaction)
- case apiPlanForwardChatItems(fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64])
- case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?)
+ case apiPlanForwardChatItems(toChatType: ChatType, toChatId: Int64, itemIds: [Int64])
+ case apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?)
case apiGetNtfToken
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationsMode)
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
@@ -68,8 +68,6 @@ enum ChatCommand: ChatCmdProtocol {
case apiNewGroup(userId: Int64, incognito: Bool, groupProfile: GroupProfile)
case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole)
case apiJoinGroup(groupId: Int64)
- case apiAcceptMember(groupId: Int64, groupMemberId: Int64, memberRole: GroupMemberRole)
- case apiDeleteMemberSupportChat(groupId: Int64, groupMemberId: Int64)
case apiMembersRole(groupId: Int64, memberIds: [Int64], memberRole: GroupMemberRole)
case apiBlockMembersForAll(groupId: Int64, memberIds: [Int64], blocked: Bool)
case apiRemoveMembers(groupId: Int64, memberIds: [Int64], withMessages: Bool)
@@ -149,8 +147,8 @@ enum ChatCommand: ChatCmdProtocol {
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
// WebRTC calls /
case apiGetNetworkStatuses
- case apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?)
- case apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64])
+ case apiChatRead(type: ChatType, id: Int64)
+ case apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64])
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?)
case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?)
@@ -211,16 +209,15 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))"
case let .apiGetChatTags(userId): return "/_get tags \(userId)"
case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on"
- case let .apiGetChat(chatId, scope, contentTag, pagination, search):
- let tag = contentTag != nil ? " content=\(contentTag!.rawValue)" : ""
- return "/_get chat \(chatId)\(scopeRef(scope: scope))\(tag) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)")
- case let .apiGetChatItemInfo(type, id, scope, itemId): return "/_get item info \(ref(type, id, scope: scope)) \(itemId)"
- case let .apiSendMessages(type, id, scope, live, ttl, composedMessages):
+ case let .apiGetChat(chatId, pagination, search): return "/_get chat \(chatId) \(pagination.cmdString)" +
+ (search == "" ? "" : " search=\(search)")
+ case let .apiGetChatItemInfo(type, id, itemId): return "/_get item info \(ref(type, id)) \(itemId)"
+ case let .apiSendMessages(type, id, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
- return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
+ return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
case let .apiCreateChatTag(tag): return "/_create tag \(encodeJSON(tag))"
- case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id, scope: nil)) \(tagIds.map({ "\($0)" }).joined(separator: ","))"
+ case let .apiSetChatTags(type, id, tagIds): return "/_tags \(ref(type, id)) \(tagIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiDeleteChatTag(tagId): return "/_delete tag \(tagId)"
case let .apiUpdateChatTag(tagId, tagData): return "/_update tag \(tagId) \(encodeJSON(tagData))"
case let .apiReorderChatTags(tagIds): return "/_reorder tags \(tagIds.map({ "\($0)" }).joined(separator: ","))"
@@ -229,17 +226,17 @@ enum ChatCommand: ChatCmdProtocol {
return "/_create *\(noteFolderId) json \(msgs)"
case let .apiReportMessage(groupId, chatItemId, reportReason, reportText):
return "/_report #\(groupId) \(chatItemId) reason=\(reportReason) \(reportText)"
- case let .apiUpdateChatItem(type, id, scope, itemId, um, live): return "/_update item \(ref(type, id, scope: scope)) \(itemId) live=\(onOff(live)) \(um.cmdString)"
- case let .apiDeleteChatItem(type, id, scope, itemIds, mode): return "/_delete item \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
+ case let .apiUpdateChatItem(type, id, itemId, um, live): return "/_update item \(ref(type, id)) \(itemId) live=\(onOff(live)) \(um.cmdString)"
+ case let .apiDeleteChatItem(type, id, itemIds, mode): return "/_delete item \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
case let .apiDeleteMemberChatItem(groupId, itemIds): return "/_delete member item #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
case let .apiArchiveReceivedReports(groupId): return "/_archive reports #\(groupId)"
case let .apiDeleteReceivedReports(groupId, itemIds, mode): return "/_delete reports #\(groupId) \(itemIds.map({ "\($0)" }).joined(separator: ",")) \(mode.rawValue)"
- case let .apiChatItemReaction(type, id, scope, itemId, add, reaction): return "/_reaction \(ref(type, id, scope: scope)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
+ case let .apiChatItemReaction(type, id, itemId, add, reaction): return "/_reaction \(ref(type, id)) \(itemId) \(onOff(add)) \(encodeJSON(reaction))"
case let .apiGetReactionMembers(userId, groupId, itemId, reaction): return "/_reaction members \(userId) #\(groupId) \(itemId) \(encodeJSON(reaction))"
- case let .apiPlanForwardChatItems(type, id, scope, itemIds): return "/_forward plan \(ref(type, id, scope: scope)) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
- case let .apiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl):
+ case let .apiPlanForwardChatItems(type, id, itemIds): return "/_forward plan \(ref(type, id)) \(itemIds.map({ "\($0)" }).joined(separator: ","))"
+ case let .apiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl):
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
- return "/_forward \(ref(toChatType, toChatId, scope: toScope)) \(ref(fromChatType, fromChatId, scope: fromScope)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)"
+ return "/_forward \(ref(toChatType, toChatId)) \(ref(fromChatType, fromChatId)) \(itemIds.map({ "\($0)" }).joined(separator: ",")) ttl=\(ttlStr)"
case .apiGetNtfToken: return "/_ntf get "
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)"
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
@@ -250,8 +247,6 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiNewGroup(userId, incognito, groupProfile): return "/_group \(userId) incognito=\(onOff(incognito)) \(encodeJSON(groupProfile))"
case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)"
case let .apiJoinGroup(groupId): return "/_join #\(groupId)"
- case let .apiAcceptMember(groupId, groupMemberId, memberRole): return "/_accept member #\(groupId) \(groupMemberId) \(memberRole.rawValue)"
- case let .apiDeleteMemberSupportChat(groupId, groupMemberId): return "/_delete member chat #\(groupId) \(groupMemberId)"
case let .apiMembersRole(groupId, memberIds, memberRole): return "/_member role #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) \(memberRole.rawValue)"
case let .apiBlockMembersForAll(groupId, memberIds, blocked): return "/_block #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) blocked=\(onOff(blocked))"
case let .apiRemoveMembers(groupId, memberIds, withMessages): return "/_remove #\(groupId) \(memberIds.map({ "\($0)" }).joined(separator: ",")) messages=\(onOff(withMessages))"
@@ -275,13 +270,13 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiAcceptConditions(conditionsId, operatorIds): return "/_accept_conditions \(conditionsId) \(joinedIds(operatorIds))"
case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))"
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
- case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id, scope: nil)) \(chatItemTTLStr(seconds: seconds))"
+ case let .apiSetChatTTL(userId, type, id, seconds): return "/_ttl \(userId) \(ref(type, id)) \(chatItemTTLStr(seconds: seconds))"
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
case .apiGetNetworkConfig: return "/network"
case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))"
case .reconnectAllServers: return "/reconnect"
case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)"
- case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id, scope: nil)) \(encodeJSON(chatSettings))"
+ case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)"
@@ -313,8 +308,8 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiConnectPlan(userId, connLink): return "/_connect plan \(userId) \(connLink)"
case let .apiConnect(userId, incognito, connLink): return "/_connect \(userId) incognito=\(onOff(incognito)) \(connLink.connFullLink) \(connLink.connShortLink ?? "")"
case let .apiConnectContactViaAddress(userId, incognito, contactId): return "/_connect contact \(userId) incognito=\(onOff(incognito)) \(contactId)"
- case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id, scope: nil)) \(chatDeleteMode.cmdString)"
- case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id, scope: nil))"
+ case let .apiDeleteChat(type, id, chatDeleteMode): return "/_delete \(ref(type, id)) \(chatDeleteMode.cmdString)"
+ case let .apiClearChat(type, id): return "/_clear chat \(ref(type, id))"
case let .apiListContacts(userId): return "/_contacts \(userId)"
case let .apiUpdateProfile(userId, profile): return "/_profile \(userId) \(encodeJSON(profile))"
case let .apiSetContactPrefs(contactId, preferences): return "/_set prefs @\(contactId) \(encodeJSON(preferences))"
@@ -339,9 +334,9 @@ enum ChatCommand: ChatCmdProtocol {
case .apiGetCallInvitations: return "/_call get"
case let .apiCallStatus(contact, callStatus): return "/_call status @\(contact.apiId) \(callStatus.rawValue)"
case .apiGetNetworkStatuses: return "/_network_statuses"
- case let .apiChatRead(type, id, scope): return "/_read chat \(ref(type, id, scope: scope))"
- case let .apiChatItemsRead(type, id, scope, itemIds): return "/_read chat items \(ref(type, id, scope: scope)) \(joinedIds(itemIds))"
- case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id, scope: nil)) \(onOff(unreadChat))"
+ case let .apiChatRead(type, id): return "/_read chat \(ref(type, id))"
+ case let .apiChatItemsRead(type, id, itemIds): return "/_read chat items \(ref(type, id)) \(joinedIds(itemIds))"
+ case let .apiChatUnread(type, id, unreadChat): return "/_unread chat \(ref(type, id)) \(onOff(unreadChat))"
case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))"
case let .cancelFile(fileId): return "/fcancel \(fileId)"
@@ -426,8 +421,6 @@ enum ChatCommand: ChatCmdProtocol {
case .apiNewGroup: return "apiNewGroup"
case .apiAddMember: return "apiAddMember"
case .apiJoinGroup: return "apiJoinGroup"
- case .apiAcceptMember: return "apiAcceptMember"
- case .apiDeleteMemberSupportChat: return "apiDeleteMemberSupportChat"
case .apiMembersRole: return "apiMembersRole"
case .apiBlockMembersForAll: return "apiBlockMembersForAll"
case .apiRemoveMembers: return "apiRemoveMembers"
@@ -530,20 +523,8 @@ enum ChatCommand: ChatCmdProtocol {
}
}
- func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String {
- "\(type.rawValue)\(id)\(scopeRef(scope: scope))"
- }
-
- func scopeRef(scope: GroupChatScope?) -> String {
- switch (scope) {
- case .none: ""
- case let .memberSupport(groupMemberId_):
- if let groupMemberId = groupMemberId_ {
- "(_support:\(groupMemberId))"
- } else {
- "(_support)"
- }
- }
+ func ref(_ type: ChatType, _ id: Int64) -> String {
+ "\(type.rawValue)\(id)"
}
func joinedIds(_ ids: [Int64]) -> String {
@@ -730,7 +711,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case contactDeleted(user: UserRef, contact: Contact)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case groupDeletedUser(user: UserRef, groupInfo: GroupInfo)
- case itemsReadForChat(user: UserRef, chatInfo: ChatInfo)
case chatCleared(user: UserRef, chatInfo: ChatInfo)
case userProfileNoChange(user: User)
case userProfileUpdated(user: User, fromProfile: Profile, toProfile: Profile, updateSummary: UserProfileUpdateSummary)
@@ -769,7 +749,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case .contactDeleted: "contactDeleted"
case .contactConnectionDeleted: "contactConnectionDeleted"
case .groupDeletedUser: "groupDeletedUser"
- case .itemsReadForChat: "itemsReadForChat"
case .chatCleared: "chatCleared"
case .userProfileNoChange: "userProfileNoChange"
case .userProfileUpdated: "userProfileUpdated"
@@ -802,7 +781,6 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case let .contactDeleted(u, contact): return withUser(u, String(describing: contact))
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .groupDeletedUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
- case let .itemsReadForChat(u, chatInfo): return withUser(u, String(describing: chatInfo))
case let .chatCleared(u, chatInfo): return withUser(u, String(describing: chatInfo))
case .userProfileNoChange: return noDetails
case let .userProfileUpdated(u, _, toProfile, _): return withUser(u, String(describing: toProfile))
@@ -853,8 +831,6 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case userDeletedMembers(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], withMessages: Bool)
case leftMemberUser(user: UserRef, groupInfo: GroupInfo)
case groupMembers(user: UserRef, group: SimpleXChat.Group)
- case memberAccepted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
- case memberSupportChatDeleted(user: UserRef, groupInfo: GroupInfo, member: GroupMember)
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
case groupUpdated(user: UserRef, toGroup: GroupInfo)
@@ -903,8 +879,6 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case .userDeletedMembers: "userDeletedMembers"
case .leftMemberUser: "leftMemberUser"
case .groupMembers: "groupMembers"
- case .memberAccepted: "memberAccepted"
- case .memberSupportChatDeleted: "memberSupportChatDeleted"
case .membersRoleUser: "membersRoleUser"
case .membersBlockedForAllUser: "membersBlockedForAllUser"
case .groupUpdated: "groupUpdated"
@@ -949,8 +923,6 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case let .userDeletedMembers(u, groupInfo, members, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\nwithMessages: \(withMessages)")
case let .leftMemberUser(u, groupInfo): return withUser(u, String(describing: groupInfo))
case let .groupMembers(u, group): return withUser(u, String(describing: group))
- case let .memberAccepted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
- case let .memberSupportChatDeleted(u, groupInfo, member): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)")
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
@@ -1016,7 +988,6 @@ enum ChatEvent: Decodable, ChatAPIResult {
case groupLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember)
case businessLinkConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, fromContact: Contact)
case joinedGroupMemberConnecting(user: UserRef, groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember)
- case memberAcceptedByOther(user: UserRef, groupInfo: GroupInfo, acceptingMember: GroupMember, member: GroupMember)
case memberRole(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole)
case memberBlockedForAll(user: UserRef, groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, blocked: Bool)
case deletedMemberUser(user: UserRef, groupInfo: GroupInfo, member: GroupMember, withMessages: Bool)
@@ -1093,7 +1064,6 @@ enum ChatEvent: Decodable, ChatAPIResult {
case .groupLinkConnecting: "groupLinkConnecting"
case .businessLinkConnecting: "businessLinkConnecting"
case .joinedGroupMemberConnecting: "joinedGroupMemberConnecting"
- case .memberAcceptedByOther: "memberAcceptedByOther"
case .memberRole: "memberRole"
case .memberBlockedForAll: "memberBlockedForAll"
case .deletedMemberUser: "deletedMemberUser"
@@ -1174,7 +1144,6 @@ enum ChatEvent: Decodable, ChatAPIResult {
case let .groupLinkConnecting(u, groupInfo, hostMember): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))")
case let .businessLinkConnecting(u, groupInfo, hostMember, fromContact): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(String(describing: hostMember))\nfromContact: \(String(describing: fromContact))")
case let .joinedGroupMemberConnecting(u, groupInfo, hostMember, member): return withUser(u, "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)")
- case let .memberAcceptedByOther(u, groupInfo, acceptingMember, member): return withUser(u, "groupInfo: \(groupInfo)\nacceptingMember: \(acceptingMember)\nmember: \(member)")
case let .memberRole(u, groupInfo, byMember, member, fromRole, toRole): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)")
case let .memberBlockedForAll(u, groupInfo, byMember, member, blocked): return withUser(u, "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nblocked: \(blocked)")
case let .deletedMemberUser(u, groupInfo, member, withMessages): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(member)\nwithMessages: \(withMessages)")
diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift
index f8cb022095..9b9fda0397 100644
--- a/apps/ios/Shared/Model/ChatModel.swift
+++ b/apps/ios/Shared/Model/ChatModel.swift
@@ -52,26 +52,8 @@ private func addTermItem(_ items: inout [TerminalItem], _ item: TerminalItem) {
items.append(item)
}
-// analogue for SecondaryContextFilter in Kotlin
-enum SecondaryItemsModelFilter {
- case groupChatScopeContext(groupScopeInfo: GroupChatScopeInfo)
- case msgContentTagContext(contentTag: MsgContentTag)
-
- func descr() -> String {
- switch self {
- case let .groupChatScopeContext(groupScopeInfo):
- return "groupChatScopeContext \(groupScopeInfo.toChatScope())"
- case let .msgContentTagContext(contentTag):
- return "msgContentTagContext \(contentTag.rawValue)"
- }
- }
-}
-
-// analogue for ChatsContext in Kotlin
class ItemsModel: ObservableObject {
- static let shared = ItemsModel(secondaryIMFilter: nil)
- public var secondaryIMFilter: SecondaryItemsModelFilter?
- public var preloadState = PreloadState()
+ static let shared = ItemsModel()
private let publisher = ObservableObjectPublisher()
private var bag = Set()
var reversedChatItems: [ChatItem] = [] {
@@ -95,20 +77,13 @@ class ItemsModel: ObservableObject {
chatState.splits.isEmpty || chatState.splits.first != reversedChatItems.first?.id
}
- init(secondaryIMFilter: SecondaryItemsModelFilter? = nil) {
- self.secondaryIMFilter = secondaryIMFilter
+ init() {
publisher
.throttle(for: 0.2, scheduler: DispatchQueue.main, latest: true)
.sink { self.objectWillChange.send() }
.store(in: &bag)
}
- static func loadSecondaryChat(_ chatId: ChatId, chatFilter: SecondaryItemsModelFilter, willNavigate: @escaping () -> Void = {}) {
- let im = ItemsModel(secondaryIMFilter: chatFilter)
- ChatModel.shared.secondaryIM = im
- im.loadOpenChat(chatId, willNavigate: willNavigate)
- }
-
func loadOpenChat(_ chatId: ChatId, willNavigate: @escaping () -> Void = {}) {
navigationTimeoutTask?.cancel()
loadChatTask?.cancel()
@@ -124,7 +99,7 @@ class ItemsModel: ObservableObject {
loadChatTask = Task {
await MainActor.run { self.isLoading = true }
// try? await Task.sleep(nanoseconds: 1000_000000)
- await loadChat(chatId: chatId, im: self)
+ await loadChat(chatId: chatId)
if !Task.isCancelled {
await MainActor.run {
self.isLoading = false
@@ -139,7 +114,7 @@ class ItemsModel: ObservableObject {
loadChatTask?.cancel()
loadChatTask = Task {
// try? await Task.sleep(nanoseconds: 1000_000000)
- await loadChat(chatId: chatId, im: self, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil)
+ await loadChat(chatId: chatId, openAroundItemId: openAroundItemId, clearItems: openAroundItemId == nil)
if !Task.isCancelled {
await MainActor.run {
if openAroundItemId == nil {
@@ -149,34 +124,6 @@ class ItemsModel: ObservableObject {
}
}
}
-
- public var contentTag: MsgContentTag? {
- switch secondaryIMFilter {
- case nil: nil
- case .groupChatScopeContext: nil
- case let .msgContentTagContext(contentTag): contentTag
- }
- }
-
- public var groupScopeInfo: GroupChatScopeInfo? {
- switch secondaryIMFilter {
- case nil: nil
- case let .groupChatScopeContext(scopeInfo): scopeInfo
- case .msgContentTagContext: nil
- }
- }
-}
-
-class PreloadState {
- var prevFirstVisible: Int64 = Int64.min
- var prevItemsCount: Int = 0
- var preloading: Bool = false
-
- func clear() {
- prevFirstVisible = Int64.min
- prevItemsCount = 0
- preloading = false
- }
}
class ChatTagsModel: ObservableObject {
@@ -340,6 +287,7 @@ final class ChatModel: ObservableObject {
// current chat
@Published var chatId: String?
@Published var openAroundItemId: ChatItem.ID? = nil
+ var chatItemStatuses: Dictionary = [:]
@Published var chatToTop: String?
@Published var groupMembers: [GMember] = []
@Published var groupMembersIndexes: Dictionary = [:] // groupMemberId to index in groupMembers list
@@ -388,10 +336,6 @@ final class ChatModel: ObservableObject {
let im = ItemsModel.shared
- // ItemsModel for secondary chat view (such as support scope chat), as opposed to ItemsModel.shared used for primary chat
- @Published var secondaryIM: ItemsModel? = nil
- @Published var secondaryPendingInviteeChatOpened = false
-
static var ok: Bool { ChatModel.shared.chatDbStatus == .ok }
let ntfEnableLocal = true
@@ -449,7 +393,7 @@ final class ChatModel: ObservableObject {
func getGroupChat(_ groupId: Int64) -> Chat? {
chats.first { chat in
- if case let .group(groupInfo, _) = chat.chatInfo {
+ if case let .group(groupInfo) = chat.chatInfo {
return groupInfo.groupId == groupId
} else {
return false
@@ -502,11 +446,7 @@ final class ChatModel: ObservableObject {
func updateChatInfo(_ cInfo: ChatInfo) {
if let i = getChatIndex(cInfo.id) {
- if case let .group(groupInfo, groupChatScope) = cInfo, groupChatScope != nil {
- chats[i].chatInfo = .group(groupInfo: groupInfo, groupChatScope: nil)
- } else {
- chats[i].chatInfo = cInfo
- }
+ chats[i].chatInfo = cInfo
chats[i].created = Date.now
}
}
@@ -528,7 +468,7 @@ final class ChatModel: ObservableObject {
}
func updateGroup(_ groupInfo: GroupInfo) {
- updateChat(.group(groupInfo: groupInfo, groupChatScope: nil))
+ updateChat(.group(groupInfo: groupInfo))
}
private func updateChat(_ cInfo: ChatInfo, addMissing: Bool = true) {
@@ -571,112 +511,77 @@ final class ChatModel: ObservableObject {
// }
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
- // updates membersRequireAttention
- updateChatInfo(cInfo)
// mark chat non deleted
if case let .direct(contact) = cInfo, contact.chatDeleted {
var updatedContact = contact
updatedContact.chatDeleted = false
updateContact(updatedContact)
}
- // update chat list
+ // update previews
if let i = getChatIndex(cInfo.id) {
- // update preview
- if cInfo.groupChatScope() == nil || cInfo.groupInfo?.membership.memberPending ?? false {
- chats[i].chatItems = switch cInfo {
- case .group:
- if let currentPreviewItem = chats[i].chatItems.first {
- if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs {
- [cItem]
- } else {
- [currentPreviewItem]
- }
- } else {
+ chats[i].chatItems = switch cInfo {
+ case .group:
+ if let currentPreviewItem = chats[i].chatItems.first {
+ if cItem.meta.itemTs >= currentPreviewItem.meta.itemTs {
[cItem]
+ } else {
+ [currentPreviewItem]
}
- default:
+ } else {
[cItem]
}
- if case .rcvNew = cItem.meta.itemStatus {
- unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0)
- }
+ default:
+ [cItem]
+ }
+ if case .rcvNew = cItem.meta.itemStatus {
+ unreadCollector.changeUnreadCounter(cInfo.id, by: 1, unreadMentions: cItem.meta.userMention ? 1 : 0)
}
- // pop chat
popChatCollector.throttlePopChat(cInfo.id, currentPosition: i)
} else {
- if cInfo.groupChatScope() == nil {
- addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
- } else {
- addChat(Chat(chatInfo: cInfo, chatItems: []))
- }
+ addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
}
- // add to current scope
- if let ciIM = getCIItemsModel(cInfo, cItem) {
- _ = _upsertChatItem(ciIM, cInfo, cItem)
- }
- }
-
- func getCIItemsModel(_ cInfo: ChatInfo, _ ci: ChatItem) -> ItemsModel? {
- let cInfoScope = cInfo.groupChatScope()
- if let cInfoScope = cInfoScope {
- switch cInfoScope {
- case .memberSupport:
- switch secondaryIM?.secondaryIMFilter {
- case .none:
- return nil
- case let .groupChatScopeContext(groupScopeInfo):
- return (cInfo.id == chatId && sameChatScope(cInfoScope, groupScopeInfo.toChatScope())) ? secondaryIM : nil
- case let .msgContentTagContext(contentTag):
- return (cInfo.id == chatId && ci.isReport && contentTag == .report) ? secondaryIM : nil
- }
- }
- } else {
- return cInfo.id == chatId ? im : nil
+ // add to current chat
+ if chatId == cInfo.id {
+ _ = _upsertChatItem(cInfo, cItem)
}
}
func upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
- // update chat list
- var itemAdded: Bool = false
- if cInfo.groupChatScope() == nil {
- if let chat = getChat(cInfo.id) {
- if let pItem = chat.chatItems.last {
- if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
- chat.chatItems = [cItem]
- }
- } else {
+ // update previews
+ var res: Bool
+ if let chat = getChat(cInfo.id) {
+ if let pItem = chat.chatItems.last {
+ if pItem.id == cItem.id || (chatId == cInfo.id && im.reversedChatItems.first(where: { $0.id == cItem.id }) == nil) {
chat.chatItems = [cItem]
}
} else {
- addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
- itemAdded = true
- }
- if cItem.isDeletedContent || cItem.meta.itemDeleted != nil {
- VoiceItemState.stopVoiceInChatView(cInfo, cItem)
+ chat.chatItems = [cItem]
}
+ res = false
+ } else {
+ addChat(Chat(chatInfo: cInfo, chatItems: [cItem]))
+ res = true
}
- // update current scope
- if let ciIM = getCIItemsModel(cInfo, cItem) {
- itemAdded = _upsertChatItem(ciIM, cInfo, cItem)
+ if cItem.isDeletedContent || cItem.meta.itemDeleted != nil {
+ VoiceItemState.stopVoiceInChatView(cInfo, cItem)
}
- return itemAdded
+ // update current chat
+ return chatId == cInfo.id ? _upsertChatItem(cInfo, cItem) : res
}
- private func _upsertChatItem(_ ciIM: ItemsModel, _ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
- if let i = getChatItemIndex(ciIM, cItem) {
- let oldStatus = ciIM.reversedChatItems[i].meta.itemStatus
- let newStatus = cItem.meta.itemStatus
- var ci = cItem
- if shouldKeepOldSndCIStatus(oldStatus: oldStatus, newStatus: newStatus) {
- ci.meta.itemStatus = oldStatus
- }
- _updateChatItem(ciIM: ciIM, at: i, with: ci)
- ChatItemDummyModel.shared.sendUpdate() // TODO [knocking] review what's this
+ private func _upsertChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) -> Bool {
+ if let i = getChatItemIndex(cItem) {
+ _updateChatItem(at: i, with: cItem)
+ ChatItemDummyModel.shared.sendUpdate()
return false
} else {
- ciIM.reversedChatItems.insert(cItem, at: hasLiveDummy ? 1 : 0)
- ciIM.chatState.itemAdded((cItem.id, cItem.isRcvNew), hasLiveDummy ? 1 : 0)
- ciIM.itemAdded = true
+ var ci = cItem
+ if let status = chatItemStatuses.removeValue(forKey: ci.id), case .sndNew = ci.meta.itemStatus {
+ ci.meta.itemStatus = status
+ }
+ im.reversedChatItems.insert(ci, at: hasLiveDummy ? 1 : 0)
+ im.chatState.itemAdded((ci.id, ci.isRcvNew), hasLiveDummy ? 1 : 0)
+ im.itemAdded = true
ChatItemDummyModel.shared.sendUpdate()
return true
}
@@ -690,42 +595,40 @@ final class ChatModel: ObservableObject {
}
func updateChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem, status: CIStatus? = nil) {
- if let ciIM = getCIItemsModel(cInfo, cItem),
- let i = getChatItemIndex(ciIM, cItem) {
+ if chatId == cInfo.id, let i = getChatItemIndex(cItem) {
withConditionalAnimation {
- _updateChatItem(ciIM: ciIM, at: i, with: cItem)
+ _updateChatItem(at: i, with: cItem)
}
+ } else if let status = status {
+ chatItemStatuses.updateValue(status, forKey: cItem.id)
}
}
- private func _updateChatItem(ciIM: ItemsModel, at i: Int, with cItem: ChatItem) {
- ciIM.reversedChatItems[i] = cItem
- ciIM.reversedChatItems[i].viewTimestamp = .now
+ private func _updateChatItem(at i: Int, with cItem: ChatItem) {
+ im.reversedChatItems[i] = cItem
+ im.reversedChatItems[i].viewTimestamp = .now
}
- func getChatItemIndex(_ ciIM: ItemsModel, _ cItem: ChatItem) -> Int? {
- ciIM.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
+ func getChatItemIndex(_ cItem: ChatItem) -> Int? {
+ im.reversedChatItems.firstIndex(where: { $0.id == cItem.id })
}
func removeChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
- // update chat list
- if cInfo.groupChatScope() == nil {
- if cItem.isRcvNew {
- unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0)
- }
- // update previews
- if let chat = getChat(cInfo.id) {
- if let pItem = chat.chatItems.last, pItem.id == cItem.id {
- chat.chatItems = [ChatItem.deletedItemDummy()]
- }
+ if cItem.isRcvNew {
+ unreadCollector.changeUnreadCounter(cInfo.id, by: -1, unreadMentions: cItem.meta.userMention ? -1 : 0)
+ }
+ // update previews
+ if let chat = getChat(cInfo.id) {
+ if let pItem = chat.chatItems.last, pItem.id == cItem.id {
+ chat.chatItems = [ChatItem.deletedItemDummy()]
}
}
- // remove from current scope
- if let ciIM = getCIItemsModel(cInfo, cItem) {
- if let i = getChatItemIndex(ciIM, cItem) {
+ // remove from current chat
+ if chatId == cInfo.id {
+ if let i = getChatItemIndex(cItem) {
withAnimation {
- let item = ciIM.reversedChatItems.remove(at: i)
- ciIM.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed())
+ let item = im.reversedChatItems.remove(at: i)
+ im.chatState.itemsRemoved([(item.id, i, item.isRcvNew)], im.reversedChatItems.reversed())
}
}
}
@@ -741,7 +644,7 @@ final class ChatModel: ObservableObject {
if chatId == groupInfo.id {
for i in 0.. = []
var i = 0
var ids = Set(itemIds)
- while i < chatIM.reversedChatItems.count && !ids.isEmpty {
- let item = chatIM.reversedChatItems[i]
+ while i < im.reversedChatItems.count && !ids.isEmpty {
+ let item = im.reversedChatItems[i]
if ids.contains(item.id) && item.isRcvNew {
- markChatItemRead_(chatIM, i)
+ markChatItemRead_(i)
unreadItemIds.insert(item.id)
ids.remove(item.id)
}
i += 1
}
- chatIM.chatState.itemsRead(unreadItemIds, chatIM.reversedChatItems.reversed())
+ im.chatState.itemsRead(unreadItemIds, im.reversedChatItems.reversed())
}
self.unreadCollector.changeUnreadCounter(cInfo.id, by: -itemIds.count, unreadMentions: -mentionsRead)
}
@@ -984,13 +888,13 @@ final class ChatModel: ObservableObject {
}
}
- private func markChatItemRead_(_ chatIM: ItemsModel, _ i: Int) {
- let meta = chatIM.reversedChatItems[i].meta
+ private func markChatItemRead_(_ i: Int) {
+ let meta = im.reversedChatItems[i].meta
if case .rcvNew = meta.itemStatus {
- chatIM.reversedChatItems[i].meta.itemStatus = .rcvRead
- chatIM.reversedChatItems[i].viewTimestamp = .now
+ im.reversedChatItems[i].meta.itemStatus = .rcvRead
+ im.reversedChatItems[i].viewTimestamp = .now
if meta.itemLive != true, let ttl = meta.itemTimed?.ttl {
- chatIM.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
+ im.reversedChatItems[i].meta.itemTimed?.deleteAt = .now + TimeInterval(ttl)
}
}
}
@@ -1069,7 +973,7 @@ final class ChatModel: ObservableObject {
var count = 0
var ns: [String] = []
if let ciCategory = chatItem.mergeCategory,
- var i = getChatItemIndex(im, chatItem) { // TODO [knocking] review: use getCIItemsModel?
+ var i = getChatItemIndex(chatItem) {
while i < im.reversedChatItems.count {
let ci = im.reversedChatItems[i]
if ci.mergeCategory != ciCategory { break }
@@ -1085,7 +989,7 @@ final class ChatModel: ObservableObject {
// returns the index of the passed item and the next item (it has smaller index)
func getNextChatItem(_ ci: ChatItem) -> (Int?, ChatItem?) {
- if let i = getChatItemIndex(im, ci) { // TODO [knocking] review: use getCIItemsModel?
+ if let i = getChatItemIndex(ci) {
(i, i > 0 ? im.reversedChatItems[i - 1] : nil)
} else {
(nil, nil)
@@ -1196,7 +1100,7 @@ final class ChatModel: ObservableObject {
func removeWallpaperFilesFromChat(_ chat: Chat) {
if case let .direct(contact) = chat.chatInfo {
removeWallpaperFilesFromTheme(contact.uiThemes)
- } else if case let .group(groupInfo, _) = chat.chatInfo {
+ } else if case let .group(groupInfo) = chat.chatInfo {
removeWallpaperFilesFromTheme(groupInfo.uiThemes)
}
}
@@ -1260,18 +1164,6 @@ final class Chat: ObservableObject, Identifiable, ChatLike {
var viewId: String { get { "\(chatInfo.id) \(created.timeIntervalSince1970)" } }
- var supportUnreadCount: Int {
- switch chatInfo {
- case let .group(groupInfo, _):
- if groupInfo.canModerate {
- return groupInfo.membersRequireAttention
- } else {
- return groupInfo.membership.supportChat?.unread ?? 0
- }
- default: return 0
- }
- }
-
public static var sampleData: Chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
}
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index 6b938aaa4d..d92411decd 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -344,54 +344,43 @@ func apiGetChatTagsAsync() async throws -> [ChatTag] {
let loadItemsPerPage = 50
-func apiGetChat(chatId: ChatId, scope: GroupChatScope?, contentTag: MsgContentTag? = nil, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) {
- let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, scope: scope, contentTag: contentTag, pagination: pagination, search: search))
+func apiGetChat(chatId: ChatId, pagination: ChatPagination, search: String = "") async throws -> (Chat, NavigationInfo) {
+ let r: ChatResponse0 = try await chatSendCmd(.apiGetChat(chatId: chatId, pagination: pagination, search: search))
if case let .apiChat(_, chat, navInfo) = r { return (Chat.init(chat), navInfo ?? NavigationInfo()) }
throw r.unexpected
}
-func loadChat(chat: Chat, im: ItemsModel, search: String = "", clearItems: Bool = true) async {
- await loadChat(chatId: chat.chatInfo.id, im: im, search: search, clearItems: clearItems)
+func loadChat(chat: Chat, search: String = "", clearItems: Bool = true) async {
+ await loadChat(chatId: chat.chatInfo.id, search: search, clearItems: clearItems)
}
-func loadChat(chatId: ChatId, im: ItemsModel, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
+func loadChat(chatId: ChatId, search: String = "", openAroundItemId: ChatItem.ID? = nil, clearItems: Bool = true) async {
+ let m = ChatModel.shared
+ let im = ItemsModel.shared
await MainActor.run {
+ m.chatItemStatuses = [:]
if clearItems {
im.reversedChatItems = []
- im.chatState.clear()
+ ItemsModel.shared.chatState.clear()
}
}
- await apiLoadMessages(
- chatId,
- im,
- ( // pagination
- openAroundItemId != nil
- ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage)
- : (
- search == ""
- ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)
- )
- ),
- search,
- openAroundItemId,
- { 0...0 }
- )
+ await apiLoadMessages(chatId, openAroundItemId != nil ? .around(chatItemId: openAroundItemId!, count: loadItemsPerPage) : (search == "" ? .initial(count: loadItemsPerPage) : .last(count: loadItemsPerPage)), im.chatState, search, openAroundItemId, { 0...0 })
}
-func apiGetChatItemInfo(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64) async throws -> ChatItemInfo {
- let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, scope: scope, itemId: itemId))
+func apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) async throws -> ChatItemInfo {
+ let r: ChatResponse0 = try await chatSendCmd(.apiGetChatItemInfo(type: type, id: id, itemId: itemId))
if case let .chatItemInfo(_, _, chatItemInfo) = r { return chatItemInfo }
throw r.unexpected
}
-func apiPlanForwardChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) {
- let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(fromChatType: type, fromChatId: id, fromScope: scope, itemIds: itemIds))
+func apiPlanForwardChatItems(type: ChatType, id: Int64, itemIds: [Int64]) async throws -> ([Int64], ForwardConfirmation?) {
+ let r: ChatResponse1 = try await chatSendCmd(.apiPlanForwardChatItems(toChatType: type, toChatId: id, itemIds: itemIds))
if case let .forwardPlan(_, chatItemIds, forwardConfimation) = r { return (chatItemIds, forwardConfimation) }
throw r.unexpected
}
-func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Int64, fromScope: GroupChatScope?, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? {
- let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, toScope: toScope, fromChatType: fromChatType, fromChatId: fromChatId, fromScope: fromScope, itemIds: itemIds, ttl: ttl)
+func apiForwardChatItems(toChatType: ChatType, toChatId: Int64, fromChatType: ChatType, fromChatId: Int64, itemIds: [Int64], ttl: Int?) async -> [ChatItem]? {
+ let cmd: ChatCommand = .apiForwardChatItems(toChatType: toChatType, toChatId: toChatId, fromChatType: fromChatType, fromChatId: fromChatId, itemIds: itemIds, ttl: ttl)
return await processSendMessageCmd(toChatType: toChatType, cmd: cmd)
}
@@ -423,8 +412,8 @@ func apiReorderChatTags(tagIds: [Int64]) async throws {
try await sendCommandOkResp(.apiReorderChatTags(tagIds: tagIds))
}
-func apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? {
- let cmd: ChatCommand = .apiSendMessages(type: type, id: id, scope: scope, live: live, ttl: ttl, composedMessages: composedMessages)
+func apiSendMessages(type: ChatType, id: Int64, live: Bool = false, ttl: Int? = nil, composedMessages: [ComposedMessage]) async -> [ChatItem]? {
+ let cmd: ChatCommand = .apiSendMessages(type: type, id: id, live: live, ttl: ttl, composedMessages: composedMessages)
return await processSendMessageCmd(toChatType: type, cmd: cmd)
}
@@ -501,8 +490,8 @@ private func createChatItemsErrorAlert(_ r: ChatError) {
)
}
-func apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem {
- let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, scope: scope, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay)
+func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, updatedMessage: UpdatedMessage, live: Bool = false) async throws -> ChatItem {
+ let r: ChatResponse1 = try await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, updatedMessage: updatedMessage, live: live), bgDelay: msgDelay)
switch r {
case let .chatItemUpdated(_, aChatItem): return aChatItem.chatItem
case let .chatItemNotChanged(_, aChatItem): return aChatItem.chatItem
@@ -510,8 +499,8 @@ func apiUpdateChatItem(type: ChatType, id: Int64, scope: GroupChatScope?, itemId
}
}
-func apiChatItemReaction(type: ChatType, id: Int64, scope: GroupChatScope?, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem {
- let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, scope: scope, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay)
+func apiChatItemReaction(type: ChatType, id: Int64, itemId: Int64, add: Bool, reaction: MsgReaction) async throws -> ChatItem {
+ let r: ChatResponse1 = try await chatSendCmd(.apiChatItemReaction(type: type, id: id, itemId: itemId, add: add, reaction: reaction), bgDelay: msgDelay)
if case let .chatItemReaction(_, _, reaction) = r { return reaction.chatReaction.chatItem }
throw r.unexpected
}
@@ -523,8 +512,8 @@ func apiGetReactionMembers(groupId: Int64, itemId: Int64, reaction: MsgReaction)
throw r.unexpected
}
-func apiDeleteChatItems(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
- let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, scope: scope, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
+func apiDeleteChatItems(type: ChatType, id: Int64, itemIds: [Int64], mode: CIDeleteMode) async throws -> [ChatItemDeletion] {
+ let r: ChatResponse1 = try await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemIds: itemIds, mode: mode), bgDelay: msgDelay)
if case let .chatItemsDeleted(_, items, _) = r { return items }
throw r.unexpected
}
@@ -1248,14 +1237,12 @@ func apiRejectContactRequest(contactReqId: Int64) async throws {
throw r.unexpected
}
-func apiChatRead(type: ChatType, id: Int64, scope: GroupChatScope?) async throws {
- try await sendCommandOkResp(.apiChatRead(type: type, id: id, scope: scope))
+func apiChatRead(type: ChatType, id: Int64) async throws {
+ try await sendCommandOkResp(.apiChatRead(type: type, id: id))
}
-func apiChatItemsRead(type: ChatType, id: Int64, scope: GroupChatScope?, itemIds: [Int64]) async throws -> ChatInfo {
- let r: ChatResponse1 = try await chatSendCmd(.apiChatItemsRead(type: type, id: id, scope: scope, itemIds: itemIds))
- if case let .itemsReadForChat(_, updatedChatInfo) = r { return updatedChatInfo }
- throw r.unexpected
+func apiChatItemsRead(type: ChatType, id: Int64, itemIds: [Int64]) async throws {
+ try await sendCommandOkResp(.apiChatItemsRead(type: type, id: id, itemIds: itemIds))
}
func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws {
@@ -1558,13 +1545,13 @@ func apiGetNetworkStatuses() throws -> [ConnNetworkStatus] {
throw r.unexpected
}
-func markChatRead(_ im: ItemsModel, _ chat: Chat) async {
+func markChatRead(_ chat: Chat) async {
do {
if chat.chatStats.unreadCount > 0 {
let cInfo = chat.chatInfo
- try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope())
+ try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId)
await MainActor.run {
- withAnimation { ChatModel.shared.markAllChatItemsRead(im, cInfo) }
+ withAnimation { ChatModel.shared.markAllChatItemsRead(cInfo) }
}
}
if chat.chatStats.unreadChat {
@@ -1587,12 +1574,11 @@ func markChatUnread(_ chat: Chat, unreadChat: Bool = true) async {
}
}
-func apiMarkChatItemsRead(_ im: ItemsModel, _ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
+func apiMarkChatItemsRead(_ cInfo: ChatInfo, _ itemIds: [ChatItem.ID], mentionsRead: Int) async {
do {
- let updatedChatInfo = try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemIds: itemIds)
- await MainActor.run {
- ChatModel.shared.updateChatInfo(updatedChatInfo)
- ChatModel.shared.markChatItemsRead(im, cInfo, itemIds, mentionsRead)
+ try await apiChatItemsRead(type: cInfo.chatType, id: cInfo.apiId, itemIds: itemIds)
+ DispatchQueue.main.async {
+ ChatModel.shared.markChatItemsRead(cInfo, itemIds, mentionsRead)
}
} catch {
logger.error("apiChatItemsRead error: \(responseError(error))")
@@ -1640,21 +1626,9 @@ func apiJoinGroup(_ groupId: Int64) async throws -> JoinGroupResult {
}
}
-func apiAcceptMember(_ groupId: Int64, _ groupMemberId: Int64, _ memberRole: GroupMemberRole) async throws -> (GroupInfo, GroupMember) {
- let r: ChatResponse2 = try await chatSendCmd(.apiAcceptMember(groupId: groupId, groupMemberId: groupMemberId, memberRole: memberRole))
- if case let .memberAccepted(_, groupInfo, member) = r { return (groupInfo, member) }
- throw r.unexpected
-}
-
-func apiDeleteMemberSupportChat(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (GroupInfo, GroupMember) {
- let r: ChatResponse2 = try await chatSendCmd(.apiDeleteMemberSupportChat(groupId: groupId, groupMemberId: groupMemberId))
- if case let .memberSupportChatDeleted(_, groupInfo, member) = r { return (groupInfo, member) }
- throw r.unexpected
-}
-
-func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> (GroupInfo, [GroupMember]) {
+func apiRemoveMembers(_ groupId: Int64, _ memberIds: [Int64], _ withMessages: Bool = false) async throws -> [GroupMember] {
let r: ChatResponse2 = try await chatSendCmd(.apiRemoveMembers(groupId: groupId, memberIds: memberIds, withMessages: withMessages), bgTask: false)
- if case let .userDeletedMembers(_, updatedGroupInfo, members, _withMessages) = r { return (updatedGroupInfo, members) }
+ if case let .userDeletedMembers(_, _, members, withMessages) = r { return members }
throw r.unexpected
}
@@ -1695,8 +1669,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{ c in c.chatInfo.sendMsgEnabled ? c.chatInfo.contact : nil }
- .filter{ c in !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
+ .compactMap{ $0.chatInfo.contact }
+ .filter{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) }
.sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() }
}
@@ -2158,7 +2132,7 @@ func processReceivedMsg(_ res: ChatEvent) async {
let cInfo = chatItem.chatInfo
let cItem = chatItem.chatItem
if !cItem.isDeletedContent && active(user) {
- _ = await MainActor.run { m.upsertChatItem(cInfo, cItem) }
+ await MainActor.run { m.updateChatItem(cInfo, cItem, status: cItem.meta.itemStatus) }
}
if let endTask = m.messageDelivery[cItem.id] {
switch cItem.meta.itemStatus {
@@ -2206,9 +2180,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
m.decreaseGroupReportsCounter(item.deletedChatItem.chatInfo.id)
}
}
- if let updatedChatInfo = items.last?.deletedChatItem.chatInfo {
- m.updateChatInfo(updatedChatInfo)
- }
}
case let .groupChatItemsDeleted(user, groupInfo, chatItemIDs, _, member_):
await groupChatItemsDeleted(user, groupInfo, chatItemIDs, member_)
@@ -2257,13 +2228,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
_ = m.upsertGroupMember(groupInfo, member)
}
}
- case let .memberAcceptedByOther(user, groupInfo, _, member):
- if active(user) {
- await MainActor.run {
- _ = m.upsertGroupMember(groupInfo, member)
- m.updateGroup(groupInfo)
- }
- }
case let .deletedMemberUser(user, groupInfo, member, withMessages): // TODO update user member
if active(user) {
await MainActor.run {
@@ -2276,7 +2240,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
case let .deletedMember(user, groupInfo, byMember, deletedMember, withMessages):
if active(user) {
await MainActor.run {
- m.updateGroup(groupInfo)
_ = m.upsertGroupMember(groupInfo, deletedMember)
if withMessages {
m.removeMemberItems(deletedMember, byMember: byMember, groupInfo)
@@ -2286,7 +2249,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
case let .leftMember(user, groupInfo, member):
if active(user) {
await MainActor.run {
- m.updateGroup(groupInfo)
_ = m.upsertGroupMember(groupInfo, member)
}
}
@@ -2301,12 +2263,6 @@ func processReceivedMsg(_ res: ChatEvent) async {
await MainActor.run {
m.updateGroup(groupInfo)
}
- if m.chatId == groupInfo.id,
- case .memberSupport(nil) = m.secondaryIM?.groupScopeInfo {
- await MainActor.run {
- m.secondaryPendingInviteeChatOpened = false
- }
- }
}
case let .joinedGroupMember(user, groupInfo, member):
if active(user) {
@@ -2593,7 +2549,7 @@ func groupChatItemsDeleted(_ user: UserRef, _ groupInfo: GroupInfo, _ chatItemID
return
}
let im = ItemsModel.shared
- let cInfo = ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil)
+ let cInfo = ChatInfo.group(groupInfo: groupInfo)
await MainActor.run {
m.decreaseGroupReportsCounter(cInfo.id, by: chatItemIDs.count)
}
diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift
index 47c0f61c79..f8d69c5fc8 100644
--- a/apps/ios/Shared/SimpleXApp.swift
+++ b/apps/ios/Shared/SimpleXApp.swift
@@ -159,7 +159,7 @@ struct SimpleXApp: App {
if let id = chatModel.chatId,
let chat = chatModel.getChat(id),
!NtfManager.shared.navigatingToChat {
- Task { await loadChat(chat: chat, im: ItemsModel.shared, clearItems: false) }
+ Task { await loadChat(chat: chat, clearItems: false) }
}
if let ncr = chatModel.ntfContactRequest {
await MainActor.run { chatModel.ntfContactRequest = nil }
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
index b60842a4a0..62a41c504a 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoToolbar.swift
@@ -22,28 +22,11 @@ struct ChatInfoToolbar: View {
Image(systemName: "theatermasks").frame(maxWidth: 24, maxHeight: 24, alignment: .center).foregroundColor(.indigo)
Spacer().frame(width: 16)
}
- ZStack(alignment: .bottomTrailing) {
- ChatInfoImage(
- chat: chat,
- size: imageSize,
- color: Color(uiColor: .tertiaryLabel)
- )
- if chat.chatStats.reportsCount > 0 {
- Image(systemName: "flag.circle.fill")
- .resizable()
- .scaledToFit()
- .frame(width: 14, height: 14)
- .symbolRenderingMode(.palette)
- .foregroundStyle(.white, .red)
- } else if chat.supportUnreadCount > 0 {
- Image(systemName: "flag.circle.fill")
- .resizable()
- .scaledToFit()
- .frame(width: 14, height: 14)
- .symbolRenderingMode(.palette)
- .foregroundStyle(.white, theme.colors.primary)
- }
- }
+ ChatInfoImage(
+ chat: chat,
+ size: imageSize,
+ color: Color(uiColor: .tertiaryLabel)
+ )
.padding(.trailing, 4)
let t = Text(cInfo.displayName).font(.headline)
(cInfo.contact?.verified == true ? contactVerifiedShield + t : t)
diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
index 0498dc5d70..8194c8fe6f 100644
--- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift
@@ -687,7 +687,7 @@ struct ChatTTLOption: View {
let m = ChatModel.shared
do {
try await setChatTTL(chatType: chat.chatInfo.chatType, id: chat.chatInfo.apiId, ttl)
- await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true)
+ await loadChat(chat: chat, clearItems: true)
await MainActor.run {
progressIndicator = false
currentChatItemTTL = chatItemTTL
@@ -700,7 +700,7 @@ struct ChatTTLOption: View {
}
catch let error {
logger.error("setChatTTL error \(responseError(error))")
- await loadChat(chat: chat, im: ItemsModel.shared, clearItems: true)
+ await loadChat(chat: chat, clearItems: true)
await MainActor.run {
chatItemTTL = currentChatItemTTL
progressIndicator = false
@@ -938,7 +938,7 @@ struct ChatWallpaperEditorSheet: View {
self.chat = chat
self.themes = if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes {
uiThemes
- } else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
+ } else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
uiThemes
} else {
ThemeModeOverrides()
@@ -974,7 +974,7 @@ struct ChatWallpaperEditorSheet: View {
private func themesFromChat(_ chat: Chat) -> ThemeModeOverrides {
if case let ChatInfo.direct(contact) = chat.chatInfo, let uiThemes = contact.uiThemes {
uiThemes
- } else if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
+ } else if case let ChatInfo.group(groupInfo) = chat.chatInfo, let uiThemes = groupInfo.uiThemes {
uiThemes
} else {
ThemeModeOverrides()
@@ -1052,12 +1052,12 @@ struct ChatWallpaperEditorSheet: View {
chat.wrappedValue = Chat.init(chatInfo: ChatInfo.direct(contact: contact))
themes = themesFromChat(chat.wrappedValue)
}
- } else if case var ChatInfo.group(groupInfo, _) = chat.wrappedValue.chatInfo {
+ } else if case var ChatInfo.group(groupInfo) = chat.wrappedValue.chatInfo {
groupInfo.uiThemes = changedThemesConstant
await MainActor.run {
- ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil))
- chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo, groupChatScope: nil))
+ ChatModel.shared.updateChatInfo(ChatInfo.group(groupInfo: groupInfo))
+ chat.wrappedValue = Chat.init(chatInfo: ChatInfo.group(groupInfo: groupInfo))
themes = themesFromChat(chat.wrappedValue)
}
}
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
index b2b4441646..02be8af73b 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIChatFeatureView.swift
@@ -12,8 +12,8 @@ import SimpleXChat
struct CIChatFeatureView: View {
@EnvironmentObject var m: ChatModel
@Environment(\.revealed) var revealed: Bool
+ @ObservedObject var im = ItemsModel.shared
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
@EnvironmentObject var theme: AppTheme
var chatItem: ChatItem
var feature: Feature
@@ -53,7 +53,7 @@ struct CIChatFeatureView: View {
private func mergedFeatures() -> [FeatureInfo]? {
var fs: [FeatureInfo] = []
var icons: Set = []
- if var i = m.getChatItemIndex(im, chatItem) {
+ if var i = m.getChatItemIndex(chatItem) {
while i < im.reversedChatItems.count,
let f = featureInfo(im.reversedChatItems[i]) {
if !icons.contains(f.icon) {
@@ -108,7 +108,6 @@ struct CIChatFeatureView_Previews: PreviewProvider {
let enabled = FeatureEnabled(forUser: false, forContact: false)
CIChatFeatureView(
chat: Chat.sampleData,
- im: ItemsModel.shared,
chatItem: ChatItem.getChatFeatureSample(.fullDelete, enabled), feature: ChatFeature.fullDelete, iconColor: enabled.iconColor(.secondary)
).environment(\.revealed, true)
}
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
index 1b9376b5db..b0b404d8b5 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift
@@ -278,7 +278,6 @@ func showFileErrorAlert(_ err: FileError, temporary: Bool = false) {
struct CIFileView_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
let sentFile: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@@ -294,16 +293,16 @@ struct CIFileView_Previews: PreviewProvider {
file: nil
)
Group {
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: fileChatItemWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
+ ChatItemView(chat: Chat.sampleData, chatItem: sentFile, scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileName: "some_long_file_name_here", fileStatus: .rcvInvitation), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvAccepted), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileStatus: .rcvCancelled), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(fileSize: 1_000_000_000, fileStatus: .rcvInvitation), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Hello there", fileStatus: .rcvInvitation), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getFileMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", fileStatus: .rcvInvitation), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: fileChatItemWtFile, scrollToItemId: { _ in })
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
index d1f49f635a..d30369339d 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift
@@ -12,7 +12,7 @@ import SimpleXChat
struct CIImageView: View {
@EnvironmentObject var m: ChatModel
let chatItem: ChatItem
- var scrollToItem: ((ChatItem.ID) -> Void)? = nil
+ var scrollToItemId: ((ChatItem.ID) -> Void)? = nil
var preview: UIImage?
let maxWidth: CGFloat
var imgWidth: CGFloat?
@@ -26,7 +26,7 @@ struct CIImageView: View {
if let uiImage = getLoadedImage(file) {
Group { if smallView { smallViewImageView(uiImage) } else { imageView(uiImage) } }
.fullScreenCover(isPresented: $showFullScreenImage) {
- FullScreenMediaView(chatItem: chatItem, scrollToItem: scrollToItem, image: uiImage, showView: $showFullScreenImage)
+ FullScreenMediaView(chatItem: chatItem, scrollToItemId: scrollToItemId, image: uiImage, showView: $showFullScreenImage)
}
.if(!smallView) { view in
view.modifier(PrivacyBlur(blurred: $blurred))
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
index 3201332c1e..4e5713c263 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIRcvDecryptionError.swift
@@ -45,7 +45,7 @@ struct CIRcvDecryptionError: View {
viewBody()
.onAppear {
// for direct chat ConnectionStats are populated on opening chat, see ChatView onAppear
- if case let .group(groupInfo, _) = chat.chatInfo,
+ if case let .group(groupInfo) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir {
do {
let (member, stats) = try apiGroupMemberInfoSync(groupInfo.apiId, groupMember.groupMemberId)
@@ -83,7 +83,7 @@ struct CIRcvDecryptionError: View {
} else {
basicDecryptionErrorItem()
}
- } else if case let .group(groupInfo, _) = chat.chatInfo,
+ } else if case let .group(groupInfo) = chat.chatInfo,
case let .groupRcv(groupMember) = chatItem.chatDir,
let mem = m.getGroupMember(groupMember.groupMemberId),
let memberStats = mem.wrapped.activeConn?.connectionStats {
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
index 47aee2a586..715e606a74 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVoiceView.swift
@@ -435,7 +435,6 @@ class VoiceItemState {
struct CIVoiceView_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
let sentVoiceMessage: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@@ -458,10 +457,10 @@ struct CIVoiceView_Previews: PreviewProvider {
duration: 30,
allowMenu: Binding.constant(true)
)
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWtFile, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: .constant(true))
+ ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in }, allowMenu: .constant(true))
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(), scrollToItemId: { _ in }, allowMenu: .constant(true))
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in }, allowMenu: .constant(true))
+ ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWtFile, scrollToItemId: { _ in }, allowMenu: .constant(true))
}
.previewLayout(.fixed(width: 360, height: 360))
}
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
index 0b6f249b9c..f4e2a4135a 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedCIVoiceView.swift
@@ -77,7 +77,6 @@ struct FramedCIVoiceView: View {
struct FramedCIVoiceView_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
let sentVoiceMessage: ChatItem = ChatItem(
chatDir: .directSnd,
meta: CIMeta.getSample(1, .now, "", .sndSent(sndProgress: .complete), itemEdited: true),
@@ -93,11 +92,11 @@ struct FramedCIVoiceView_Previews: PreviewProvider {
file: CIFile.getSample(fileStatus: .sndComplete)
)
Group {
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: sentVoiceMessage, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: voiceMessageWithQuote, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
+ ChatItemView(chat: Chat.sampleData, chatItem: sentVoiceMessage, scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Hello there", fileStatus: .rcvTransfer(rcvProgress: 7, rcvTotal: 10)), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getVoiceMsgContentSample(text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: voiceMessageWithQuote, scrollToItemId: { _ in })
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 360))
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
index c9c9952688..b27d266d8a 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift
@@ -13,10 +13,8 @@ struct FramedItemView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
var chatItem: ChatItem
- var scrollToItem: (ChatItem.ID) -> Void
- @Binding var scrollToItemId: ChatItem.ID?
+ var scrollToItemId: (ChatItem.ID) -> Void
var preview: UIImage?
var maxWidth: CGFloat = .infinity
@State var msgWidth: CGFloat = 0
@@ -58,16 +56,12 @@ struct FramedItemView: View {
if let qi = chatItem.quotedItem {
ciQuoteView(qi)
.simultaneousGesture(TapGesture().onEnded {
- if let ci = im.reversedChatItems.first(where: { $0.id == qi.itemId }) {
+ if let ci = ItemsModel.shared.reversedChatItems.first(where: { $0.id == qi.itemId }) {
withAnimation {
- scrollToItem(ci.id)
+ scrollToItemId(ci.id)
}
} else if let id = qi.itemId {
- if (chatItem.isReport && im.secondaryIMFilter != nil) {
- scrollToItemId = id
- } else {
- scrollToItem(id)
- }
+ scrollToItemId(id)
} else {
showQuotedItemDoesNotExistAlert()
}
@@ -76,7 +70,7 @@ struct FramedItemView: View {
framedItemHeader(icon: "arrowshape.turn.up.forward", caption: Text(itemForwarded.text(chat.chatInfo.chatType)).italic(), pad: true)
}
- ChatItemContentView(chat: chat, im: im, chatItem: chatItem, msgContentView: framedMsgContentView)
+ ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: framedMsgContentView)
.padding(chatItem.content.msgContent != nil ? 0 : 4)
.overlay(DetermineWidth())
}
@@ -125,7 +119,7 @@ struct FramedItemView: View {
} else {
switch (chatItem.content.msgContent) {
case let .image(text, _):
- CIImageView(chatItem: chatItem, scrollToItem: scrollToItem, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery)
+ CIImageView(chatItem: chatItem, scrollToItemId: scrollToItemId, preview: preview, maxWidth: maxWidth, imgWidth: imgWidth, showFullScreenImage: $showFullscreenGallery)
.overlay(DetermineWidth())
if text == "" && !chatItem.meta.isLive {
Color.clear
@@ -296,7 +290,7 @@ struct FramedItemView: View {
private func membership() -> GroupMember? {
switch chat.chatInfo {
- case let .group(groupInfo: groupInfo, _): return groupInfo.membership
+ case let .group(groupInfo: groupInfo): return groupInfo.membership
default: return nil
}
}
@@ -392,16 +386,15 @@ func chatItemFrameContextColor(_ ci: ChatItem, _ theme: AppTheme) -> Color {
struct FramedItemView_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
Group{
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line "), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat"), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
}
.previewLayout(.fixed(width: 360, height: 200))
}
@@ -409,18 +402,17 @@ struct FramedItemView_Previews: PreviewProvider {
struct FramedItemView_Edited_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
Group {
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemEdited: true), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 200))
@@ -429,18 +421,17 @@ struct FramedItemView_Edited_Previews: PreviewProvider {
struct FramedItemView_Deleted_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
Group {
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
- FramedItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil), allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directSnd), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "https://simplex.chat", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "hi", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directSnd, .now, "👍", .sndSent(sndProgress: .complete), quotedItem: CIQuote.getSample(1, .now, "Hello too", chatDir: .directRcv), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this covers -", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too!!! this text has the time on the same line ", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "https://simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "chaT@simplex.chat", .rcvRead, itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello", quotedItem: CIQuote.getSample(1, .now, "hi there hello hello hello ther hello hello", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
+ FramedItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .groupRcv(groupMember: GroupMember.sampleData), .now, "hello there this is a long text", quotedItem: CIQuote.getSample(1, .now, "hi there", chatDir: .directSnd, image: "data:image/jpg;base64,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"), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in }, allowMenu: Binding.constant(true))
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 200))
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
index f243a83142..10e5efa298 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/FullScreenMediaView.swift
@@ -14,7 +14,7 @@ import AVKit
struct FullScreenMediaView: View {
@EnvironmentObject var m: ChatModel
@State var chatItem: ChatItem
- var scrollToItem: ((ChatItem.ID) -> Void)?
+ var scrollToItemId: ((ChatItem.ID) -> Void)?
@State var image: UIImage?
@State var player: AVPlayer? = nil
@State var url: URL? = nil
@@ -71,7 +71,7 @@ struct FullScreenMediaView: View {
let w = abs(t.width)
if t.height > 60 && t.height > w * 2 {
showView = false
- scrollToItem?(chatItem.id)
+ scrollToItemId?(chatItem.id)
} else if w > 60 && w > abs(t.height) * 2 && !scrolling {
let previous = t.width > 0
scrolling = true
diff --git a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
index c6a5d0353c..87a9b2ce61 100644
--- a/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItem/MarkedDeletedItemView.swift
@@ -14,7 +14,6 @@ struct MarkedDeletedItemView: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
var chatItem: ChatItem
var body: some View {
@@ -30,14 +29,14 @@ struct MarkedDeletedItemView: View {
var mergedMarkedDeletedText: LocalizedStringKey {
if !revealed,
let ciCategory = chatItem.mergeCategory,
- var i = m.getChatItemIndex(im, chatItem) {
+ var i = m.getChatItemIndex(chatItem) {
var moderated = 0
var blocked = 0
var blockedByAdmin = 0
var deleted = 0
var moderatedBy: Set = []
- while i < im.reversedChatItems.count,
- let ci = .some(im.reversedChatItems[i]),
+ while i < ItemsModel.shared.reversedChatItems.count,
+ let ci = .some(ItemsModel.shared.reversedChatItems[i]),
ci.mergeCategory == ciCategory,
let itemDeleted = ci.meta.itemDeleted {
switch itemDeleted {
@@ -86,7 +85,6 @@ struct MarkedDeletedItemView_Previews: PreviewProvider {
Group {
MarkedDeletedItemView(
chat: Chat.sampleData,
- im: ItemsModel.shared,
chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now))
).environment(\.revealed, true)
}
diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift
index a412bf4452..f5558bcd93 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift
@@ -40,31 +40,25 @@ extension EnvironmentValues {
struct ChatItemView: View {
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
@EnvironmentObject var theme: AppTheme
@Environment(\.showTimestamp) var showTimestamp: Bool
@Environment(\.revealed) var revealed: Bool
var chatItem: ChatItem
- var scrollToItem: (ChatItem.ID) -> Void
- @Binding var scrollToItemId: ChatItem.ID?
+ var scrollToItemId: (ChatItem.ID) -> Void
var maxWidth: CGFloat = .infinity
@Binding var allowMenu: Bool
init(
chat: Chat,
- im: ItemsModel,
chatItem: ChatItem,
- scrollToItem: @escaping (ChatItem.ID) -> Void,
- scrollToItemId: Binding = .constant(nil),
+ scrollToItemId: @escaping (ChatItem.ID) -> Void,
showMember: Bool = false,
maxWidth: CGFloat = .infinity,
allowMenu: Binding = .constant(false)
) {
self.chat = chat
- self.im = im
self.chatItem = chatItem
- self.scrollToItem = scrollToItem
- _scrollToItemId = scrollToItemId
+ self.scrollToItemId = scrollToItemId
self.maxWidth = maxWidth
_allowMenu = allowMenu
}
@@ -72,14 +66,14 @@ struct ChatItemView: View {
var body: some View {
let ci = chatItem
if chatItem.meta.itemDeleted != nil && (!revealed || chatItem.isDeletedContent) {
- MarkedDeletedItemView(chat: chat, im: im, chatItem: chatItem)
+ MarkedDeletedItemView(chat: chat, chatItem: chatItem)
} else if ci.quotedItem == nil && ci.meta.itemForwarded == nil && ci.meta.itemDeleted == nil && !ci.meta.isLive {
if let mc = ci.content.msgContent, mc.isText && isShortEmoji(ci.content.text) {
EmojiItemView(chat: chat, chatItem: ci)
} else if ci.content.text.isEmpty, case let .voice(_, duration) = ci.content.msgContent {
CIVoiceView(chat: chat, chatItem: ci, recordingFile: ci.file, duration: duration, allowMenu: $allowMenu)
} else if ci.content.msgContent == nil {
- ChatItemContentView(chat: chat, im: im, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
+ ChatItemContentView(chat: chat, chatItem: chatItem, msgContentView: { Text(ci.text) }) // msgContent is unreachable branch in this case
} else {
framedItemView()
}
@@ -107,10 +101,8 @@ struct ChatItemView: View {
}()
return FramedItemView(
chat: chat,
- im: im,
chatItem: chatItem,
- scrollToItem: scrollToItem,
- scrollToItemId: $scrollToItemId,
+ scrollToItemId: scrollToItemId,
preview: preview,
maxWidth: maxWidth,
imgWidth: adjustedMaxWidth,
@@ -125,7 +117,6 @@ struct ChatItemContentView: View {
@EnvironmentObject var theme: AppTheme
@Environment(\.revealed) var revealed: Bool
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
var chatItem: ChatItem
var msgContentView: () -> Content
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
@@ -149,9 +140,7 @@ struct ChatItemContentView: View {
case let .sndGroupInvitation(groupInvitation, memberRole): groupInvitationItemView(groupInvitation, memberRole)
case .rcvDirectEvent: eventItemView()
case .rcvGroupEvent(.memberCreatedContact): CIMemberCreatedContactView(chatItem: chatItem)
- case .rcvGroupEvent(.newMemberPendingReview): CIEventView(eventText: pendingReviewEventItemText())
case .rcvGroupEvent: eventItemView()
- case .sndGroupEvent(.userPendingReview): CIEventView(eventText: pendingReviewEventItemText())
case .sndGroupEvent: eventItemView()
case .rcvConnEvent: eventItemView()
case .sndConnEvent: eventItemView()
@@ -160,7 +149,7 @@ struct ChatItemContentView: View {
case let .rcvChatPreference(feature, allowed, param):
CIFeaturePreferenceView(chat: chat, chatItem: chatItem, feature: feature, allowed: allowed, param: param)
case let .sndChatPreference(feature, _, _):
- CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
+ CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, icon: feature.icon, iconColor: theme.colors.secondary)
case let .rcvGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .sndGroupFeature(feature, preference, _, role): chatFeatureView(feature, preference.enabled(role, for: chat.chatInfo.groupInfo?.membership).iconColor(theme.colors.secondary))
case let .rcvChatFeatureRejected(feature): chatFeatureView(feature, .red)
@@ -192,13 +181,6 @@ struct ChatItemContentView: View {
CIEventView(eventText: eventItemViewText(theme.colors.secondary))
}
- private func pendingReviewEventItemText() -> Text {
- Text(chatItem.content.text)
- .font(.caption)
- .foregroundColor(theme.colors.secondary)
- .fontWeight(.bold)
- }
-
private func eventItemViewText(_ secondaryColor: Color) -> Text {
if !revealed, let t = mergedGroupEventText {
return chatEventText(t + textSpace + chatItem.timestampText, secondaryColor)
@@ -214,7 +196,7 @@ struct ChatItemContentView: View {
}
private func chatFeatureView(_ feature: Feature, _ iconColor: Color) -> some View {
- CIChatFeatureView(chat: chat, im: im, chatItem: chatItem, feature: feature, iconColor: iconColor)
+ CIChatFeatureView(chat: chat, chatItem: chatItem, feature: feature, iconColor: iconColor)
}
private var mergedGroupEventText: Text? {
@@ -274,17 +256,16 @@ func chatEventText(_ ci: ChatItem, _ secondaryColor: Color) -> Text {
struct ChatItemView_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
Group{
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getDeletedContentSample(), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true)
- ChatItemView(chat: Chat.sampleData, im: im, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)).environment(\.revealed, true)
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "hello there too"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(2, .directRcv, .now, "🙂🙂🙂🙂🙂🙂"), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getDeletedContentSample(), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemDeleted: .deleted(deletedTs: .now)), scrollToItemId: { _ in })
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "🙂", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true)
+ ChatItemView(chat: Chat.sampleData, chatItem: ChatItem.getSample(1, .directSnd, .now, "hello", .sndSent(sndProgress: .complete), itemLive: true), scrollToItemId: { _ in }).environment(\.revealed, true)
}
.environment(\.revealed, false)
.previewLayout(.fixed(width: 360, height: 70))
@@ -294,12 +275,10 @@ struct ChatItemView_Previews: PreviewProvider {
struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
static var previews: some View {
- let im = ItemsModel.shared
let ciFeatureContent = CIContent.rcvChatFeature(feature: .fullDelete, enabled: FeatureEnabled(forUser: false, forContact: false), param: nil)
Group{
ChatItemView(
chat: Chat.sampleData,
- im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -307,12 +286,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
- scrollToItem: { _ in },
- scrollToItemId: Binding.constant(nil)
+ scrollToItemId: { _ in }
)
ChatItemView(
chat: Chat.sampleData,
- im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "1 skipped message", .rcvRead),
@@ -320,11 +297,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
- scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil)
+ scrollToItemId: { _ in }
)
ChatItemView(
chat: Chat.sampleData,
- im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "received invitation to join group team as admin", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -332,12 +308,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
- scrollToItem: { _ in },
- scrollToItemId: Binding.constant(nil)
+ scrollToItemId: { _ in }
)
ChatItemView(
chat: Chat.sampleData,
- im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, "group event text", .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -345,12 +319,10 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
- scrollToItem: { _ in },
- scrollToItemId: Binding.constant(nil)
+ scrollToItemId: { _ in }
)
ChatItemView(
chat: Chat.sampleData,
- im: im,
chatItem: ChatItem(
chatDir: .directRcv,
meta: CIMeta.getSample(1, .now, ciFeatureContent.text, .rcvRead, itemDeleted: .deleted(deletedTs: .now)),
@@ -358,8 +330,7 @@ struct ChatItemView_NonMsgContentDeleted_Previews: PreviewProvider {
quotedItem: nil,
file: nil
),
- scrollToItem: { _ in },
- scrollToItemId: Binding.constant(nil)
+ scrollToItemId: { _ in }
)
}
.environment(\.revealed, true)
diff --git a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
index 93ecf870eb..07034cf8ec 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemsLoader.swift
@@ -13,8 +13,8 @@ let TRIM_KEEP_COUNT = 200
func apiLoadMessages(
_ chatId: ChatId,
- _ im: ItemsModel,
_ pagination: ChatPagination,
+ _ chatState: ActiveChatState,
_ search: String = "",
_ openAroundItemId: ChatItem.ID? = nil,
_ visibleItemIndexesNonReversed: @MainActor () -> ClosedRange = { 0 ... 0 }
@@ -22,7 +22,7 @@ func apiLoadMessages(
let chat: Chat
let navInfo: NavigationInfo
do {
- (chat, navInfo) = try await apiGetChat(chatId: chatId, scope: im.groupScopeInfo?.toChatScope(), contentTag: im.contentTag, pagination: pagination, search: search)
+ (chat, navInfo) = try await apiGetChat(chatId: chatId, pagination: pagination, search: search)
} catch let error {
logger.error("apiLoadMessages error: \(responseError(error))")
return
@@ -38,31 +38,30 @@ func apiLoadMessages(
return
}
- let unreadAfterItemId = im.chatState.unreadAfterItemId
+ let unreadAfterItemId = chatState.unreadAfterItemId
- let oldItems = Array(im.reversedChatItems.reversed())
+ let oldItems = Array(ItemsModel.shared.reversedChatItems.reversed())
var newItems: [ChatItem] = []
switch pagination {
case .initial:
let newSplits: [Int64] = if !chat.chatItems.isEmpty && navInfo.afterTotal > 0 { [chat.chatItems.last!.id] } else { [] }
- if im.secondaryIMFilter == nil && chatModel.getChat(chat.id) == nil {
+ if chatModel.getChat(chat.id) == nil {
chatModel.addChat(chat)
}
await MainActor.run {
- im.reversedChatItems = chat.chatItems.reversed()
- if im.secondaryIMFilter == nil {
- chatModel.updateChatInfo(chat.chatInfo)
- }
- im.chatState.splits = newSplits
+ chatModel.chatItemStatuses.removeAll()
+ ItemsModel.shared.reversedChatItems = chat.chatItems.reversed()
+ chatModel.updateChatInfo(chat.chatInfo)
+ chatState.splits = newSplits
if !chat.chatItems.isEmpty {
- im.chatState.unreadAfterItemId = chat.chatItems.last!.id
+ chatState.unreadAfterItemId = chat.chatItems.last!.id
}
- im.chatState.totalAfter = navInfo.afterTotal
- im.chatState.unreadTotal = chat.chatStats.unreadCount
- im.chatState.unreadAfter = navInfo.afterUnread
- im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread
+ chatState.totalAfter = navInfo.afterTotal
+ chatState.unreadTotal = chat.chatStats.unreadCount
+ chatState.unreadAfter = navInfo.afterUnread
+ chatState.unreadAfterNewestLoaded = navInfo.afterUnread
- im.preloadState.clear()
+ PreloadState.shared.clear()
}
case let .before(paginationChatItemId, _):
newItems.append(contentsOf: oldItems)
@@ -72,15 +71,15 @@ func apiLoadMessages(
let wasSize = newItems.count
let visibleItemIndexes = await MainActor.run { visibleItemIndexesNonReversed() }
let modifiedSplits = removeDuplicatesAndModifySplitsOnBeforePagination(
- unreadAfterItemId, &newItems, newIds, im.chatState.splits, visibleItemIndexes
+ unreadAfterItemId, &newItems, newIds, chatState.splits, visibleItemIndexes
)
let insertAt = max((indexInCurrentItems - (wasSize - newItems.count) + modifiedSplits.trimmedIds.count), 0)
newItems.insert(contentsOf: chat.chatItems, at: insertAt)
let newReversed: [ChatItem] = newItems.reversed()
await MainActor.run {
- im.reversedChatItems = newReversed
- im.chatState.splits = modifiedSplits.newSplits
- im.chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems)
+ ItemsModel.shared.reversedChatItems = newReversed
+ chatState.splits = modifiedSplits.newSplits
+ chatState.moveUnreadAfterItem(modifiedSplits.oldUnreadSplitIndex, modifiedSplits.newUnreadSplitIndex, oldItems)
}
case let .after(paginationChatItemId, _):
newItems.append(contentsOf: oldItems)
@@ -90,7 +89,7 @@ func apiLoadMessages(
let mappedItems = mapItemsToIds(chat.chatItems)
let newIds = mappedItems.0
let (newSplits, unreadInLoaded) = removeDuplicatesAndModifySplitsOnAfterPagination(
- mappedItems.1, paginationChatItemId, &newItems, newIds, chat, im.chatState.splits
+ mappedItems.1, paginationChatItemId, &newItems, newIds, chat, chatState.splits
)
let indexToAdd = min(indexInCurrentItems + 1, newItems.count)
let indexToAddIsLast = indexToAdd == newItems.count
@@ -98,19 +97,19 @@ func apiLoadMessages(
let new: [ChatItem] = newItems
let newReversed: [ChatItem] = newItems.reversed()
await MainActor.run {
- im.reversedChatItems = newReversed
- im.chatState.splits = newSplits
- im.chatState.moveUnreadAfterItem(im.chatState.splits.first ?? new.last!.id, new)
+ ItemsModel.shared.reversedChatItems = newReversed
+ chatState.splits = newSplits
+ chatState.moveUnreadAfterItem(chatState.splits.first ?? new.last!.id, new)
// loading clear bottom area, updating number of unread items after the newest loaded item
if indexToAddIsLast {
- im.chatState.unreadAfterNewestLoaded -= unreadInLoaded
+ chatState.unreadAfterNewestLoaded -= unreadInLoaded
}
}
case .around:
var newSplits: [Int64]
if openAroundItemId == nil {
newItems.append(contentsOf: oldItems)
- newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, im.chatState.splits, visibleItemIndexesNonReversed)
+ newSplits = await removeDuplicatesAndUpperSplits(&newItems, chat, chatState.splits, visibleItemIndexesNonReversed)
} else {
newSplits = []
}
@@ -121,37 +120,33 @@ func apiLoadMessages(
let newReversed: [ChatItem] = newItems.reversed()
let orderedSplits = newSplits
await MainActor.run {
- im.reversedChatItems = newReversed
- im.chatState.splits = orderedSplits
- im.chatState.unreadAfterItemId = chat.chatItems.last!.id
- im.chatState.totalAfter = navInfo.afterTotal
- im.chatState.unreadTotal = chat.chatStats.unreadCount
- im.chatState.unreadAfter = navInfo.afterUnread
+ ItemsModel.shared.reversedChatItems = newReversed
+ chatState.splits = orderedSplits
+ chatState.unreadAfterItemId = chat.chatItems.last!.id
+ chatState.totalAfter = navInfo.afterTotal
+ chatState.unreadTotal = chat.chatStats.unreadCount
+ chatState.unreadAfter = navInfo.afterUnread
if let openAroundItemId {
- im.chatState.unreadAfterNewestLoaded = navInfo.afterUnread
- if im.secondaryIMFilter == nil {
- ChatModel.shared.openAroundItemId = openAroundItemId // TODO [knocking] move openAroundItemId from ChatModel to ItemsModel?
- ChatModel.shared.chatId = chat.id
- }
+ chatState.unreadAfterNewestLoaded = navInfo.afterUnread
+ ChatModel.shared.openAroundItemId = openAroundItemId
+ ChatModel.shared.chatId = chatId
} else {
// no need to set it, count will be wrong
// chatState.unreadAfterNewestLoaded = navInfo.afterUnread
}
- im.preloadState.clear()
+ PreloadState.shared.clear()
}
case .last:
newItems.append(contentsOf: oldItems)
- let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, im.chatState.splits)
+ let newSplits = await removeDuplicatesAndUnusedSplits(&newItems, chat, chatState.splits)
newItems.append(contentsOf: chat.chatItems)
let items = newItems
await MainActor.run {
- im.reversedChatItems = items.reversed()
- im.chatState.splits = newSplits
- if im.secondaryIMFilter == nil {
- chatModel.updateChatInfo(chat.chatInfo)
- }
- im.chatState.unreadAfterNewestLoaded = 0
+ ItemsModel.shared.reversedChatItems = items.reversed()
+ chatState.splits = newSplits
+ chatModel.updateChatInfo(chat.chatInfo)
+ chatState.unreadAfterNewestLoaded = 0
}
}
}
diff --git a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift
index 5f2102b8bc..0a55ed48cc 100644
--- a/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift
+++ b/apps/ios/Shared/Views/Chat/ChatItemsMerger.swift
@@ -10,7 +10,6 @@ import SwiftUI
import SimpleXChat
struct MergedItems: Hashable, Equatable {
- let im: ItemsModel
let items: [MergedItem]
let splits: [SplitRange]
// chat item id, index in list
@@ -24,15 +23,15 @@ struct MergedItems: Hashable, Equatable {
hasher.combine("\(items.hashValue)")
}
- static func create(_ im: ItemsModel, _ revealedItems: Set) -> MergedItems {
- if im.reversedChatItems.isEmpty {
- return MergedItems(im: im, items: [], splits: [], indexInParentItems: [:])
+ static func create(_ items: [ChatItem], _ revealedItems: Set, _ chatState: ActiveChatState) -> MergedItems {
+ if items.isEmpty {
+ return MergedItems(items: [], splits: [], indexInParentItems: [:])
}
- let unreadCount = im.chatState.unreadTotal
+ let unreadCount = chatState.unreadTotal
- let unreadAfterItemId = im.chatState.unreadAfterItemId
- let itemSplits = im.chatState.splits
+ let unreadAfterItemId = chatState.unreadAfterItemId
+ let itemSplits = chatState.splits
var mergedItems: [MergedItem] = []
// Indexes of splits here will be related to reversedChatItems, not chatModel.chatItems
var splitRanges: [SplitRange] = []
@@ -41,19 +40,19 @@ struct MergedItems: Hashable, Equatable {
var unclosedSplitIndex: Int? = nil
var unclosedSplitIndexInParent: Int? = nil
var visibleItemIndexInParent = -1
- var unreadBefore = unreadCount - im.chatState.unreadAfterNewestLoaded
+ var unreadBefore = unreadCount - chatState.unreadAfterNewestLoaded
var lastRevealedIdsInMergedItems: BoxedValue<[Int64]>? = nil
var lastRangeInReversedForMergedItems: BoxedValue>? = nil
var recent: MergedItem? = nil
- while index < im.reversedChatItems.count {
- let item = im.reversedChatItems[index]
- let prev = index >= 1 ? im.reversedChatItems[index - 1] : nil
- let next = index + 1 < im.reversedChatItems.count ? im.reversedChatItems[index + 1] : nil
+ while index < items.count {
+ let item = items[index]
+ let prev = index >= 1 ? items[index - 1] : nil
+ let next = index + 1 < items.count ? items[index + 1] : nil
let category = item.mergeCategory
let itemIsSplit = itemSplits.contains(item.id)
if item.id == unreadAfterItemId {
- unreadBefore = unreadCount - im.chatState.unreadAfter
+ unreadBefore = unreadCount - chatState.unreadAfter
}
if item.isRcvNew {
unreadBefore -= 1
@@ -107,19 +106,18 @@ struct MergedItems: Hashable, Equatable {
// found item that is considered as a split
if let unclosedSplitIndex, let unclosedSplitIndexInParent {
// it was at least second split in the list
- splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
+ splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index - 1, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent - 1))
}
unclosedSplitIndex = index
unclosedSplitIndexInParent = visibleItemIndexInParent
- } else if index + 1 == im.reversedChatItems.count, let unclosedSplitIndex, let unclosedSplitIndexInParent {
+ } else if index + 1 == items.count, let unclosedSplitIndex, let unclosedSplitIndexInParent {
// just one split for the whole list, there will be no more, it's the end
- splitRanges.append(SplitRange(itemId: im.reversedChatItems[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
+ splitRanges.append(SplitRange(itemId: items[unclosedSplitIndex].id, indexRangeInReversed: unclosedSplitIndex ... index, indexRangeInParentItems: unclosedSplitIndexInParent ... visibleItemIndexInParent))
}
indexInParentItems[item.id] = visibleItemIndexInParent
index += 1
}
return MergedItems(
- im: im,
items: mergedItems,
splits: splitRanges,
indexInParentItems: indexInParentItems
@@ -129,6 +127,7 @@ struct MergedItems: Hashable, Equatable {
// Use this check to ensure that mergedItems state based on currently actual state of global
// splits and reversedChatItems
func isActualState() -> Bool {
+ let im = ItemsModel.shared
// do not load anything if global splits state is different than in merged items because it
// will produce undefined results in terms of loading and placement of items.
// Same applies to reversedChatItems
@@ -435,7 +434,7 @@ class BoxedValue: Equatable, Hashable {
}
@MainActor
-func visibleItemIndexesNonReversed(_ im: ItemsModel, _ listState: EndlessScrollView.ListState, _ mergedItems: MergedItems) -> ClosedRange {
+func visibleItemIndexesNonReversed(_ listState: EndlessScrollView.ListState, _ mergedItems: MergedItems) -> ClosedRange {
let zero = 0 ... 0
let items = mergedItems.items
if items.isEmpty {
@@ -446,12 +445,12 @@ func visibleItemIndexesNonReversed(_ im: ItemsModel, _ listState: EndlessScrollV
guard let newest, let oldest else {
return zero
}
- let size = im.reversedChatItems.count
+ let size = ItemsModel.shared.reversedChatItems.count
let range = size - oldest ... size - newest
if range.lowerBound < 0 || range.upperBound < 0 {
return zero
}
- // visible items mapped to their underlying data structure which is im.reversedChatItems.reversed()
+ // visible items mapped to their underlying data structure which is ItemsModel.shared.reversedChatItems.reversed()
return range
}
diff --git a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift
index 2fb1c3fb35..c1a1eec7d2 100644
--- a/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift
+++ b/apps/ios/Shared/Views/Chat/ChatScrollHelpers.swift
@@ -9,7 +9,7 @@
import SwiftUI
import SimpleXChat
-func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat, _ im: ItemsModel) async {
+func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Binding, _ chat: Chat) async {
await MainActor.run {
loadingMoreItems.wrappedValue = true
loadingBottomItems.wrappedValue = true
@@ -22,15 +22,27 @@ func loadLastItems(_ loadingMoreItems: Binding, loadingBottomItems: Bindin
}
return
}
- await apiLoadMessages(chat.chatInfo.id, im, ChatPagination.last(count: 50))
+ await apiLoadMessages(chat.chatInfo.id, ChatPagination.last(count: 50), ItemsModel.shared.chatState)
await MainActor.run {
loadingMoreItems.wrappedValue = false
loadingBottomItems.wrappedValue = false
}
}
+class PreloadState {
+ static let shared = PreloadState()
+ var prevFirstVisible: Int64 = Int64.min
+ var prevItemsCount: Int = 0
+ var preloading: Bool = false
+
+ func clear() {
+ prevFirstVisible = Int64.min
+ prevItemsCount = 0
+ preloading = false
+ }
+}
+
func preloadIfNeeded(
- _ im: ItemsModel,
_ allowLoadMoreItems: Binding,
_ ignoreLoadingRequests: Binding,
_ listState: EndlessScrollView.ListState,
@@ -38,7 +50,7 @@ func preloadIfNeeded(
loadItems: @escaping (Bool, ChatPagination) async -> Bool,
loadLastItems: @escaping () async -> Void
) {
- let state = im.preloadState
+ let state = PreloadState.shared
guard !listState.isScrolling && !listState.isAnimatedScrolling,
!state.preloading,
listState.totalItemsCount > 0
@@ -51,7 +63,7 @@ func preloadIfNeeded(
Task {
defer { state.preloading = false }
var triedToLoad = true
- await preloadItems(im, mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in
+ await preloadItems(mergedItems.boxedValue, allowLoadMore, listState, ignoreLoadingRequests) { pagination in
triedToLoad = await loadItems(false, pagination)
return triedToLoad
}
@@ -61,11 +73,11 @@ func preloadIfNeeded(
}
// it's important to ask last items when the view is fully covered with items. Otherwise, visible items from one
// split will be merged with last items and position of scroll will change unexpectedly.
- if listState.itemsCanCoverScreen && !im.lastItemsLoaded {
+ if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded {
await loadLastItems()
}
}
- } else if listState.itemsCanCoverScreen && !im.lastItemsLoaded {
+ } else if listState.itemsCanCoverScreen && !ItemsModel.shared.lastItemsLoaded {
state.preloading = true
Task {
defer { state.preloading = false }
@@ -75,7 +87,6 @@ func preloadIfNeeded(
}
func preloadItems(
- _ im: ItemsModel,
_ mergedItems: MergedItems,
_ allowLoadMoreItems: Bool,
_ listState: EndlessScrollView.ListState,
@@ -94,7 +105,7 @@ async {
let splits = mergedItems.splits
let lastVisibleIndex = listState.lastVisibleItemIndex
var lastIndexToLoadFrom: Int? = findLastIndexToLoadFromInSplits(firstVisibleIndex, lastVisibleIndex, remaining, splits)
- let items: [ChatItem] = im.reversedChatItems.reversed()
+ let items: [ChatItem] = ItemsModel.shared.reversedChatItems.reversed()
if splits.isEmpty && !items.isEmpty && lastVisibleIndex > mergedItems.items.count - remaining {
lastIndexToLoadFrom = items.count - 1
}
@@ -111,7 +122,7 @@ async {
let sizeWas = items.count
let firstItemIdWas = items.first?.id
let triedToLoad = await loadItems(ChatPagination.before(chatItemId: loadFromItemId, count: ChatPagination.PRELOAD_COUNT))
- if triedToLoad && sizeWas == im.reversedChatItems.count && firstItemIdWas == im.reversedChatItems.last?.id {
+ if triedToLoad && sizeWas == ItemsModel.shared.reversedChatItems.count && firstItemIdWas == ItemsModel.shared.reversedChatItems.last?.id {
ignoreLoadingRequests.wrappedValue = loadFromItemId
return false
}
@@ -122,7 +133,7 @@ async {
let splits = mergedItems.splits
let split = splits.last(where: { $0.indexRangeInParentItems.contains(firstVisibleIndex) })
// we're inside a splitRange (top --- [end of the splitRange --- we're here --- start of the splitRange] --- bottom)
- let reversedItems: [ChatItem] = im.reversedChatItems
+ let reversedItems: [ChatItem] = ItemsModel.shared.reversedChatItems
if let split, split.indexRangeInParentItems.lowerBound + remaining > firstVisibleIndex {
let index = split.indexRangeInReversed.lowerBound
if index >= 0 {
diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift
index 8ce0c50849..c136ebc01b 100644
--- a/apps/ios/Shared/Views/Chat/ChatView.swift
+++ b/apps/ios/Shared/Views/Chat/ChatView.swift
@@ -15,6 +15,8 @@ private let memberImageSize: CGFloat = 34
struct ChatView: View {
@EnvironmentObject var chatModel: ChatModel
+ @ObservedObject var im = ItemsModel.shared
+ @State var mergedItems: BoxedValue = BoxedValue(MergedItems.create(ItemsModel.shared.reversedChatItems, [], ItemsModel.shared.chatState))
@State var revealedItems: Set = Set()
@State var theme: AppTheme = buildTheme()
@Environment(\.dismiss) var dismiss
@@ -22,10 +24,6 @@ struct ChatView: View {
@Environment(\.presentationMode) var presentationMode
@Environment(\.scenePhase) var scenePhase
@State @ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
- @State var mergedItems: BoxedValue
- @State var floatingButtonModel: FloatingButtonModel
- @Binding var scrollToItemId: ChatItem.ID?
@State private var showChatInfoSheet: Bool = false
@State private var showAddMembersSheet: Bool = false
@State private var composeState = ComposeState()
@@ -57,14 +55,12 @@ struct ChatView: View {
@State private var allowLoadMoreItems: Bool = false
@State private var ignoreLoadingRequests: Int64? = nil
@State private var animatedScrollingInProgress: Bool = false
- @State private var showUserSupportChatSheet = false
+ @State private var floatingButtonModel: FloatingButtonModel = FloatingButtonModel()
@State private var scrollView: EndlessScrollView = EndlessScrollView(frame: .zero)
@AppStorage(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
- let userSupportScopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil)
-
var body: some View {
if #available(iOS 16.0, *) {
viewBody
@@ -77,39 +73,25 @@ struct ChatView: View {
private var viewBody: some View {
let cInfo = chat.chatInfo
- let memberSupportChat: (groupInfo: GroupInfo, member: GroupMember?)? =
- if case let .group(groupInfo, .memberSupport(member)) = cInfo {
- (groupInfo, member)
- } else {
- nil
- }
- let userMemberKnockingChat = memberSupportChat?.groupInfo.membership.memberPending == true
return ZStack {
let wallpaperImage = theme.wallpaper.type.image
let wallpaperType = theme.wallpaper.type
let backgroundColor = theme.wallpaper.background ?? wallpaperType.defaultBackgroundColor(theme.base, theme.colors.background)
let tintColor = theme.wallpaper.tint ?? wallpaperType.defaultTintColor(theme.base)
Color.clear.ignoresSafeArea(.all)
- .if(wallpaperImage != nil && im.secondaryIMFilter == nil) { view in
+ .if(wallpaperImage != nil) { view in
view.modifier(
ChatViewBackground(image: wallpaperImage!, imageType: wallpaperType, background: backgroundColor, tint: tintColor)
)
}
VStack(spacing: 0) {
ZStack(alignment: .bottomTrailing) {
- if userMemberKnockingChat {
- ZStack(alignment: .top) {
- chatItemsList()
- userMemberKnockingTitleBar()
- }
- } else {
- chatItemsList()
- }
+ chatItemsList()
if let groupInfo = chat.chatInfo.groupInfo, !composeState.message.isEmpty {
- GroupMentionsView(im: im, groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible)
+ GroupMentionsView(groupInfo: groupInfo, composeState: $composeState, selectedRange: $selectedRange, keyboardVisible: $keyboardVisible)
}
- FloatingButtons(im: im, theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: {
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ FloatingButtons(theme: theme, scrollView: scrollView, chat: chat, loadingMoreItems: $loadingMoreItems, loadingTopItems: $loadingTopItems, requestedTopScroll: $requestedTopScroll, loadingBottomItems: $loadingBottomItems, requestedBottomScroll: $requestedBottomScroll, animatedScrollingInProgress: $animatedScrollingInProgress, listState: scrollView.listState, model: floatingButtonModel, reloadItems: {
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState)
scrollView.updateItems(mergedItems.boxedValue.items)
}
)
@@ -119,7 +101,6 @@ struct ChatView: View {
let reason = chat.chatInfo.userCantSendReason
ComposeView(
chat: chat,
- im: im,
composeState: $composeState,
keyboardVisible: $keyboardVisible,
keyboardHiddenDate: $keyboardHiddenDate,
@@ -137,7 +118,7 @@ struct ChatView: View {
}
} else {
SelectedItemsBottomToolbar(
- im: im,
+ chatItems: ItemsModel.shared.reversedChatItems,
selectedChatItems: $selectedChatItems,
chatInfo: chat.chatInfo,
deleteItems: { forAll in
@@ -148,7 +129,7 @@ struct ChatView: View {
showArchiveSelectedReports = true
},
moderateItems: {
- if case let .group(groupInfo, _) = chat.chatInfo {
+ if case let .group(groupInfo) = chat.chatInfo {
showModerateSelectedMessagesAlert(groupInfo)
}
},
@@ -167,11 +148,7 @@ struct ChatView: View {
}
.background(ToolbarMaterial.material(toolbarMaterial))
}
- .navigationTitle(
- memberSupportChat == nil
- ? cInfo.chatViewName
- : memberSupportChat?.member?.chatViewName ?? NSLocalizedString("Chat with admins", comment: "chat toolbar")
- )
+ .navigationTitle(cInfo.chatViewName)
.background(theme.colors.background)
.navigationBarTitleDisplayMode(.inline)
.environmentObject(theme)
@@ -195,7 +172,7 @@ struct ChatView: View {
archiveReports(chat.chatInfo, selected.sorted(), false, deletedSelectedMessages)
}
}
- if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive {
+ if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive {
Button("For all moderators", role: .destructive) {
if let selected = selectedChatItems {
archiveReports(chat.chatInfo, selected.sorted(), true, deletedSelectedMessages)
@@ -204,20 +181,23 @@ struct ChatView: View {
}
}
.appSheet(item: $selectedMember) { member in
- if case let .group(groupInfo, _) = chat.chatInfo {
- GroupMemberInfoView(
- groupInfo: groupInfo,
- chat: chat,
- groupMember: member,
- scrollToItemId: $scrollToItemId,
- navigation: true
- )
+ Group {
+ if case let .group(groupInfo) = chat.chatInfo {
+ GroupMemberInfoView(
+ groupInfo: groupInfo,
+ chat: chat,
+ groupMember: member,
+ navigation: true
+ )
+ }
}
}
// it should be presented on top level in order to prevent a bug in SwiftUI on iOS 16 related to .focused() modifier in AddGroupMembersView's search field
.appSheet(isPresented: $showAddMembersSheet) {
- if case let .group(groupInfo, _) = cInfo {
- AddGroupMembersView(chat: chat, groupInfo: groupInfo)
+ Group {
+ if case let .group(groupInfo) = cInfo {
+ AddGroupMembersView(chat: chat, groupInfo: groupInfo)
+ }
}
}
.sheet(isPresented: Binding(
@@ -236,21 +216,6 @@ struct ChatView: View {
ChatItemForwardingView(chatItems: forwardedChatItems, fromChatInfo: chat.chatInfo, composeState: $composeState)
}
}
- .appSheet(
- isPresented: $showUserSupportChatSheet,
- onDismiss: {
- if chat.chatInfo.groupInfo?.membership.memberPending ?? false {
- chatModel.chatId = nil
- }
- }
- ) {
- if let groupInfo = cInfo.groupInfo {
- SecondaryChatView(
- chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: userSupportScopeInfo), chatItems: [], chatStats: ChatStats()),
- scrollToItemId: $scrollToItemId
- )
- }
- }
.onAppear {
scrollView.listState.onUpdateListener = onChatItemsUpdated
selectedChatItems = nil
@@ -266,14 +231,6 @@ struct ChatView: View {
}
}
}
- // if this is the main chat of the group with the pending member (knocking)
- if case let .group(groupInfo, nil) = chat.chatInfo,
- groupInfo.membership.memberPending {
- ItemsModel.loadSecondaryChat(chat.id, chatFilter: .groupChatScopeContext(groupScopeInfo: userSupportScopeInfo)) {
- showUserSupportChatSheet = true
- chatModel.secondaryPendingInviteeChatOpened = true
- }
- }
}
.onChange(of: chatModel.chatId) { cId in
showChatInfoSheet = false
@@ -288,7 +245,7 @@ struct ChatView: View {
initChatView()
theme = buildTheme()
closeSearch()
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState)
scrollView.updateItems(mergedItems.boxedValue.items)
if let openAround = chatModel.openAroundItemId, let index = mergedItems.boxedValue.indexInParentItems[openAround] {
@@ -305,15 +262,10 @@ struct ChatView: View {
dismiss()
}
}
- .onChange(of: chatModel.secondaryPendingInviteeChatOpened) { opened in
- if im.secondaryIMFilter != nil && !opened {
- dismiss()
- }
- }
.onChange(of: chatModel.openAroundItemId) { openAround in
if let openAround {
closeSearch()
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState)
scrollView.updateItems(mergedItems.boxedValue.items)
chatModel.openAroundItemId = nil
@@ -336,8 +288,9 @@ struct ChatView: View {
if chatModel.chatId == cInfo.id && !presentationMode.wrappedValue.isPresented {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) {
if chatModel.chatId == nil {
- im.reversedChatItems = []
- im.chatState.clear()
+ chatModel.chatItemStatuses = [:]
+ ItemsModel.shared.reversedChatItems = []
+ ItemsModel.shared.chatState.clear()
chatModel.groupMembers = []
chatModel.groupMembersIndexes.removeAll()
chatModel.membersLoaded = false
@@ -350,220 +303,124 @@ struct ChatView: View {
}
.toolbar {
ToolbarItem(placement: .principal) {
- if im.secondaryIMFilter == nil {
- primaryPrincipalToolbarContent()
- } else if !userMemberKnockingChat { // no toolbar while knocking chat, it's unstable on sheet
- secondaryPrincipalToolbarContent()
+ if selectedChatItems != nil {
+ SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
+ } else if case let .direct(contact) = cInfo {
+ Button {
+ Task {
+ showChatInfoSheet = true
+ }
+ } label: {
+ ChatInfoToolbar(chat: chat)
+ }
+ .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
+ ChatInfoView(
+ chat: chat,
+ contact: contact,
+ localAlias: chat.chatInfo.localAlias,
+ featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences),
+ currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences),
+ onSearch: { focusSearch() }
+ )
+ }
+ } else if case let .group(groupInfo) = cInfo {
+ Button {
+ Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
+ } label: {
+ ChatInfoToolbar(chat: chat)
+ .tint(theme.colors.primary)
+ }
+ .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
+ GroupChatInfoView(
+ chat: chat,
+ groupInfo: Binding(
+ get: { groupInfo },
+ set: { gInfo in
+ chat.chatInfo = .group(groupInfo: gInfo)
+ chat.created = Date.now
+ }
+ ),
+ onSearch: { focusSearch() },
+ localAlias: groupInfo.localAlias
+ )
+ }
+ } else if case .local = cInfo {
+ ChatInfoToolbar(chat: chat)
}
}
ToolbarItem(placement: .navigationBarTrailing) {
- if im.secondaryIMFilter == nil {
- primaryTrailingToolbarContent()
- } else if !userMemberKnockingChat {
- secondaryTrailingToolbarContent()
- }
- }
- }
- .if(im.secondaryIMFilter == nil) { v in
- v.onChange(of: scrollToItemId) { itemId in
- if let itemId = itemId {
- dismissAllSheets(animated: false) {
- scrollToItem(itemId)
- scrollToItemId = nil
- }
- }
- }
- }
- }
-
- @inline(__always)
- @ViewBuilder private func primaryPrincipalToolbarContent() -> some View {
- let cInfo = chat.chatInfo
- if selectedChatItems != nil {
- SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
- } else if case let .direct(contact) = cInfo {
- Button {
- Task {
- showChatInfoSheet = true
- }
- } label: {
- ChatInfoToolbar(chat: chat)
- }
- .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
- ChatInfoView(
- chat: chat,
- contact: contact,
- localAlias: chat.chatInfo.localAlias,
- featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences),
- currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences),
- onSearch: { focusSearch() }
- )
- }
- } else if case let .group(groupInfo, _) = cInfo {
- Button {
- Task { await chatModel.loadGroupMembers(groupInfo) { showChatInfoSheet = true } }
- } label: {
- ChatInfoToolbar(chat: chat)
- .tint(theme.colors.primary)
- }
- .appSheet(isPresented: $showChatInfoSheet, onDismiss: { theme = buildTheme() }) {
- GroupChatInfoView(
- chat: chat,
- groupInfo: Binding(
- get: { groupInfo },
- set: { gInfo in
- chat.chatInfo = .group(groupInfo: gInfo, groupChatScope: nil)
- chat.created = Date.now
+ if selectedChatItems != nil {
+ Button {
+ withAnimation {
+ selectedChatItems = nil
}
- ),
- scrollToItemId: $scrollToItemId,
- onSearch: { focusSearch() },
- localAlias: groupInfo.localAlias
- )
- }
- } else if case .local = cInfo {
- ChatInfoToolbar(chat: chat)
- }
- }
-
- @inline(__always)
- @ViewBuilder private func primaryTrailingToolbarContent() -> some View {
- let cInfo = chat.chatInfo
- if selectedChatItems != nil {
- Button {
- withAnimation {
- selectedChatItems = nil
- }
- } label: {
- Text("Cancel")
- }
- } else {
- switch cInfo {
- case let .direct(contact):
- HStack {
- let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
- if callsPrefEnabled {
- if chatModel.activeCall == nil {
- callButton(contact, .audio, imageName: "phone")
- .disabled(!contact.ready || !contact.active)
- } else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
- endCallButton(call)
- }
- }
- Menu {
- if callsPrefEnabled && chatModel.activeCall == nil {
- Button {
- CallController.shared.startCall(contact, .video)
- } label: {
- Label("Video call", systemImage: "video")
- }
- .disabled(!contact.ready || !contact.active)
- }
- searchButton()
- ToggleNtfsButton(chat: chat)
- .disabled(!contact.ready || !contact.active)
} label: {
- Image(systemName: "ellipsis")
+ Text("Cancel")
}
- }
- case let .group(groupInfo, _):
- HStack {
- if groupInfo.canAddMembers {
- if (chat.chatInfo.incognito) {
- groupLinkButton()
- .appSheet(isPresented: $showGroupLinkSheet) {
- GroupLinkView(
- groupId: groupInfo.groupId,
- groupLink: $groupLink,
- groupLinkMemberRole: $groupLinkMemberRole,
- showTitle: true,
- creatingGroup: false
- )
+ } else {
+ switch cInfo {
+ case let .direct(contact):
+ HStack {
+ let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser
+ if callsPrefEnabled {
+ if chatModel.activeCall == nil {
+ callButton(contact, .audio, imageName: "phone")
+ .disabled(!contact.ready || !contact.active)
+ } else if let call = chatModel.activeCall, call.contact.id == cInfo.id {
+ endCallButton(call)
}
- } else {
- addMembersButton()
+ }
+ Menu {
+ if callsPrefEnabled && chatModel.activeCall == nil {
+ Button {
+ CallController.shared.startCall(contact, .video)
+ } label: {
+ Label("Video call", systemImage: "video")
+ }
+ .disabled(!contact.ready || !contact.active)
+ }
+ searchButton()
+ ToggleNtfsButton(chat: chat)
+ .disabled(!contact.ready || !contact.active)
+ } label: {
+ Image(systemName: "ellipsis")
+ }
}
- }
- Menu {
+ case let .group(groupInfo):
+ HStack {
+ if groupInfo.canAddMembers {
+ if (chat.chatInfo.incognito) {
+ groupLinkButton()
+ .appSheet(isPresented: $showGroupLinkSheet) {
+ GroupLinkView(
+ groupId: groupInfo.groupId,
+ groupLink: $groupLink,
+ groupLinkMemberRole: $groupLinkMemberRole,
+ showTitle: true,
+ creatingGroup: false
+ )
+ }
+ } else {
+ addMembersButton()
+ }
+ }
+ Menu {
+ searchButton()
+ ToggleNtfsButton(chat: chat)
+ } label: {
+ Image(systemName: "ellipsis")
+ }
+ }
+ case .local:
searchButton()
- ToggleNtfsButton(chat: chat)
- } label: {
- Image(systemName: "ellipsis")
+ default:
+ EmptyView()
}
}
- case .local:
- searchButton()
- default:
- EmptyView()
}
}
}
- @inline(__always)
- @ViewBuilder private func secondaryPrincipalToolbarContent() -> some View {
- if selectedChatItems != nil {
- SelectedItemsTopToolbar(selectedChatItems: $selectedChatItems)
- } else {
- switch im.secondaryIMFilter {
- case let .groupChatScopeContext(groupScopeInfo):
- switch groupScopeInfo {
- case let .memberSupport(groupMember_):
- if let groupMember = groupMember_ {
- MemberSupportChatToolbar(groupMember: groupMember)
- } else {
- textChatToolbar("Chat with admins")
- }
- }
- case let .msgContentTagContext(contentTag):
- switch contentTag {
- case .report:
- textChatToolbar("Member reports")
- default:
- EmptyView()
- }
- case .none:
- EmptyView()
- }
- }
- }
-
- @inline(__always)
- @ViewBuilder private func secondaryTrailingToolbarContent() -> some View {
- if selectedChatItems != nil {
- Button {
- withAnimation {
- selectedChatItems = nil
- }
- } label: {
- Text("Cancel")
- }
- } else {
- searchButton()
- }
- }
-
- @inline(__always)
- private func userMemberKnockingTitleBar() -> some View {
- VStack(spacing: 0) {
- Text("Chat with admins")
- .font(.headline)
- .foregroundColor(theme.colors.onBackground)
- .padding(.top, 8)
- .padding(.bottom, 14)
- .frame(maxWidth: .infinity)
- .background(ToolbarMaterial.material(toolbarMaterial))
- Divider()
- }
- }
-
- func textChatToolbar(_ text: LocalizedStringKey) -> some View {
- Text(text)
- .font(.headline)
- .lineLimit(1)
- .foregroundColor(theme.colors.onBackground)
- .frame(width: 220)
- }
-
private func initChatView() {
let cInfo = chat.chatInfo
// This check prevents the call to apiContactInfo after the app is suspended, and the database is closed.
@@ -594,19 +451,19 @@ struct ChatView: View {
floatingButtonModel.updateOnListChange(scrollView.listState)
}
- private func scrollToItem(_ itemId: ChatItem.ID) {
+ private func scrollToItemId(_ itemId: ChatItem.ID) {
Task {
do {
var index = mergedItems.boxedValue.indexInParentItems[itemId]
if index == nil {
let pagination = ChatPagination.around(chatItemId: itemId, count: ChatPagination.PRELOAD_COUNT * 2)
- let oldSize = im.reversedChatItems.count
+ let oldSize = ItemsModel.shared.reversedChatItems.count
let triedToLoad = await loadChatItems(chat, pagination)
if !triedToLoad {
return
}
var repeatsLeft = 50
- while oldSize == im.reversedChatItems.count && repeatsLeft > 0 {
+ while oldSize == ItemsModel.shared.reversedChatItems.count && repeatsLeft > 0 {
try await Task.sleep(nanoseconds: 20_000000)
repeatsLeft -= 1
}
@@ -616,7 +473,7 @@ struct ChatView: View {
closeKeyboardAndRun {
Task {
await MainActor.run { animatedScrollingInProgress = true }
- await scrollView.scrollToItemAnimated(min(im.reversedChatItems.count - 1, index))
+ await scrollView.scrollToItemAnimated(min(ItemsModel.shared.reversedChatItems.count - 1, index))
await MainActor.run { animatedScrollingInProgress = false }
}
}
@@ -691,13 +548,11 @@ struct ChatView: View {
? (g.size.width - 32)
: (g.size.width - 32) * 0.84
return ChatItemWithMenu(
- im: im,
chat: $chat,
index: index,
isLastItem: index == mergedItems.boxedValue.items.count - 1,
chatItem: ci,
- scrollToItem: scrollToItem,
- scrollToItemId: $scrollToItemId,
+ scrollToItemId: scrollToItemId,
merged: mergedItem,
maxWidth: maxWidth,
composeState: $composeState,
@@ -725,7 +580,7 @@ struct ChatView: View {
}
}
.onChange(of: im.reversedChatItems) { items in
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ mergedItems.boxedValue = MergedItems.create(items, revealedItems, im.chatState)
scrollView.updateItems(mergedItems.boxedValue.items)
if im.itemAdded {
im.itemAdded = false
@@ -737,7 +592,7 @@ struct ChatView: View {
}
}
.onChange(of: revealedItems) { revealed in
- mergedItems.boxedValue = MergedItems.create(im, revealed)
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealed, im.chatState)
scrollView.updateItems(mergedItems.boxedValue.items)
}
.onChange(of: chat.id) { _ in
@@ -772,7 +627,7 @@ struct ChatView: View {
private func updateWithInitiallyLoadedItems() {
if mergedItems.boxedValue.items.isEmpty {
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, ItemsModel.shared.chatState)
}
let unreadIndex = mergedItems.boxedValue.items.lastIndex(where: { $0.hasUnread() })
let unreadItemId: Int64? = if let unreadIndex { mergedItems.boxedValue.items[unreadIndex].newest().item.id } else { nil }
@@ -792,8 +647,8 @@ struct ChatView: View {
private func searchTextChanged(_ s: String) {
Task {
- await loadChat(chat: chat, im: im, search: s)
- mergedItems.boxedValue = MergedItems.create(im, revealedItems)
+ await loadChat(chat: chat, search: s)
+ mergedItems.boxedValue = MergedItems.create(im.reversedChatItems, revealedItems, im.chatState)
await MainActor.run {
scrollView.updateItems(mergedItems.boxedValue.items)
}
@@ -808,8 +663,79 @@ struct ChatView: View {
}
}
+ class FloatingButtonModel: ObservableObject {
+ @Published var unreadAbove: Int = 0
+ @Published var unreadBelow: Int = 0
+ @Published var isNearBottom: Bool = true
+ @Published var date: Date? = nil
+ @Published var isDateVisible: Bool = false
+ var hideDateWorkItem: DispatchWorkItem? = nil
+
+ func updateOnListChange(_ listState: EndlessScrollView.ListState) {
+ let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(listState)
+ let unreadBelow = if let lastVisibleItem {
+ max(0, ItemsModel.shared.chatState.unreadTotal - lastVisibleItem.unreadBefore)
+ } else {
+ 0
+ }
+ let unreadAbove = ItemsModel.shared.chatState.unreadTotal - unreadBelow
+ let date: Date? =
+ if let lastVisible = listState.visibleItems.last {
+ Calendar.current.startOfDay(for: lastVisible.item.oldest().item.meta.itemTs)
+ } else {
+ nil
+ }
+
+ // set the counters and date indicator
+ DispatchQueue.main.async { [weak self] in
+ guard let it = self else { return }
+ it.setDate(visibility: true)
+ it.unreadAbove = unreadAbove
+ it.unreadBelow = unreadBelow
+ it.date = date
+ }
+
+ // set floating button indication mode
+ let nearBottom = listState.firstVisibleItemIndex < 1
+ if nearBottom != self.isNearBottom {
+ DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
+ self?.isNearBottom = nearBottom
+ }
+ }
+
+ // hide Date indicator after 1 second of no scrolling
+ hideDateWorkItem?.cancel()
+ let workItem = DispatchWorkItem { [weak self] in
+ guard let it = self else { return }
+ it.setDate(visibility: false)
+ it.hideDateWorkItem = nil
+ }
+ DispatchQueue.main.async { [weak self] in
+ self?.hideDateWorkItem = workItem
+ DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
+ }
+ }
+
+ func resetDate() {
+ date = nil
+ isDateVisible = false
+ }
+
+ private func setDate(visibility isVisible: Bool) {
+ if isVisible {
+ if !isNearBottom,
+ !isDateVisible,
+ let date, !Calendar.current.isDateInToday(date) {
+ withAnimation { self.isDateVisible = true }
+ }
+ } else if isDateVisible {
+ withAnimation { self.isDateVisible = false }
+ }
+ }
+
+ }
+
private struct FloatingButtons: View {
- @ObservedObject var im: ItemsModel
let theme: AppTheme
let scrollView: EndlessScrollView
let chat: Chat
@@ -854,7 +780,7 @@ struct ChatView: View {
.contextMenu {
Button {
Task {
- await markChatRead(im, chat)
+ await markChatRead(chat)
}
} label: {
Label("Mark read", systemImage: "checkmark")
@@ -879,7 +805,7 @@ struct ChatView: View {
}
}
.onTapGesture {
- if loadingBottomItems || !im.lastItemsLoaded {
+ if loadingBottomItems || !ItemsModel.shared.lastItemsLoaded {
requestedTopScroll = false
requestedBottomScroll = true
} else {
@@ -899,7 +825,7 @@ struct ChatView: View {
}
}
.onChange(of: loadingBottomItems) { loading in
- if !loading && requestedBottomScroll && im.lastItemsLoaded {
+ if !loading && requestedBottomScroll && ItemsModel.shared.lastItemsLoaded {
requestedBottomScroll = false
scrollToBottom()
}
@@ -909,9 +835,9 @@ struct ChatView: View {
private func scrollToTopUnread() {
Task {
- if !im.chatState.splits.isEmpty {
+ if !ItemsModel.shared.chatState.splits.isEmpty {
await MainActor.run { loadingMoreItems = true }
- await loadChat(chatId: chat.id, im: im, openAroundItemId: nil, clearItems: false)
+ await loadChat(chatId: chat.id, openAroundItemId: nil, clearItems: false)
await MainActor.run { reloadItems() }
if let index = listState.items.lastIndex(where: { $0.hasUnread() }) {
await MainActor.run { animatedScrollingInProgress = true }
@@ -1021,7 +947,7 @@ struct ChatView: View {
private func addMembersButton() -> some View {
Button {
- if case let .group(gInfo, _) = chat.chatInfo {
+ if case let .group(gInfo) = chat.chatInfo {
Task { await chatModel.loadGroupMembers(gInfo) { showAddMembersSheet = true } }
}
} label: {
@@ -1031,7 +957,7 @@ struct ChatView: View {
private func groupLinkButton() -> some View {
Button {
- if case let .group(gInfo, _) = chat.chatInfo {
+ if case let .group(gInfo) = chat.chatInfo {
Task {
do {
if let link = try apiGetGroupLink(gInfo.groupId) {
@@ -1082,7 +1008,6 @@ struct ChatView: View {
let (validItems, confirmation) = try await apiPlanForwardChatItems(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
- scope: chat.chatInfo.groupChatScope(),
itemIds: Array(selectedChatItems)
)
if let confirmation {
@@ -1172,6 +1097,7 @@ struct ChatView: View {
}
func openForwardingSheet(_ items: [Int64]) async {
+ let im = ItemsModel.shared
var items = Set(items)
var fci = [ChatItem]()
for reversedChatItem in im.reversedChatItems {
@@ -1210,11 +1136,11 @@ struct ChatView: View {
private func loadChatItemsUnchecked(_ chat: Chat, _ pagination: ChatPagination) async -> Bool {
await apiLoadMessages(
chat.chatInfo.id,
- im,
pagination,
+ im.chatState,
searchText,
nil,
- { visibleItemIndexesNonReversed(im, scrollView.listState, mergedItems.boxedValue) }
+ { visibleItemIndexesNonReversed(scrollView.listState, mergedItems.boxedValue) }
)
return true
}
@@ -1226,12 +1152,11 @@ struct ChatView: View {
func onChatItemsUpdated() {
if !mergedItems.boxedValue.isActualState() {
- //logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(im.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(im.reversedChatItems.count)")
+ //logger.debug("Items are not actual, waiting for the next update: \(String(describing: mergedItems.boxedValue.splits)) \(ItemsModel.shared.chatState.splits), \(mergedItems.boxedValue.indexInParentItems.count) vs \(ItemsModel.shared.reversedChatItems.count)")
return
}
floatingButtonModel.updateOnListChange(scrollView.listState)
preloadIfNeeded(
- im,
$allowLoadMoreItems,
$ignoreLoadingRequests,
scrollView.listState,
@@ -1245,14 +1170,13 @@ struct ChatView: View {
},
loadLastItems: {
if !loadingMoreItems {
- await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat, im)
+ await loadLastItems($loadingMoreItems, loadingBottomItems: $loadingBottomItems, chat)
}
}
)
}
private struct ChatItemWithMenu: View {
- @ObservedObject var im: ItemsModel
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
@AppStorage(DEFAULT_PROFILE_IMAGE_CORNER_RADIUS) private var profileRadius = defaultProfileImageCorner
@@ -1261,8 +1185,7 @@ struct ChatView: View {
let index: Int
let isLastItem: Bool
let chatItem: ChatItem
- let scrollToItem: (ChatItem.ID) -> Void
- @Binding var scrollToItemId: ChatItem.ID?
+ let scrollToItemId: (ChatItem.ID) -> Void
let merged: MergedItem
let maxWidth: CGFloat
@Binding var composeState: ComposeState
@@ -1338,6 +1261,8 @@ struct ChatView: View {
}
var body: some View {
+ let im = ItemsModel.shared
+
let last = isLastItem ? im.reversedChatItems.last : nil
let listItem = merged.newest()
let item = listItem.item
@@ -1381,12 +1306,12 @@ struct ChatView: View {
let (itemIds, unreadMentions) = unreadItemIds(range)
if !itemIds.isEmpty {
waitToMarkRead {
- await apiMarkChatItemsRead(im, chat.chatInfo, itemIds, mentionsRead: unreadMentions)
+ await apiMarkChatItemsRead(chat.chatInfo, itemIds, mentionsRead: unreadMentions)
}
}
} else if chatItem.isRcvNew {
waitToMarkRead {
- await apiMarkChatItemsRead(im, chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0)
+ await apiMarkChatItemsRead(chat.chatInfo, [chatItem.id], mentionsRead: chatItem.meta.userMention ? 1 : 0)
}
}
}
@@ -1408,6 +1333,7 @@ struct ChatView: View {
}
private func unreadItemIds(_ range: ClosedRange) -> ([ChatItem.ID], Int) {
+ let im = ItemsModel.shared
var unreadItems: [ChatItem.ID] = []
var unreadMentions: Int = 0
@@ -1620,10 +1546,8 @@ struct ChatView: View {
}
ChatItemView(
chat: chat,
- im: im,
chatItem: ci,
- scrollToItem: scrollToItem,
- scrollToItemId: $scrollToItemId,
+ scrollToItemId: scrollToItemId,
maxWidth: maxWidth,
allowMenu: $allowMenu
)
@@ -1663,7 +1587,7 @@ struct ChatView: View {
self.archivingReports = []
}
}
- if case let ChatInfo.group(groupInfo, _) = chat.chatInfo, groupInfo.membership.memberActive {
+ if case let ChatInfo.group(groupInfo) = chat.chatInfo, groupInfo.membership.memberActive {
Button("For all moderators", role: .destructive) {
if let reports = self.archivingReports {
archiveReports(chat.chatInfo, reports.sorted(), true)
@@ -1712,7 +1636,7 @@ struct ChatView: View {
})
}
switch chat.chatInfo {
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
v.contextMenu {
ReactionContextMenu(
groupInfo: groupInfo,
@@ -1735,7 +1659,7 @@ struct ChatView: View {
@ViewBuilder
private func menu(_ ci: ChatItem, _ range: ClosedRange?, live: Bool) -> some View {
- if case let .group(gInfo, _) = chat.chatInfo, ci.isReport, ci.meta.itemDeleted == nil {
+ if case let .group(gInfo) = chat.chatInfo, ci.isReport, ci.meta.itemDeleted == nil {
if ci.chatDir != .groupSnd, gInfo.membership.memberRole >= .moderator {
archiveReportButton(ci)
}
@@ -1794,7 +1718,7 @@ struct ChatView: View {
if let (groupInfo, _) = ci.memberToModerate(chat.chatInfo) {
moderateButton(ci, groupInfo)
} else if ci.meta.itemDeleted == nil && chat.groupFeatureEnabled(.reports),
- case let .group(gInfo, _) = chat.chatInfo,
+ case let .group(gInfo) = chat.chatInfo,
gInfo.membership.memberRole == .member
&& !live
&& composeState.voiceMessageRecordingState == .noRecording {
@@ -1905,7 +1829,6 @@ struct ChatView: View {
let chatItem = try await apiChatItemReaction(
type: cInfo.chatType,
id: cInfo.apiId,
- scope: cInfo.groupChatScope(),
itemId: ci.id,
add: add,
reaction: reaction
@@ -2019,11 +1942,11 @@ struct ChatView: View {
Task {
do {
let cInfo = chat.chatInfo
- let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, scope: cInfo.groupChatScope(), itemId: ci.id)
+ let ciInfo = try await apiGetChatItemInfo(type: cInfo.chatType, id: cInfo.apiId, itemId: ci.id)
await MainActor.run {
chatItemInfo = ciInfo
}
- if case let .group(gInfo, _) = chat.chatInfo {
+ if case let .group(gInfo) = chat.chatInfo {
await m.loadGroupMembers(gInfo)
}
} catch let error {
@@ -2077,13 +2000,13 @@ struct ChatView: View {
private func deleteButton(_ ci: ChatItem, label: LocalizedStringKey = "Delete") -> Button {
Button(role: .destructive) {
if !revealed,
- let currIndex = m.getChatItemIndex(im, ci),
+ let currIndex = m.getChatItemIndex(ci),
let ciCategory = ci.mergeCategory {
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
if let range = itemsRange(currIndex, prevHidden) {
var itemIds: [Int64] = []
for i in range {
- itemIds.append(im.reversedChatItems[i].id)
+ itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
}
showDeleteMessages = true
deletingItems = itemIds
@@ -2221,12 +2144,12 @@ struct ChatView: View {
selectedChatItems = selectedChatItems ?? []
var itemIds: [Int64] = []
if !revealed,
- let currIndex = m.getChatItemIndex(im, ci),
+ let currIndex = m.getChatItemIndex(ci),
let ciCategory = ci.mergeCategory {
let (prevHidden, _) = m.getPrevShownChatItem(currIndex, ciCategory)
if let range = itemsRange(currIndex, prevHidden) {
for i in range {
- itemIds.append(im.reversedChatItems[i].id)
+ itemIds.append(ItemsModel.shared.reversedChatItems[i].id)
}
} else {
itemIds.append(ci.id)
@@ -2260,7 +2183,6 @@ struct ChatView: View {
try await apiDeleteChatItems(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
- scope: chat.chatInfo.groupChatScope(),
itemIds: [di.id],
mode: mode
)
@@ -2277,7 +2199,6 @@ struct ChatView: View {
if deletedItem.isActiveReport {
m.decreaseGroupReportsCounter(chat.chatInfo.id)
}
- m.updateChatInfo(itemDeletion.deletedChatItem.chatInfo)
}
}
}
@@ -2316,14 +2237,14 @@ struct ChatView: View {
if searchIsNotBlank {
goToItemInnerButton(alignStart, "magnifyingglass", touchInProgress: touchInProgress) {
closeKeyboardAndRun {
- im.loadOpenChatNoWait(chat.id, chatItem.id)
+ ItemsModel.shared.loadOpenChatNoWait(chat.id, chatItem.id)
}
}
} else if let chatTypeApiIdMsgId {
goToItemInnerButton(alignStart, "arrow.right", touchInProgress: touchInProgress) {
closeKeyboardAndRun {
let (chatType, apiId, msgId) = chatTypeApiIdMsgId
- im.loadOpenChatNoWait("\(chatType.rawValue)\(apiId)", msgId)
+ ItemsModel.shared.loadOpenChatNoWait("\(chatType.rawValue)\(apiId)", msgId)
}
}
}
@@ -2350,84 +2271,6 @@ struct ChatView: View {
}
}
-class FloatingButtonModel: ObservableObject {
- @ObservedObject var im: ItemsModel
-
- public init(im: ItemsModel) {
- self.im = im
- }
-
- @Published var unreadAbove: Int = 0
- @Published var unreadBelow: Int = 0
- @Published var isNearBottom: Bool = true
- @Published var date: Date? = nil
- @Published var isDateVisible: Bool = false
- var hideDateWorkItem: DispatchWorkItem? = nil
-
- func updateOnListChange(_ listState: EndlessScrollView.ListState) {
- let lastVisibleItem = oldestPartiallyVisibleListItemInListStateOrNull(listState)
- let unreadBelow = if let lastVisibleItem {
- max(0, im.chatState.unreadTotal - lastVisibleItem.unreadBefore)
- } else {
- 0
- }
- let unreadAbove = im.chatState.unreadTotal - unreadBelow
- let date: Date? =
- if let lastVisible = listState.visibleItems.last {
- Calendar.current.startOfDay(for: lastVisible.item.oldest().item.meta.itemTs)
- } else {
- nil
- }
-
- // set the counters and date indicator
- DispatchQueue.main.async { [weak self] in
- guard let it = self else { return }
- it.setDate(visibility: true)
- it.unreadAbove = unreadAbove
- it.unreadBelow = unreadBelow
- it.date = date
- }
-
- // set floating button indication mode
- let nearBottom = listState.firstVisibleItemIndex < 1
- if nearBottom != self.isNearBottom {
- DispatchQueue.main.asyncAfter(deadline: .now() + 0.35) { [weak self] in
- self?.isNearBottom = nearBottom
- }
- }
-
- // hide Date indicator after 1 second of no scrolling
- hideDateWorkItem?.cancel()
- let workItem = DispatchWorkItem { [weak self] in
- guard let it = self else { return }
- it.setDate(visibility: false)
- it.hideDateWorkItem = nil
- }
- DispatchQueue.main.async { [weak self] in
- self?.hideDateWorkItem = workItem
- DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: workItem)
- }
- }
-
- func resetDate() {
- date = nil
- isDateVisible = false
- }
-
- private func setDate(visibility isVisible: Bool) {
- if isVisible {
- if !isNearBottom,
- !isDateVisible,
- let date, !Calendar.current.isDateInToday(date) {
- withAnimation { self.isDateVisible = true }
- }
- } else if isDateVisible {
- withAnimation { self.isDateVisible = false }
- }
- }
-
-}
-
private func broadcastDeleteButtonText(_ chat: Chat) -> LocalizedStringKey {
chat.chatInfo.featureEnabled(.fullDelete) ? "Delete for everyone" : "Mark deleted for everyone"
}
@@ -2449,7 +2292,6 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe
try await apiDeleteChatItems(
type: chatInfo.chatType,
id: chatInfo.apiId,
- scope: chatInfo.groupChatScope(),
itemIds: itemIds,
mode: mode
)
@@ -2467,9 +2309,6 @@ private func deleteMessages(_ chat: Chat, _ deletingItems: [Int64], _ mode: CIDe
ChatModel.shared.decreaseGroupReportsCounter(chat.chatInfo.id)
}
}
- if let updatedChatInfo = deletedItems.last?.deletedChatItem.chatInfo {
- ChatModel.shared.updateChatInfo(updatedChatInfo)
- }
}
await onSuccess()
} catch {
@@ -2501,9 +2340,6 @@ func archiveReports(_ chatInfo: ChatInfo, _ itemIds: [Int64], _ forAll: Bool, _
ChatModel.shared.decreaseGroupReportsCounter(chatInfo.id)
}
}
- if let updatedChatInfo = deleted.last?.deletedChatItem.chatInfo {
- ChatModel.shared.updateChatInfo(updatedChatInfo)
- }
}
await onSuccess()
} catch {
@@ -2517,7 +2353,7 @@ private func buildTheme() -> AppTheme {
if let cId = ChatModel.shared.chatId, let chat = ChatModel.shared.getChat(cId) {
let perChatTheme = if case let .direct(contact) = chat.chatInfo {
contact.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight)
- } else if case let .group(groupInfo, _) = chat.chatInfo {
+ } else if case let .group(groupInfo) = chat.chatInfo {
groupInfo.uiThemes?.preferredMode(!AppTheme.shared.colors.isLight)
} else {
nil as ThemeModeOverride?
@@ -2670,7 +2506,7 @@ func updateChatSettings(_ chat: Chat, chatSettings: ChatSettings) {
case var .direct(contact):
contact.chatSettings = chatSettings
ChatModel.shared.updateContact(contact)
- case var .group(groupInfo, _):
+ case var .group(groupInfo):
groupInfo.chatSettings = chatSettings
ChatModel.shared.updateGroup(groupInfo)
default: ()
@@ -2687,8 +2523,7 @@ struct ChatView_Previews: PreviewProvider {
static var previews: some View {
let chatModel = ChatModel()
chatModel.chatId = "@1"
- let im = ItemsModel.shared
- im.reversedChatItems = [
+ ItemsModel.shared.reversedChatItems = [
ChatItem.getSample(1, .directSnd, .now, "hello"),
ChatItem.getSample(2, .directRcv, .now, "hi"),
ChatItem.getSample(3, .directRcv, .now, "hi there"),
@@ -2700,13 +2535,7 @@ struct ChatView_Previews: PreviewProvider {
ChatItem.getSample(9, .directSnd, .now, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
]
@State var showChatInfo = false
- return ChatView(
- chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []),
- im: im,
- mergedItems: BoxedValue(MergedItems.create(im, [])),
- floatingButtonModel: FloatingButtonModel(im: im),
- scrollToItemId: Binding.constant(nil)
- )
- .environmentObject(chatModel)
+ return ChatView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
+ .environmentObject(chatModel)
}
}
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
index 68a2f6d7b1..8993de886f 100644
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
+++ b/apps/ios/Shared/Views/Chat/ComposeMessage/ComposeView.swift
@@ -323,7 +323,6 @@ struct ComposeView: View {
@EnvironmentObject var chatModel: ChatModel
@EnvironmentObject var theme: AppTheme
@ObservedObject var chat: Chat
- @ObservedObject var im: ItemsModel
@Binding var composeState: ComposeState
@Binding var keyboardVisible: Bool
@Binding var keyboardHiddenDate: Date
@@ -357,20 +356,6 @@ struct ComposeView: View {
var body: some View {
VStack(spacing: 0) {
Divider()
- if let groupInfo = chat.chatInfo.groupInfo,
- case let .groupChatScopeContext(groupScopeInfo) = im.secondaryIMFilter,
- case let .memberSupport(member) = groupScopeInfo,
- let member = member,
- member.memberPending,
- composeState.contextItem == .noContextItem,
- composeState.noPreview {
- ContextPendingMemberActionsView(
- groupInfo: groupInfo,
- member: member
- )
- Divider()
- }
-
if chat.chatInfo.contact?.nextSendGrpInv ?? false {
ContextInvitingContactMemberView()
Divider()
@@ -381,8 +366,8 @@ struct ComposeView: View {
Divider()
}
// preference checks should match checks in forwarding list
- let simplexLinkProhibited = im.secondaryIMFilter == nil && hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
- let fileProhibited = im.secondaryIMFilter == nil && composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
+ let simplexLinkProhibited = hasSimplexLink && !chat.groupFeatureEnabled(.simplexLinks)
+ let fileProhibited = composeState.attachmentPreview && !chat.groupFeatureEnabled(.files)
let voiceProhibited = composeState.voicePreview && !chat.chatInfo.featureEnabled(.voice)
if simplexLinkProhibited {
msgNotAllowedView("SimpleX links not allowed", icon: "link")
@@ -412,8 +397,7 @@ struct ComposeView: View {
.padding(.bottom, 16)
.padding(.leading, 12)
.tint(theme.colors.primary)
- if im.secondaryIMFilter == nil,
- case let .group(g, _) = chat.chatInfo,
+ if case let .group(g) = chat.chatInfo,
!g.fullGroupPreferences.files.on(for: g.membership) {
b.disabled(true).onTapGesture {
AlertManager.shared.showAlertMsg(
@@ -454,8 +438,8 @@ 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.chatInfo.sendMsgEnabled)
@@ -955,7 +939,6 @@ struct ComposeView: View {
let chatItem = try await apiUpdateChatItem(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
- scope: chat.chatInfo.groupChatScope(),
itemId: ei.id,
updatedMessage: UpdatedMessage(msgContent: mc, mentions: composeState.memberMentions),
live: live
@@ -1018,9 +1001,9 @@ struct ComposeView: View {
reportReason: reportReason,
reportText: msgText
) {
- if showReportsInSupportChatAlertDefault.get() {
- await MainActor.run {
- showReportsInSupportChatAlert()
+ await MainActor.run {
+ for chatItem in chatItems {
+ chatModel.addChatItem(chat.chatInfo, chatItem)
}
}
return chatItems.first
@@ -1028,27 +1011,7 @@ struct ComposeView: View {
return nil
}
-
- func showReportsInSupportChatAlert() {
- showAlert(
- NSLocalizedString("Report sent to moderators", comment: "alert title"),
- message: NSLocalizedString("You can view your reports in Chat with admins.", comment: "alert message"),
- actions: {[
- UIAlertAction(
- title: NSLocalizedString("Don't show again", comment: "alert action"),
- style: .default,
- handler: { _ in
- showReportsInSupportChatAlertDefault.set(false)
- }
- ),
- UIAlertAction(
- title: NSLocalizedString("Ok", comment: "alert action"),
- style: .default
- )
- ]}
- )
- }
-
+
func send(_ mc: MsgContent, quoted: Int64?, file: CryptoFile? = nil, live: Bool = false, ttl: Int?, mentions: [String: Int64]) async -> ChatItem? {
await send(
[ComposedMessage(fileSource: file, quotedItemId: quoted, msgContent: mc, mentions: mentions)],
@@ -1063,7 +1026,6 @@ struct ComposeView: View {
: await apiSendMessages(
type: chat.chatInfo.chatType,
id: chat.chatInfo.apiId,
- scope: chat.chatInfo.groupChatScope(),
live: live,
ttl: ttl,
composedMessages: msgs
@@ -1088,10 +1050,8 @@ struct ComposeView: View {
if let chatItems = await apiForwardChatItems(
toChatType: chat.chatInfo.chatType,
toChatId: chat.chatInfo.apiId,
- toScope: chat.chatInfo.groupChatScope(),
fromChatType: fromChatInfo.chatType,
fromChatId: fromChatInfo.apiId,
- fromScope: fromChatInfo.groupChatScope(),
itemIds: forwardedItems.map { $0.id },
ttl: ttl
) {
@@ -1312,14 +1272,12 @@ struct ComposeView: View {
struct ComposeView_Previews: PreviewProvider {
static var previews: some View {
let chat = Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: [])
- let im = ItemsModel.shared
@State var composeState = ComposeState(message: "hello")
@State var selectedRange = NSRange()
return Group {
ComposeView(
chat: chat,
- im: im,
composeState: $composeState,
keyboardVisible: Binding.constant(true),
keyboardHiddenDate: Binding.constant(Date.now),
@@ -1328,7 +1286,6 @@ struct ComposeView_Previews: PreviewProvider {
.environmentObject(ChatModel())
ComposeView(
chat: chat,
- im: im,
composeState: $composeState,
keyboardVisible: Binding.constant(true),
keyboardHiddenDate: Binding.constant(Date.now),
diff --git a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift b/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift
deleted file mode 100644
index 96915b342f..0000000000
--- a/apps/ios/Shared/Views/Chat/ComposeMessage/ContextPendingMemberActionsView.swift
+++ /dev/null
@@ -1,108 +0,0 @@
-//
-// ContextPendingMemberActionsView.swift
-// SimpleX (iOS)
-//
-// Created by spaced4ndy on 02.05.2025.
-// Copyright © 2025 SimpleX Chat. All rights reserved.
-//
-
-import SwiftUI
-import SimpleXChat
-
-struct ContextPendingMemberActionsView: View {
- @EnvironmentObject var theme: AppTheme
- @Environment(\.dismiss) var dismiss
- var groupInfo: GroupInfo
- var member: GroupMember
-
- var body: some View {
- HStack(spacing: 0) {
- ZStack {
- Text("Reject")
- .foregroundColor(.red)
- }
- .frame(maxWidth: .infinity)
- .contentShape(Rectangle())
- .onTapGesture {
- showRejectMemberAlert(groupInfo, member, dismiss: dismiss)
- }
-
- ZStack {
- Text("Accept")
- .foregroundColor(theme.colors.primary)
- }
- .frame(maxWidth: .infinity)
- .contentShape(Rectangle())
- .onTapGesture {
- showAcceptMemberAlert(groupInfo, member, dismiss: dismiss)
- }
- }
- .frame(minHeight: 54)
- .frame(maxWidth: .infinity)
- .background(.thinMaterial)
- }
-}
-
-func showRejectMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) {
- showAlert(
- title: NSLocalizedString("Reject member?", comment: "alert title"),
- buttonTitle: "Reject",
- buttonAction: { removeMember(groupInfo, member, dismiss: dismiss) },
- cancelButton: true
- )
-}
-
-func showAcceptMemberAlert(_ groupInfo: GroupInfo, _ member: GroupMember, dismiss: DismissAction? = nil) {
- showAlert(
- NSLocalizedString("Accept member", comment: "alert title"),
- message: NSLocalizedString("Member will join the group, accept member?", comment: "alert message"),
- actions: {[
- UIAlertAction(
- title: NSLocalizedString("Accept as member", comment: "alert action"),
- style: .default,
- handler: { _ in
- acceptMember(groupInfo, member, .member, dismiss: dismiss)
- }
- ),
- UIAlertAction(
- title: NSLocalizedString("Accept as observer", comment: "alert action"),
- style: .default,
- handler: { _ in
- acceptMember(groupInfo, member, .observer, dismiss: dismiss)
- }
- ),
- UIAlertAction(
- title: NSLocalizedString("Cancel", comment: "alert action"),
- style: .default
- )
- ]}
- )
-}
-
-func acceptMember(_ groupInfo: GroupInfo, _ member: GroupMember, _ role: GroupMemberRole, dismiss: DismissAction? = nil) {
- Task {
- do {
- let (gInfo, acceptedMember) = try await apiAcceptMember(groupInfo.groupId, member.groupMemberId, role)
- await MainActor.run {
- _ = ChatModel.shared.upsertGroupMember(gInfo, acceptedMember)
- ChatModel.shared.updateGroup(gInfo)
- dismiss?()
- }
- } catch let error {
- logger.error("apiAcceptMember error: \(responseError(error))")
- await MainActor.run {
- showAlert(
- NSLocalizedString("Error accepting member", comment: "alert title"),
- message: responseError(error)
- )
- }
- }
- }
-}
-
-#Preview {
- ContextPendingMemberActionsView(
- groupInfo: GroupInfo.sampleData,
- member: GroupMember.sampleData
- )
-}
diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
index 3154f16f5b..7cd543af10 100644
--- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift
@@ -78,12 +78,6 @@ struct AddGroupMembersViewCommon: View {
let count = selectedContacts.count
Section {
if creatingGroup {
- MemberAdmissionButton(
- groupInfo: $groupInfo,
- admission: groupInfo.groupProfile.memberAdmission_,
- currentAdmission: groupInfo.groupProfile.memberAdmission_,
- creatingGroup: true
- )
GroupPreferencesButton(
groupInfo: $groupInfo,
preferences: groupInfo.fullGroupPreferences,
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
index 4218e94224..15749b0761 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift
@@ -17,7 +17,6 @@ struct GroupChatInfoView: View {
@Environment(\.dismiss) var dismiss: DismissAction
@ObservedObject var chat: Chat
@Binding var groupInfo: GroupInfo
- @Binding var scrollToItemId: ChatItem.ID?
var onSearch: () -> Void
@State var localAlias: String
@FocusState private var aliasTextFieldFocused: Bool
@@ -88,25 +87,7 @@ struct GroupChatInfoView: View {
.listRowBackground(Color.clear)
.listRowSeparator(.hidden)
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
-
- Section {
- if groupInfo.canAddMembers && groupInfo.businessChat == nil {
- groupLinkButton()
- }
- if groupInfo.businessChat == nil && groupInfo.membership.memberRole >= .moderator {
- memberSupportButton()
- }
- if groupInfo.canModerate {
- GroupReportsChatNavLink(chat: chat, scrollToItemId: $scrollToItemId)
- }
- if groupInfo.membership.memberActive
- && (groupInfo.membership.memberRole < .moderator || groupInfo.membership.supportChat != nil) {
- UserSupportChatNavLink(chat: chat, groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
- }
- } header: {
- Text("")
- }
-
+
Section {
if groupInfo.isOwner && groupInfo.businessChat == nil {
editGroupButton()
@@ -115,6 +96,19 @@ struct GroupChatInfoView: View {
addOrEditWelcomeMessage()
}
GroupPreferencesButton(groupInfo: $groupInfo, preferences: groupInfo.fullGroupPreferences, currentPreferences: groupInfo.fullGroupPreferences)
+ if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
+ sendReceiptsOption()
+ } else {
+ sendReceiptsOptionDisabled()
+ }
+
+ NavigationLink {
+ ChatWallpaperEditorSheet(chat: chat)
+ } label: {
+ Label("Chat theme", systemImage: "photo")
+ }
+ } header: {
+ Text("")
} footer: {
let label: LocalizedStringKey = (
groupInfo.businessChat == nil
@@ -126,16 +120,6 @@ struct GroupChatInfoView: View {
}
Section {
- if members.filter({ $0.wrapped.memberCurrent }).count <= SMALL_GROUPS_RCPS_MEM_LIMIT {
- sendReceiptsOption()
- } else {
- sendReceiptsOptionDisabled()
- }
- NavigationLink {
- ChatWallpaperEditorSheet(chat: chat)
- } label: {
- Label("Chat theme", systemImage: "photo")
- }
ChatTTLOption(chat: chat, progressIndicator: $progressIndicator)
} footer: {
Text("Delete chat messages from your device.")
@@ -143,6 +127,9 @@ struct GroupChatInfoView: View {
Section(header: Text("\(members.count + 1) members").foregroundColor(theme.colors.secondary)) {
if groupInfo.canAddMembers {
+ if groupInfo.businessChat == nil {
+ groupLinkButton()
+ }
if (chat.chatInfo.incognito) {
Label("Invite members", systemImage: "plus")
.foregroundColor(Color(uiColor: .tertiaryLabel))
@@ -157,16 +144,9 @@ struct GroupChatInfoView: View {
let filteredMembers = s == ""
? members
: members.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
- MemberRowView(
- chat: chat,
- groupInfo: groupInfo,
- groupMember: GMember(groupInfo.membership),
- scrollToItemId: $scrollToItemId,
- user: true,
- alert: $alert
- )
+ MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: GMember(groupInfo.membership), user: true, alert: $alert)
ForEach(filteredMembers) { member in
- MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, scrollToItemId: $scrollToItemId, alert: $alert)
+ MemberRowView(chat: chat, groupInfo: groupInfo, groupMember: member, alert: $alert)
}
}
@@ -175,7 +155,7 @@ struct GroupChatInfoView: View {
if groupInfo.canDelete {
deleteGroupButton()
}
- if groupInfo.membership.memberCurrentOrPending {
+ if groupInfo.membership.memberCurrent {
leaveGroupButton()
}
}
@@ -373,7 +353,6 @@ struct GroupChatInfoView: View {
var chat: Chat
var groupInfo: GroupInfo
@ObservedObject var groupMember: GMember
- @Binding var scrollToItemId: ChatItem.ID?
@EnvironmentObject var theme: AppTheme
var user: Bool = false
@Binding var alert: GroupChatInfoViewAlert?
@@ -436,7 +415,7 @@ struct GroupChatInfoView: View {
}
private func memberInfoView() -> some View {
- GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember, scrollToItemId: $scrollToItemId)
+ GroupMemberInfoView(groupInfo: groupInfo, chat: chat, groupMember: groupMember)
.navigationBarHidden(false)
}
@@ -544,89 +523,6 @@ struct GroupChatInfoView: View {
.navigationBarTitleDisplayMode(.large)
}
- struct UserSupportChatNavLink: View {
- @ObservedObject var chat: Chat
- @EnvironmentObject var theme: AppTheme
- var groupInfo: GroupInfo
- @EnvironmentObject var chatModel: ChatModel
- @Binding var scrollToItemId: ChatItem.ID?
- @State private var navLinkActive = false
-
- var body: some View {
- let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: nil)
- NavigationLink(isActive: $navLinkActive) {
- SecondaryChatView(
- chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
- scrollToItemId: $scrollToItemId
- )
- } label: {
- HStack {
- Label("Chat with admins", systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag")
- Spacer()
- if chat.supportUnreadCount > 0 {
- UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary)
- }
- }
- }
- .onChange(of: navLinkActive) { active in
- if active {
- ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo))
- }
- }
- }
- }
-
- private func memberSupportButton() -> some View {
- NavigationLink {
- MemberSupportView(groupInfo: groupInfo, scrollToItemId: $scrollToItemId)
- .navigationBarTitle("Chats with members")
- .modifier(ThemedBackground())
- .navigationBarTitleDisplayMode(.large)
- } label: {
- HStack {
- Label(
- "Chats with members",
- systemImage: chat.supportUnreadCount > 0 ? "flag.fill" : "flag"
- )
- Spacer()
- if chat.supportUnreadCount > 0 {
- UnreadBadge(count: chat.supportUnreadCount, color: theme.colors.primary)
- }
- }
- }
- }
-
- struct GroupReportsChatNavLink: View {
- @EnvironmentObject var chatModel: ChatModel
- @EnvironmentObject var theme: AppTheme
- @State private var navLinkActive = false
- @ObservedObject var chat: Chat
- @Binding var scrollToItemId: ChatItem.ID?
-
- var body: some View {
- NavigationLink(isActive: $navLinkActive) {
- SecondaryChatView(chat: chat, scrollToItemId: $scrollToItemId)
- } label: {
- HStack {
- Label {
- Text("Member reports")
- } icon: {
- Image(systemName: chat.chatStats.reportsCount > 0 ? "flag.fill" : "flag").foregroundColor(.red)
- }
- Spacer()
- if chat.chatStats.reportsCount > 0 {
- UnreadBadge(count: chat.chatStats.reportsCount, color: .red)
- }
- }
- }
- .onChange(of: navLinkActive) { active in
- if active {
- ItemsModel.loadSecondaryChat(chat.id, chatFilter: .msgContentTagContext(contentTag: .report))
- }
- }
- }
- }
-
private func editGroupButton() -> some View {
NavigationLink {
GroupProfileView(
@@ -787,36 +683,26 @@ struct GroupChatInfoView: View {
title: Text("Remove member?"),
message: Text(messageLabel),
primaryButton: .destructive(Text("Remove")) {
- removeMember(groupInfo, mem)
+ Task {
+ do {
+ let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
+ await MainActor.run {
+ updatedMembers.forEach { updatedMember in
+ _ = chatModel.upsertGroupMember(groupInfo, updatedMember)
+ }
+ }
+ } catch let error {
+ logger.error("apiRemoveMembers error: \(responseError(error))")
+ let a = getErrorAlert(error, "Error removing member")
+ alert = .error(title: a.title, error: a.message)
+ }
+ }
},
secondaryButton: .cancel()
)
}
}
-func removeMember(_ groupInfo: GroupInfo, _ mem: GroupMember, dismiss: DismissAction? = nil) {
- Task {
- do {
- let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
- await MainActor.run {
- ChatModel.shared.updateGroup(updatedGroupInfo)
- updatedMembers.forEach { updatedMember in
- _ = ChatModel.shared.upsertGroupMember(updatedGroupInfo, updatedMember)
- }
- dismiss?()
- }
- } catch let error {
- logger.error("apiRemoveMembers error: \(responseError(error))")
- await MainActor.run {
- showAlert(
- NSLocalizedString("Error removing member", comment: "alert title"),
- message: responseError(error)
- )
- }
- }
- }
-}
-
func deleteGroupAlertMessage(_ groupInfo: GroupInfo) -> Text {
groupInfo.businessChat == nil ? (
groupInfo.membership.memberCurrent ? Text("Group will be deleted for all members - this cannot be undone!") : Text("Group will be deleted for you - this cannot be undone!")
@@ -910,7 +796,6 @@ struct GroupChatInfoView_Previews: PreviewProvider {
GroupChatInfoView(
chat: Chat(chatInfo: ChatInfo.sampleData.group, chatItems: []),
groupInfo: Binding.constant(GroupInfo.sampleData),
- scrollToItemId: Binding.constant(nil),
onSearch: {},
localAlias: ""
)
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
index fa7fc7cae4..79ad242366 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift
@@ -16,7 +16,6 @@ struct GroupMemberInfoView: View {
@State var groupInfo: GroupInfo
@ObservedObject var chat: Chat
@ObservedObject var groupMember: GMember
- @Binding var scrollToItemId: ChatItem.ID?
var navigation: Bool = false
@State private var connectionStats: ConnectionStats? = nil
@State private var connectionCode: String? = nil
@@ -104,10 +103,6 @@ struct GroupMemberInfoView: View {
if member.memberActive {
Section {
- if groupInfo.membership.memberRole >= .moderator
- && (member.memberRole < .moderator || member.supportChat != nil) {
- MemberInfoSupportChatNavLink(groupInfo: groupInfo, member: groupMember, scrollToItemId: $scrollToItemId)
- }
if let code = connectionCode { verifyCodeButton(code) }
if let connStats = connectionStats,
connStats.ratchetSyncAllowed {
@@ -283,7 +278,7 @@ struct GroupMemberInfoView: View {
}
}
.onChange(of: chat.chatInfo) { c in
- if case let .group(gI, _) = chat.chatInfo {
+ if case let .group(gI) = chat.chatInfo {
groupInfo = gI
}
}
@@ -479,31 +474,6 @@ struct GroupMemberInfoView: View {
.frame(maxWidth: .infinity, alignment: .center)
}
- struct MemberInfoSupportChatNavLink: View {
- @EnvironmentObject var theme: AppTheme
- var groupInfo: GroupInfo
- var member: GMember
- @Binding var scrollToItemId: ChatItem.ID?
- @State private var navLinkActive = false
-
- var body: some View {
- let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: member.wrapped)
- NavigationLink(isActive: $navLinkActive) {
- SecondaryChatView(
- chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
- scrollToItemId: $scrollToItemId
- )
- } label: {
- Label("Chat with member", systemImage: "flag")
- }
- .onChange(of: navLinkActive) { active in
- if active {
- ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo))
- }
- }
- }
- }
-
private func verifyCodeButton(_ code: String) -> some View {
let member = groupMember.wrapped
return NavigationLink {
@@ -640,11 +610,10 @@ struct GroupMemberInfoView: View {
primaryButton: .destructive(Text("Remove")) {
Task {
do {
- let (updatedGroupInfo, updatedMembers) = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
+ let updatedMembers = try await apiRemoveMembers(groupInfo.groupId, [mem.groupMemberId])
await MainActor.run {
- chatModel.updateGroup(updatedGroupInfo)
updatedMembers.forEach { updatedMember in
- _ = chatModel.upsertGroupMember(updatedGroupInfo, updatedMember)
+ _ = chatModel.upsertGroupMember(groupInfo, updatedMember)
}
dismiss()
}
@@ -852,8 +821,7 @@ struct GroupMemberInfoView_Previews: PreviewProvider {
GroupMemberInfoView(
groupInfo: GroupInfo.sampleData,
chat: Chat.sampleData,
- groupMember: GMember.sampleData,
- scrollToItemId: Binding.constant(nil)
+ groupMember: GMember.sampleData
)
}
}
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
index 07cc7bd217..9bb4a0cc35 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupMentions.swift
@@ -17,7 +17,6 @@ let MAX_VISIBLE_MEMBER_ROWS: CGFloat = 4.8
struct GroupMentionsView: View {
@EnvironmentObject var m: ChatModel
@EnvironmentObject var theme: AppTheme
- var im: ItemsModel
var groupInfo: GroupInfo
@Binding var composeState: ComposeState
@Binding var selectedRange: NSRange
@@ -94,31 +93,12 @@ struct GroupMentionsView: View {
currentMessage = composeState.message
}
}
-
- func contextMemberFilter(_ member: GroupMember) -> Bool {
- switch im.secondaryIMFilter {
- case nil:
- return true
- case let .groupChatScopeContext(groupScopeInfo):
- switch (groupScopeInfo) {
- case let .memberSupport(groupMember_):
- if let scopeMember = groupMember_ {
- return member.memberRole >= .moderator || member.groupMemberId == scopeMember.groupMemberId
- } else {
- return member.memberRole >= .moderator
- }
- }
- case .msgContentTagContext:
- return false
- }
- }
-
+
private func filteredMembers() -> [GMember] {
let s = mentionName.lowercased()
- return sortedMembers.filter {
- contextMemberFilter($0.wrapped)
- && (s.isEmpty || $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s))
- }
+ return s.isEmpty
+ ? sortedMembers
+ : sortedMembers.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
}
private func messageChanged(_ msg: String, _ parsedMsg: [FormattedText], _ range: NSRange) {
diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift
index 55b1dc6d2e..ed39c401ce 100644
--- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift
+++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift
@@ -30,14 +30,6 @@ struct GroupPreferencesView: View {
let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members"
VStack {
List {
- Section {
- MemberAdmissionButton(
- groupInfo: $groupInfo,
- admission: groupInfo.groupProfile.memberAdmission_,
- currentAdmission: groupInfo.groupProfile.memberAdmission_,
- creatingGroup: creatingGroup
- )
- }
featureSection(.timedMessages, $preferences.timedMessages.enable)
featureSection(.fullDelete, $preferences.fullDelete.enable)
featureSection(.directMessages, $preferences.directMessages.enable, $preferences.directMessages.role)
@@ -148,66 +140,6 @@ struct GroupPreferencesView: View {
}
}
-struct MemberAdmissionButton: View {
- @Binding var groupInfo: GroupInfo
- @State var admission: GroupMemberAdmission
- @State var currentAdmission: GroupMemberAdmission
- var creatingGroup: Bool = false
-
- var body: some View {
- NavigationLink {
- MemberAdmissionView(
- groupInfo: $groupInfo,
- admission: $admission,
- currentAdmission: currentAdmission,
- creatingGroup: creatingGroup,
- saveAdmission: saveAdmission
- )
- .navigationBarTitle("Member admission")
- .modifier(ThemedBackground(grouped: true))
- .navigationBarTitleDisplayMode(.large)
- .onDisappear {
- let saveText = NSLocalizedString(
- creatingGroup ? "Save" : "Save and notify group members",
- comment: "alert button"
- )
-
- if groupInfo.groupProfile.memberAdmission_ != admission {
- showAlert(
- title: NSLocalizedString("Save admission settings?", comment: "alert title"),
- buttonTitle: saveText,
- buttonAction: { saveAdmission() },
- cancelButton: true
- )
- }
- }
- } label: {
- if creatingGroup {
- Text("Set member admission")
- } else {
- Label("Member admission", systemImage: "switch.2")
- }
- }
- }
-
- private func saveAdmission() {
- Task {
- do {
- var gp = groupInfo.groupProfile
- gp.memberAdmission = admission
- let gInfo = try await apiUpdateGroup(groupInfo.groupId, gp)
- await MainActor.run {
- groupInfo = gInfo
- ChatModel.shared.updateGroup(gInfo)
- currentAdmission = admission
- }
- } catch {
- logger.error("MemberAdmissionView apiUpdateGroup error: \(responseError(error))")
- }
- }
- }
-}
-
struct GroupPreferencesView_Previews: PreviewProvider {
static var previews: some View {
GroupPreferencesView(
diff --git a/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift b/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift
deleted file mode 100644
index d80615b5d2..0000000000
--- a/apps/ios/Shared/Views/Chat/Group/MemberAdmissionView.swift
+++ /dev/null
@@ -1,93 +0,0 @@
-//
-// MemberAdmissionView.swift
-// SimpleX (iOS)
-//
-// Created by spaced4ndy on 28.04.2025.
-// Copyright © 2025 SimpleX Chat. All rights reserved.
-//
-
-import SwiftUI
-import SimpleXChat
-
-private let memberCriterias: [(criteria: MemberCriteria?, text: LocalizedStringKey)] = [
- (nil, "off"),
- (.all, "all")
-]
-
-struct MemberAdmissionView: View {
- @Environment(\.dismiss) var dismiss: DismissAction
- @EnvironmentObject var chatModel: ChatModel
- @EnvironmentObject var theme: AppTheme
- @Binding var groupInfo: GroupInfo
- @Binding var admission: GroupMemberAdmission
- var currentAdmission: GroupMemberAdmission
- let creatingGroup: Bool
- let saveAdmission: () -> Void
- @State private var showSaveDialogue = false
-
- var body: some View {
- let saveText: LocalizedStringKey = creatingGroup ? "Save" : "Save and notify group members"
- VStack {
- List {
- admissionSection(
- NSLocalizedString("Review members", comment: "admission stage"),
- NSLocalizedString("Review members before admitting (\"knocking\").", comment: "admission stage description"),
- $admission.review
- )
-
- if groupInfo.isOwner {
- Section {
- Button("Reset") { admission = currentAdmission }
- Button(saveText) { saveAdmission() }
- }
- .disabled(currentAdmission == admission)
- }
- }
- }
- .modifier(BackButton(disabled: Binding.constant(false)) {
- if currentAdmission == admission {
- dismiss()
- } else {
- showSaveDialogue = true
- }
- })
- .confirmationDialog("Save admission settings?", isPresented: $showSaveDialogue) {
- Button(saveText) {
- saveAdmission()
- dismiss()
- }
- Button("Exit without saving") {
- admission = currentAdmission
- dismiss()
- }
- }
- }
-
- private func admissionSection(_ admissionStageStr: String, _ admissionStageDescrStr: String, _ memberCriteria: Binding) -> some View {
- Section {
- if groupInfo.isOwner {
- Picker(admissionStageStr, selection: memberCriteria) {
- ForEach(memberCriterias, id: \.criteria) { mc in
- Text(mc.text)
- }
- }
- .frame(height: 36)
- } else {
- infoRow(Text(admissionStageStr), memberCriteria.wrappedValue?.text ?? NSLocalizedString("off", comment: "member criteria value"))
- }
- } footer: {
- Text(admissionStageDescrStr)
- .foregroundColor(theme.colors.secondary)
- }
- }
-}
-
-#Preview {
- MemberAdmissionView(
- groupInfo: Binding.constant(GroupInfo.sampleData),
- admission: Binding.constant(GroupMemberAdmission.sampleData),
- currentAdmission: GroupMemberAdmission.sampleData,
- creatingGroup: false,
- saveAdmission: {}
- )
-}
diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift
deleted file mode 100644
index 23001e64bf..0000000000
--- a/apps/ios/Shared/Views/Chat/Group/MemberSupportChatToolbar.swift
+++ /dev/null
@@ -1,44 +0,0 @@
-//
-// MemberSupportChatToolbar.swift
-// SimpleX (iOS)
-//
-// Created by spaced4ndy on 01.05.2025.
-// Copyright © 2025 SimpleX Chat. All rights reserved.
-//
-
-import SwiftUI
-import SimpleXChat
-
-struct MemberSupportChatToolbar: View {
- @Environment(\.colorScheme) var colorScheme
- @EnvironmentObject var theme: AppTheme
- var groupMember: GroupMember
- var imageSize: CGFloat = 32
-
- var body: some View {
- return HStack {
- MemberProfileImage(groupMember, size: imageSize)
- .padding(.trailing, 4)
- let t = Text(groupMember.chatViewName).font(.headline)
- (groupMember.verified ? memberVerifiedShield + t : t)
- .lineLimit(1)
- }
- .foregroundColor(theme.colors.onBackground)
- .frame(width: 220)
- }
-
- private var memberVerifiedShield: Text {
- (Text(Image(systemName: "checkmark.shield")) + textSpace)
- .font(.caption)
- .foregroundColor(theme.colors.secondary)
- .baselineOffset(1)
- .kerning(-2)
- }
-}
-
-#Preview {
- MemberSupportChatToolbar(
- groupMember: GroupMember.sampleData
- )
- .environmentObject(CurrentColors.toAppTheme())
-}
diff --git a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift b/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
deleted file mode 100644
index 7f3672ea17..0000000000
--- a/apps/ios/Shared/Views/Chat/Group/MemberSupportView.swift
+++ /dev/null
@@ -1,287 +0,0 @@
-//
-// MemberSupportView.swift
-// SimpleX (iOS)
-//
-// Created by spaced4ndy on 28.04.2025.
-// Copyright © 2025 SimpleX Chat. All rights reserved.
-//
-
-import SwiftUI
-import SimpleXChat
-
-struct MemberSupportView: View {
- @EnvironmentObject var chatModel: ChatModel
- @EnvironmentObject var theme: AppTheme
- @State private var searchText: String = ""
- @FocusState private var searchFocussed
- var groupInfo: GroupInfo
- @Binding var scrollToItemId: ChatItem.ID?
-
- var body: some View {
- viewBody()
- .onAppear {
- Task {
- await chatModel.loadGroupMembers(groupInfo)
- }
- }
- .toolbar {
- ToolbarItem(placement: .navigationBarTrailing) {
- Button {
- Task {
- await chatModel.loadGroupMembers(groupInfo)
- }
- } label: {
- Image(systemName: "arrow.clockwise")
- }
- }
- }
- }
-
- @ViewBuilder private func viewBody() -> some View {
- let membersWithChats = sortedMembersWithChats()
- let s = searchText.trimmingCharacters(in: .whitespaces).localizedLowercase
- let filteredMembersWithChats = s == ""
- ? membersWithChats
- : membersWithChats.filter { $0.wrapped.localAliasAndFullName.localizedLowercase.contains(s) }
-
- if membersWithChats.isEmpty {
- Text("No chats with members")
- .foregroundColor(.secondary)
- } else {
- List {
- searchFieldView(text: $searchText, focussed: $searchFocussed, theme.colors.onBackground, theme.colors.secondary)
- .padding(.leading, 8)
- ForEach(filteredMembersWithChats) { memberWithChat in
- MemberSupportChatNavLink(
- groupInfo: groupInfo,
- memberWithChat: memberWithChat,
- scrollToItemId: $scrollToItemId
- )
- }
- }
- }
- }
-
- struct MemberSupportChatNavLink: View {
- @EnvironmentObject var chatModel: ChatModel
- @EnvironmentObject var theme: AppTheme
- @State private var memberSupportChatNavLinkActive = false
- var groupInfo: GroupInfo
- var memberWithChat: GMember
- @Binding var scrollToItemId: ChatItem.ID?
-
- var body: some View {
- ZStack {
- let scopeInfo: GroupChatScopeInfo = .memberSupport(groupMember_: memberWithChat.wrapped)
- Button {
- ItemsModel.loadSecondaryChat(groupInfo.id, chatFilter: .groupChatScopeContext(groupScopeInfo: scopeInfo)) {
- memberSupportChatNavLinkActive = true
- }
- } label: {
- SupportChatRowView(groupMember: memberWithChat, groupInfo: groupInfo)
- }
-
- NavigationLink(isActive: $memberSupportChatNavLinkActive) {
- SecondaryChatView(
- chat: Chat(chatInfo: .group(groupInfo: groupInfo, groupChatScope: scopeInfo), chatItems: [], chatStats: ChatStats()),
- scrollToItemId: $scrollToItemId
- )
- } label: {
- EmptyView()
- }
- .frame(width: 1, height: 1)
- .hidden()
- }
- .swipeActions(edge: .trailing, allowsFullSwipe: true) {
- if memberWithChat.wrapped.memberPending {
- Button {
- showAcceptMemberAlert(groupInfo, memberWithChat.wrapped)
- } label: {
- Label("Accept", systemImage: "checkmark")
- }
- .tint(theme.colors.primary)
- } else {
- Button {
- showDeleteMemberSupportChatAlert(groupInfo, memberWithChat.wrapped)
- } label: {
- Label("Delete", systemImage: "trash")
- }
- .tint(.red)
- }
- }
- }
- }
-
- func sortedMembersWithChats() -> [GMember] {
- chatModel.groupMembers
- .filter {
- $0.wrapped.supportChat != nil &&
- $0.wrapped.memberStatus != .memLeft &&
- $0.wrapped.memberStatus != .memRemoved
- }
- .sorted { (m0: GMember, m1: GMember) -> Bool in
- if m0.wrapped.memberPending != m1.wrapped.memberPending {
- return m0.wrapped.memberPending
- }
-
- let mentions0 = (m0.wrapped.supportChat?.mentions ?? 0) > 0
- let mentions1 = (m1.wrapped.supportChat?.mentions ?? 0) > 0
- if mentions0 != mentions1 {
- return mentions0
- }
-
- let attention0 = (m0.wrapped.supportChat?.memberAttention ?? 0) > 0
- let attention1 = (m1.wrapped.supportChat?.memberAttention ?? 0) > 0
- if attention0 != attention1 {
- return attention0
- }
-
- let unread0 = (m0.wrapped.supportChat?.unread ?? 0) > 0
- let unread1 = (m1.wrapped.supportChat?.unread ?? 0) > 0
- if unread0 != unread1 {
- return unread0
- }
-
- return (m0.wrapped.supportChat?.chatTs ?? .distantPast) > (m1.wrapped.supportChat?.chatTs ?? .distantPast)
- }
- }
-
- private struct SupportChatRowView: View {
- @EnvironmentObject var chatModel: ChatModel
- @ObservedObject var groupMember: GMember
- @EnvironmentObject var theme: AppTheme
- @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
- var groupInfo: GroupInfo
-
- var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
-
- var body: some View {
- let member = groupMember.wrapped
- HStack{
- MemberProfileImage(member, size: 38)
- .padding(.trailing, 2)
- VStack(alignment: .leading) {
- let t = Text(member.chatViewName).foregroundColor(theme.colors.onBackground)
- (member.verified ? memberVerifiedShield + t : t)
- .lineLimit(1)
- Text(memberStatus(member))
- .lineLimit(1)
- .font(.caption)
- .foregroundColor(theme.colors.secondary)
- }
-
- Spacer()
-
- if member.memberPending {
- Image(systemName: "flag.fill")
- .resizable()
- .scaledToFill()
- .frame(width: dynamicChatInfoSize * 0.8, height: dynamicChatInfoSize * 0.8)
- .foregroundColor(theme.colors.primary)
- }
- if let supportChat = member.supportChat {
- SupportChatUnreadIndicator(supportChat: supportChat)
- }
- }
- }
-
- private func memberStatus(_ member: GroupMember) -> LocalizedStringKey {
- if member.activeConn?.connDisabled ?? false {
- return "disabled"
- } else if member.activeConn?.connInactive ?? false {
- return "inactive"
- } else if member.memberPending {
- return member.memberStatus.text
- } else {
- return LocalizedStringKey(member.memberRole.text)
- }
- }
-
- struct SupportChatUnreadIndicator: View {
- @EnvironmentObject var theme: AppTheme
- @Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
- var supportChat: GroupSupportChat
-
- var dynamicChatInfoSize: CGFloat { dynamicSize(userFont).chatInfoSize }
-
- private var indicatorTint: Color {
- if supportChat.mentions > 0 || supportChat.memberAttention > 0 {
- return theme.colors.primary
- } else {
- return theme.colors.secondary
- }
- }
-
- var body: some View {
- HStack(alignment: .center, spacing: 2) {
- if supportChat.unread > 0 || supportChat.mentions > 0 || supportChat.memberAttention > 0 {
- if supportChat.mentions > 0 && supportChat.unread > 1 {
- Text("\(MENTION_START)")
- .font(userFont <= .xxxLarge ? .body : .callout)
- .foregroundColor(indicatorTint)
- .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
- .cornerRadius(dynamicSize(userFont).unreadCorner)
- .padding(.bottom, 1)
- }
- let singleUnreadIsMention = supportChat.mentions > 0 && supportChat.unread == 1
- (singleUnreadIsMention ? Text("\(MENTION_START)") : unreadCountText(supportChat.unread))
- .font(userFont <= .xxxLarge ? .caption : .caption2)
- .foregroundColor(.white)
- .padding(.horizontal, dynamicSize(userFont).unreadPadding)
- .frame(minWidth: dynamicChatInfoSize, minHeight: dynamicChatInfoSize)
- .background(indicatorTint)
- .cornerRadius(dynamicSize(userFont).unreadCorner)
- }
- }
- .frame(height: dynamicChatInfoSize)
- .frame(minWidth: 22)
- }
- }
-
- private var memberVerifiedShield: Text {
- (Text(Image(systemName: "checkmark.shield")) + textSpace)
- .font(.caption)
- .baselineOffset(2)
- .kerning(-2)
- .foregroundColor(theme.colors.secondary)
- }
- }
-}
-
-func showDeleteMemberSupportChatAlert(_ groupInfo: GroupInfo, _ member: GroupMember) {
- showAlert(
- title: NSLocalizedString("Delete chat with member?", comment: "alert title"),
- buttonTitle: "Delete",
- buttonAction: { deleteMemberSupportChat(groupInfo, member) },
- cancelButton: true
- )
-}
-
-func deleteMemberSupportChat(_ groupInfo: GroupInfo, _ member: GroupMember) {
- Task {
- do {
- let (gInfo, updatedMember) = try await apiDeleteMemberSupportChat(groupInfo.groupId, member.groupMemberId)
- await MainActor.run {
- _ = ChatModel.shared.upsertGroupMember(gInfo, updatedMember)
- ChatModel.shared.updateGroup(gInfo)
- }
- // TODO member row doesn't get removed from list (upsertGroupMember correctly sets supportChat to nil) - this repopulates list to fix it
- await ChatModel.shared.loadGroupMembers(gInfo)
- } catch let error {
- logger.error("apiDeleteMemberSupportChat error: \(responseError(error))")
- await MainActor.run {
- showAlert(
- NSLocalizedString("Error deleting chat with member", comment: "alert title"),
- message: responseError(error)
- )
- }
- }
- }
-}
-
-#Preview {
- MemberSupportView(
- groupInfo: GroupInfo.sampleData,
- scrollToItemId: Binding.constant(nil)
- )
-}
diff --git a/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift b/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift
deleted file mode 100644
index 47c5df264f..0000000000
--- a/apps/ios/Shared/Views/Chat/Group/SecondaryChatView.swift
+++ /dev/null
@@ -1,42 +0,0 @@
-//
-// SecondaryChatView.swift
-// SimpleX (iOS)
-//
-// Created by spaced4ndy on 29.04.2025.
-// Copyright © 2025 SimpleX Chat. All rights reserved.
-//
-
-import SwiftUI
-import SimpleXChat
-
-struct SecondaryChatView: View {
- @EnvironmentObject var chatModel: ChatModel
- @ObservedObject var chat: Chat
- @Binding var scrollToItemId: ChatItem.ID?
-
- var body: some View {
- if let im = chatModel.secondaryIM {
- ChatView(
- chat: chat,
- im: im,
- mergedItems: BoxedValue(MergedItems.create(im, [])),
- floatingButtonModel: FloatingButtonModel(im: im),
- scrollToItemId: $scrollToItemId
- )
- .onDisappear {
- chatModel.secondaryIM = nil
- }
- }
- }
-}
-
-#Preview {
- SecondaryChatView(
- chat: Chat(
- chatInfo: .group(groupInfo: GroupInfo.sampleData, groupChatScope: .memberSupport(groupMember_: GroupMember.sampleData)),
- chatItems: [],
- chatStats: ChatStats()
- ),
- scrollToItemId: Binding.constant(nil)
- )
-}
diff --git a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift
index e397970acd..85d6b279c5 100644
--- a/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift
+++ b/apps/ios/Shared/Views/Chat/SelectableChatItemToolbars.swift
@@ -25,7 +25,7 @@ struct SelectedItemsTopToolbar: View {
struct SelectedItemsBottomToolbar: View {
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject var theme: AppTheme
- let im: ItemsModel
+ let chatItems: [ChatItem]
@Binding var selectedChatItems: Set?
var chatInfo: ChatInfo
// Bool - delete for everyone is possible
@@ -75,9 +75,9 @@ struct SelectedItemsBottomToolbar: View {
.resizable()
.scaledToFit()
.frame(width: 20, height: 20, alignment: .center)
- .foregroundColor(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : .red)
+ .foregroundColor(!moderateEnabled || deleteCountProhibited ? theme.colors.secondary : .red)
}
- .disabled(!moderateEnabled || deleteCountProhibited || im.secondaryIMFilter != nil)
+ .disabled(!moderateEnabled || deleteCountProhibited)
.opacity(canModerate ? 1 : 0)
Spacer()
@@ -88,24 +88,24 @@ struct SelectedItemsBottomToolbar: View {
.resizable()
.scaledToFit()
.frame(width: 20, height: 20, alignment: .center)
- .foregroundColor(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil ? theme.colors.secondary : theme.colors.primary)
+ .foregroundColor(!forwardEnabled || forwardCountProhibited ? theme.colors.secondary : theme.colors.primary)
}
- .disabled(!forwardEnabled || forwardCountProhibited || im.secondaryIMFilter != nil)
+ .disabled(!forwardEnabled || forwardCountProhibited)
}
.frame(maxHeight: .infinity)
.padding([.leading, .trailing], 12)
}
.onAppear {
- recheckItems(chatInfo, im.reversedChatItems, selectedChatItems)
+ recheckItems(chatInfo, chatItems, selectedChatItems)
}
.onChange(of: chatInfo) { info in
- recheckItems(info, im.reversedChatItems, selectedChatItems)
+ recheckItems(info, chatItems, selectedChatItems)
}
- .onChange(of: im.reversedChatItems) { items in
+ .onChange(of: chatItems) { items in
recheckItems(chatInfo, items, selectedChatItems)
}
.onChange(of: selectedChatItems) { selected in
- recheckItems(chatInfo, im.reversedChatItems, selected)
+ recheckItems(chatInfo, chatItems, selected)
}
.frame(height: 55.5)
.background(.thinMaterial)
@@ -116,7 +116,7 @@ struct SelectedItemsBottomToolbar: View {
deleteCountProhibited = count == 0 || count > 200
forwardCountProhibited = count == 0 || count > 20
canModerate = possibleToModerate(chatInfo)
- let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info, _) = chatInfo {
+ let groupInfo: GroupInfo? = if case let ChatInfo.group(groupInfo: info) = chatInfo {
info
} else {
nil
@@ -145,7 +145,7 @@ struct SelectedItemsBottomToolbar: View {
private func possibleToModerate(_ chatInfo: ChatInfo) -> Bool {
return switch chatInfo {
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
groupInfo.membership.memberRole >= .admin
default: false
}
diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
index 1e747b8019..81d78fbadd 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift
@@ -66,7 +66,7 @@ struct ChatListNavLink: View {
switch chat.chatInfo {
case let .direct(contact):
contactNavLink(contact)
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
groupNavLink(groupInfo)
case let .local(noteFolder):
noteFolderNavLink(noteFolder)
@@ -189,7 +189,7 @@ struct ChatListNavLink: View {
}
.swipeActions(edge: .trailing) {
tagChatButton(chat)
- if (groupInfo.membership.memberCurrentOrPending) {
+ if (groupInfo.membership.memberCurrent) {
leaveGroupChatButton(groupInfo)
}
if groupInfo.canDelete {
@@ -214,7 +214,7 @@ struct ChatListNavLink: View {
let showReportsButton = chat.chatStats.reportsCount > 0 && groupInfo.membership.memberRole >= .moderator
let showClearButton = !chat.chatItems.isEmpty
let showDeleteGroup = groupInfo.canDelete
- let showLeaveGroup = groupInfo.membership.memberCurrentOrPending
+ let showLeaveGroup = groupInfo.membership.memberCurrent
let totalNumberOfButtons = 1 + (showReportsButton ? 1 : 0) + (showClearButton ? 1 : 0) + (showDeleteGroup ? 1 : 0) + (showLeaveGroup ? 1 : 0)
if showClearButton && totalNumberOfButtons <= 3 {
@@ -276,7 +276,7 @@ struct ChatListNavLink: View {
@ViewBuilder private func markReadButton() -> some View {
if chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat {
Button {
- Task { await markChatRead(ItemsModel.shared, chat) }
+ Task { await markChatRead(chat) }
} label: {
SwipeLabel(NSLocalizedString("Read", comment: "swipe action"), systemImage: "checkmark", inverted: oneHandUI)
}
@@ -482,10 +482,12 @@ struct ChatListNavLink: View {
.tint(theme.colors.primary)
}
.appSheet(isPresented: $showContactConnectionInfo) {
- if case let .contactConnection(contactConnection) = chat.chatInfo {
- ContactConnectionInfo(contactConnection: contactConnection)
- .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil)
- .modifier(ThemedBackground(grouped: true))
+ Group {
+ if case let .contactConnection(contactConnection) = chat.chatInfo {
+ ContactConnectionInfo(contactConnection: contactConnection)
+ .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil)
+ .modifier(ThemedBackground(grouped: true))
+ }
}
}
.contentShape(Rectangle())
diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift
index 377764ac83..f34f930c6f 100644
--- a/apps/ios/Shared/Views/ChatList/ChatListView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift
@@ -148,7 +148,6 @@ struct ChatListView: View {
@State private var userPickerShown: Bool = false
@State private var sheet: SomeSheet? = nil
@StateObject private var chatTagsModel = ChatTagsModel.shared
- @State private var scrollToItemId: ChatItem.ID? = nil
// iOS 15 is required it to show/hide toolbar while chat is hidden/visible
@State private var viewOnScreen = true
@@ -447,14 +446,7 @@ struct ChatListView: View {
@ViewBuilder private func chatView() -> some View {
if let chatId = chatModel.chatId, let chat = chatModel.getChat(chatId) {
- let im = ItemsModel.shared
- ChatView(
- chat: chat,
- im: im,
- mergedItems: BoxedValue(MergedItems.create(im, [])),
- floatingButtonModel: FloatingButtonModel(im: im),
- scrollToItemId: $scrollToItemId
- )
+ ChatView(chat: chat)
}
}
@@ -900,12 +892,12 @@ func presetTagMatchesChat(_ tag: PresetTag, _ chatInfo: ChatInfo, _ chatStats: C
case let .direct(contact): !(contact.activeConn == nil && contact.profile.contactLink != nil && contact.active) && !contact.chatDeleted
case .contactRequest: true
case .contactConnection: true
- case let .group(groupInfo, _): groupInfo.businessChat?.chatType == .customer
+ case let .group(groupInfo): groupInfo.businessChat?.chatType == .customer
default: false
}
case .groups:
switch chatInfo {
- case let .group(groupInfo, _): groupInfo.businessChat == nil
+ case let .group(groupInfo): groupInfo.businessChat == nil
default: false
}
case .business:
diff --git a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
index 49f629d084..b8c8233e6e 100644
--- a/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
+++ b/apps/ios/Shared/Views/ChatList/ChatPreviewView.swift
@@ -141,7 +141,7 @@ struct ChatPreviewView: View {
} else {
EmptyView()
}
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
switch (groupInfo.membership.memberStatus) {
case .memRejected: inactiveIcon()
case .memLeft: inactiveIcon()
@@ -165,7 +165,7 @@ struct ChatPreviewView: View {
switch chat.chatInfo {
case let .direct(contact):
previewTitle(contact.verified == true ? verifiedIcon + t : t).foregroundColor(deleting ? Color.secondary : nil)
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
let v = previewTitle(t)
switch (groupInfo.membership.memberStatus) {
case .memInvited: v.foregroundColor(deleting ? theme.colors.secondary : chat.chatInfo.incognito ? .indigo : theme.colors.primary)
@@ -341,12 +341,11 @@ struct ChatPreviewView: View {
chatPreviewInfoText("connecting…")
}
}
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
switch (groupInfo.membership.memberStatus) {
case .memRejected: chatPreviewInfoText("rejected")
case .memInvited: groupInvitationPreviewText(groupInfo)
case .memAccepted: chatPreviewInfoText("connecting…")
- case .memPendingReview, .memPendingApproval: chatPreviewInfoText("reviewed by admins")
default: EmptyView()
}
default: EmptyView()
@@ -440,11 +439,7 @@ struct ChatPreviewView: View {
if progressByTimeout {
ProgressView()
} else if chat.chatStats.reportsCount > 0 {
- flagIcon(size: size * 0.8, color: .red)
- } else if chat.supportUnreadCount > 0 {
- flagIcon(size: size * 0.8, color: theme.colors.primary)
- } else if chat.chatInfo.groupInfo?.membership.memberPending ?? false {
- flagIcon(size: size * 0.8, color: theme.colors.secondary)
+ groupReportsIcon(size: size * 0.8)
} else {
incognitoIcon(chat.chatInfo.incognito, theme.colors.secondary, size: size)
}
@@ -490,12 +485,12 @@ struct ChatPreviewView: View {
}
}
-func flagIcon(size: CGFloat, color: Color) -> some View {
+func groupReportsIcon(size: CGFloat) -> some View {
Image(systemName: "flag")
.resizable()
.scaledToFit()
.frame(width: size, height: size)
- .foregroundColor(color)
+ .foregroundColor(.red)
}
func smallContentPreview(size: CGFloat, _ view: @escaping () -> some View) -> some View {
diff --git a/apps/ios/Shared/Views/ChatList/UserPicker.swift b/apps/ios/Shared/Views/ChatList/UserPicker.swift
index c38ddfb1da..dbe10ad997 100644
--- a/apps/ios/Shared/Views/ChatList/UserPicker.swift
+++ b/apps/ios/Shared/Views/ChatList/UserPicker.swift
@@ -124,7 +124,7 @@ struct UserPicker: View {
ZStack(alignment: .topTrailing) {
ProfileImage(imageStr: u.user.image, size: size, color: Color(uiColor: .tertiarySystemGroupedBackground))
if (u.unreadCount > 0) {
- userUnreadBadge(u, theme: theme).offset(x: 4, y: -4)
+ UnreadBadge(userInfo: u).offset(x: 4, y: -4)
}
}
.padding(.trailing, 6)
@@ -171,27 +171,19 @@ struct UserPicker: View {
}
}
-@inline(__always)
-func userUnreadBadge(_ userInfo: UserInfo, theme: AppTheme) -> some View {
- UnreadBadge(
- count: userInfo.unreadCount,
- color: userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary
- )
-}
-
struct UnreadBadge: View {
+ var userInfo: UserInfo
+ @EnvironmentObject var theme: AppTheme
@Environment(\.dynamicTypeSize) private var userFont: DynamicTypeSize
- var count: Int
- var color: Color
var body: some View {
let size = dynamicSize(userFont).chatInfoSize
- unreadCountText(count)
+ unreadCountText(userInfo.unreadCount)
.font(userFont <= .xxxLarge ? .caption : .caption2)
.foregroundColor(.white)
.padding(.horizontal, dynamicSize(userFont).unreadPadding)
.frame(minWidth: size, minHeight: size)
- .background(color)
+ .background(userInfo.user.showNtfs ? theme.colors.primary : theme.colors.secondary)
.cornerRadius(dynamicSize(userFont).unreadCorner)
}
}
diff --git a/apps/ios/Shared/Views/Helpers/AppSheet.swift b/apps/ios/Shared/Views/Helpers/AppSheet.swift
index 17fe95a058..1e334367e8 100644
--- a/apps/ios/Shared/Views/Helpers/AppSheet.swift
+++ b/apps/ios/Shared/Views/Helpers/AppSheet.swift
@@ -33,7 +33,7 @@ extension View {
func appSheet(
isPresented: Binding,
onDismiss: (() -> Void)? = nil,
- @ViewBuilder content: @escaping () -> Content
+ content: @escaping () -> Content
) -> some View where Content: View {
sheet(isPresented: isPresented, onDismiss: onDismiss) {
content().modifier(PrivacySensitive())
@@ -43,7 +43,7 @@ extension View {
func appSheet(
item: Binding,
onDismiss: (() -> Void)? = nil,
- @ViewBuilder content: @escaping (T) -> Content
+ content: @escaping (T) -> Content
) -> some View where T: Identifiable, Content: View {
sheet(item: item, onDismiss: onDismiss) { it in
content(it).modifier(PrivacySensitive())
diff --git a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
index c21ff9be8b..16ab26eff7 100644
--- a/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
+++ b/apps/ios/Shared/Views/LocalAuth/LocalAuthView.swift
@@ -66,8 +66,6 @@ struct LocalAuthView: View {
m.chatId = nil
ItemsModel.shared.reversedChatItems = []
ItemsModel.shared.chatState.clear()
- ChatModel.shared.secondaryIM?.reversedChatItems = []
- ChatModel.shared.secondaryIM?.chatState.clear()
m.updateChats([])
m.users = []
_ = kcAppPassword.set(password)
diff --git a/apps/ios/Shared/Views/NewChat/AddGroupView.swift b/apps/ios/Shared/Views/NewChat/AddGroupView.swift
index b3c33e95ea..87c0b80372 100644
--- a/apps/ios/Shared/Views/NewChat/AddGroupView.swift
+++ b/apps/ios/Shared/Views/NewChat/AddGroupView.swift
@@ -193,7 +193,7 @@ struct AddGroupView: View {
Task {
await m.loadGroupMembers(gInfo)
}
- let c = Chat(chatInfo: .group(groupInfo: gInfo, groupChatScope: nil), chatItems: [])
+ let c = Chat(chatInfo: .group(groupInfo: gInfo), chatItems: [])
m.addChat(c)
withAnimation {
groupInfo = gInfo
diff --git a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
index 02dec5a618..c6d0e27289 100644
--- a/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
+++ b/apps/ios/Shared/Views/UserSettings/AppearanceSettings.swift
@@ -367,13 +367,13 @@ struct ChatThemePreview: View {
let alice = ChatItem.getSample(1, CIDirection.directRcv, Date.now, NSLocalizedString("Good afternoon!", comment: "message preview"))
let bob = ChatItem.getSample(2, CIDirection.directSnd, Date.now, NSLocalizedString("Good morning!", comment: "message preview"), quotedItem: CIQuote.getSample(alice.id, alice.meta.itemTs, alice.content.text, chatDir: alice.chatDir))
HStack {
- ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: alice, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
+ ChatItemView(chat: Chat.sampleData, chatItem: alice, scrollToItemId: { _ in })
.modifier(ChatItemClipped(alice, tailVisible: true))
Spacer()
}
HStack {
Spacer()
- ChatItemView(chat: Chat.sampleData, im: ItemsModel.shared, chatItem: bob, scrollToItem: { _ in }, scrollToItemId: Binding.constant(nil))
+ ChatItemView(chat: Chat.sampleData, chatItem: bob, scrollToItemId: { _ in })
.modifier(ChatItemClipped(bob, tailVisible: true))
.frame(alignment: .trailing)
}
diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
index 50a012f4f8..e06b1c4dd3 100644
--- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift
+++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift
@@ -58,7 +58,6 @@ let DEFAULT_CONNECT_VIA_LINK_TAB = "connectViaLinkTab"
let DEFAULT_LIVE_MESSAGE_ALERT_SHOWN = "liveMessageAlertShown"
let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice"
let DEFAULT_SHOW_MUTE_PROFILE_ALERT = "showMuteProfileAlert"
-let DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "showReportsInSupportChatAlert"
let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion"
let DEFAULT_ONBOARDING_STAGE = "onboardingStage"
let DEFAULT_MIGRATION_TO_STAGE = "migrationToStage"
@@ -118,7 +117,6 @@ let appDefaults: [String: Any] = [
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN: false,
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE: true,
DEFAULT_SHOW_MUTE_PROFILE_ALERT: true,
- DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT: true,
DEFAULT_ONBOARDING_STAGE: OnboardingStage.onboardingComplete.rawValue,
DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME: 300,
DEFAULT_SHOW_UNREAD_AND_FAVORITES: false,
@@ -146,7 +144,6 @@ let hintDefaults = [
DEFAULT_LIVE_MESSAGE_ALERT_SHOWN,
DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE,
DEFAULT_SHOW_MUTE_PROFILE_ALERT,
- DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT,
DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE,
DEFAULT_SHOW_DELETE_CONTACT_NOTICE
]
@@ -198,8 +195,6 @@ let customDisappearingMessageTimeDefault = IntDefault(defaults: UserDefaults.sta
let showDeleteConversationNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONVERSATION_NOTICE)
let showDeleteContactNoticeDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_DELETE_CONTACT_NOTICE)
-let showReportsInSupportChatAlertDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT)
-
/// after importing new database, this flag will be set and unset only after importing app settings in `initializeChat` */
let shouldImportAppSettingsDefault = BoolDefault(defaults: UserDefaults.standard, forKey: DEFAULT_SHOULD_IMPORT_APP_SETTINGS)
let currentThemeDefault = StringDefault(defaults: UserDefaults.standard, forKey: DEFAULT_CURRENT_THEME, withDefault: DefaultTheme.SYSTEM_THEME_NAME)
diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
index ddfe59e719..887023b670 100644
--- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
+++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift
@@ -350,7 +350,7 @@ struct UserProfilesView: View {
Image(systemName: "checkmark").foregroundColor(theme.colors.onBackground)
} else {
if userInfo.unreadCount > 0 {
- userUnreadBadge(userInfo, theme: theme)
+ UnreadBadge(userInfo: userInfo)
}
if user.hidden {
Image(systemName: "lock").foregroundColor(theme.colors.secondary)
diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
index b5a217d8c0..776199ac1f 100644
--- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
+++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
@@ -554,14 +554,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Приеми условията
@@ -583,10 +575,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Приети условия
@@ -1547,23 +1535,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Чатове
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
No comment provided by engineer.
@@ -2324,10 +2300,6 @@ swipe action
Изтриване на чат профила?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2726,7 +2698,7 @@ swipe action
Don't show again
Не показвай отново
- alert action
+ No comment provided by engineer.
Done
@@ -3034,10 +3006,6 @@ chat item action
Грешка при приемане на заявка за контакт
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Грешка при добавяне на член(ове)
@@ -3126,10 +3094,6 @@ chat item action
Грешка при изтриване на базата данни
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Грешка при изтриването на чата!
@@ -3232,7 +3196,7 @@ chat item action
Error removing member
Грешка при отстраняване на член
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4544,10 +4508,6 @@ This is your link for group %@!
Член
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
item status text
@@ -4579,10 +4539,6 @@ This is your link for group %@!
Членът ще бъде премахнат от групата - това не може да бъде отменено!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Членовете на групата могат да добавят реакции към съобщенията.
@@ -4975,10 +4931,6 @@ This is your link for group %@!
Нова членска роля
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Ново съобщение
@@ -5015,10 +4967,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Няма избрани контакти
@@ -5192,8 +5140,7 @@ This is your link for group %@!
Ok
Ок
- alert action
-alert button
+ alert button
Old database
@@ -5584,10 +5531,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6007,10 +5950,6 @@ swipe action
Отхвърли заявката за контакт
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Реле сървър се използва само ако е необходимо. Друга страна може да наблюдава вашия IP адрес.
@@ -6114,10 +6053,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6222,14 +6157,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Отзови
@@ -6283,10 +6210,6 @@ chat item action
Запази (и уведоми контактите)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Запази и уведоми контакта
@@ -6766,10 +6689,6 @@ chat item action
Задайте го вместо системната идентификация.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8424,10 +8343,6 @@ Repeat join request?
Можете да видите отново линкът за покана в подробностите за връзката.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Не може да изпращате съобщения!
@@ -8720,10 +8635,6 @@ Repeat connection request?
по-горе, след това избери:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
обаждането прието
@@ -8733,10 +8644,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
админ
@@ -8757,10 +8664,6 @@ Repeat connection request?
съгласуване на криптиране…
chat item text
-
- all
- member criteria value
-
all members
всички членове
@@ -8844,10 +8747,6 @@ marked deleted chat item preview text
повикване…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
отменен %@
@@ -8953,14 +8852,6 @@ marked deleted chat item preview text
името на контакта %1$@ е променено на %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
контактът има e2e криптиране
@@ -8971,10 +8862,6 @@ marked deleted chat item preview text
контактът няма e2e криптиране
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
създател
@@ -9143,10 +9030,6 @@ pref value
групата е изтрита
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
профилът на групата е актуализиран
@@ -9270,10 +9153,6 @@ pref value
свързан
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
No comment provided by engineer.
@@ -9337,10 +9216,6 @@ pref value
няма текст
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
наблюдател
@@ -9351,7 +9226,6 @@ pref value
изключено
enabled status
group pref value
-member criteria value
time to disappear
@@ -9400,10 +9274,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
квантово устойчиво e2e криптиране
@@ -9443,10 +9313,6 @@ time to disappear
премахнат адрес за контакт
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
премахната профилна снимка
@@ -9457,22 +9323,10 @@ time to disappear
ви острани
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
запазено
@@ -9654,10 +9508,6 @@ last received msg: %2$@
вие
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
вие сте поканени в групата
diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
index fe0e02ccdf..0400839cb0 100644
--- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
+++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff
@@ -544,14 +544,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
No comment provided by engineer.
@@ -572,10 +564,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
No comment provided by engineer.
@@ -1468,23 +1456,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Chaty
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
No comment provided by engineer.
@@ -2217,10 +2193,6 @@ swipe action
Smazat chat profil?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2612,7 +2584,7 @@ swipe action
Don't show again
Znovu neukazuj
- alert action
+ No comment provided by engineer.
Done
@@ -2906,10 +2878,6 @@ chat item action
Chyba při přijímání žádosti o kontakt
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Chyba přidávání člena(ů)
@@ -2997,10 +2965,6 @@ chat item action
Chyba při mazání databáze chatu
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Chyba při mazání chatu!
@@ -3101,7 +3065,7 @@ chat item action
Error removing member
Chyba při odebrání člena
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4369,10 +4333,6 @@ This is your link for group %@!
Člen
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
item status text
@@ -4404,10 +4364,6 @@ This is your link for group %@!
Člen bude odstraněn ze skupiny - toto nelze vzít zpět!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Členové skupin mohou přidávat reakce na zprávy.
@@ -4783,10 +4739,6 @@ This is your link for group %@!
Nová role člena
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Nová zpráva
@@ -4823,10 +4775,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Nebyl vybrán žádný kontakt
@@ -4997,8 +4945,7 @@ This is your link for group %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5373,10 +5320,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -5788,10 +5731,6 @@ swipe action
Odmítnout žádost o kontakt
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Přenosový server se používá pouze v případě potřeby. Jiná strana může sledovat vaši IP adresu.
@@ -5890,10 +5829,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -5997,14 +5932,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Odvolat
@@ -6057,10 +5984,6 @@ chat item action
Uložit (a informovat kontakty)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Uložit a upozornit kontakt
@@ -6532,10 +6455,6 @@ chat item action
Nastavte jej namísto ověřování systému.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8125,10 +8044,6 @@ Repeat join request?
You can view invitation link again in connection details.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Nemůžete posílat zprávy!
@@ -8415,10 +8330,6 @@ Repeat connection request?
výše, pak vyberte:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
přijatý hovor
@@ -8428,10 +8339,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
správce
@@ -8451,10 +8358,6 @@ Repeat connection request?
povoluji šifrování…
chat item text
-
- all
- member criteria value
-
all members
feature role
@@ -8532,10 +8435,6 @@ marked deleted chat item preview text
volání…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
zrušeno %@
@@ -8640,14 +8539,6 @@ marked deleted chat item preview text
contact %1$@ changed to %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
kontakt má šifrování e2e
@@ -8658,10 +8549,6 @@ marked deleted chat item preview text
kontakt nemá šifrování e2e
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
tvůrce
@@ -8828,10 +8715,6 @@ pref value
skupina smazána
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
profil skupiny aktualizován
@@ -8954,10 +8837,6 @@ pref value
připojeno
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
No comment provided by engineer.
@@ -9021,10 +8900,6 @@ pref value
žádný text
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
pozorovatel
@@ -9035,7 +8910,6 @@ pref value
vypnuto
enabled status
group pref value
-member criteria value
time to disappear
@@ -9083,10 +8957,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
chat item text
@@ -9124,10 +8994,6 @@ time to disappear
removed contact address
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profile update event chat item
@@ -9137,22 +9003,10 @@ time to disappear
odstranil vás
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
No comment provided by engineer.
@@ -9324,10 +9178,6 @@ last received msg: %2$@
you
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
jste pozváni do skupiny
diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
index b2e404c141..06fd7c5a1d 100644
--- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
+++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff
@@ -565,16 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- Als Mitglied übernehmen
- alert action
-
-
- Accept as observer
- Als Beobachter übernehmen
- alert action
-
Accept conditions
Nutzungsbedingungen akzeptieren
@@ -596,11 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- Mitglied übernehmen
- alert title
-
Accepted conditions
Akzeptierte Nutzungsbedingungen
@@ -1597,26 +1582,11 @@ set passcode view
Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!
No comment provided by engineer.
-
- Chat with admins
- Chat mit Administratoren
- chat toolbar
-
-
- Chat with member
- Chat mit einem Mitglied
- No comment provided by engineer.
-
Chats
Chats
No comment provided by engineer.
-
- Chats with members
- Chats mit Mitgliedern
- No comment provided by engineer.
-
Check messages every 20 min.
Alle 20min Nachrichten überprüfen.
@@ -2432,11 +2402,6 @@ swipe action
Chat-Profil löschen?
No comment provided by engineer.
-
- Delete chat with member?
- Chat mit dem Mitglied löschen?
- alert title
-
Delete chat?
Chat löschen?
@@ -2860,7 +2825,7 @@ swipe action
Don't show again
Nicht nochmals anzeigen
- alert action
+ No comment provided by engineer.
Done
@@ -3178,11 +3143,6 @@ chat item action
Fehler beim Annehmen der Kontaktanfrage
No comment provided by engineer.
-
- Error accepting member
- Fehler beim Übernehmen des Mitglieds
- alert title
-
Error adding member(s)
Fehler beim Hinzufügen von Mitgliedern
@@ -3278,11 +3238,6 @@ chat item action
Fehler beim Löschen der Chat-Datenbank
No comment provided by engineer.
-
- Error deleting chat with member
- Fehler beim Löschen des Chats mit dem Mitglied
- alert title
-
Error deleting chat!
Fehler beim Löschen des Chats!
@@ -3391,7 +3346,7 @@ chat item action
Error removing member
Fehler beim Entfernen des Mitglieds
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4775,11 +4730,6 @@ Das ist Ihr Link für die Gruppe %@!
Mitglied
No comment provided by engineer.
-
- Member admission
- Aufnahme von Mitgliedern
- No comment provided by engineer.
-
Member inactive
Mitglied inaktiv
@@ -4815,11 +4765,6 @@ Das ist Ihr Link für die Gruppe %@!
Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!
No comment provided by engineer.
-
- Member will join the group, accept member?
- Ein Mitglied wird der Gruppe beitreten. Übernehmen?
- alert message
-
Members can add message reactions.
Gruppenmitglieder können eine Reaktion auf Nachrichten geben.
@@ -5240,11 +5185,6 @@ Das ist Ihr Link für die Gruppe %@!
Neue Mitgliedsrolle
No comment provided by engineer.
-
- New member wants to join the group.
- Ein neues Mitglied will der Gruppe beitreten.
- rcv group event chat item
-
New message
Neue Nachricht
@@ -5285,11 +5225,6 @@ Das ist Ihr Link für die Gruppe %@!
Keine Chats in der Liste %@
No comment provided by engineer.
-
- No chats with members
- Keine Chats mit Mitgliedern
- No comment provided by engineer.
-
No contacts selected
Keine Kontakte ausgewählt
@@ -5482,8 +5417,7 @@ Das ist Ihr Link für die Gruppe %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5641,7 +5575,6 @@ Dies erfordert die Aktivierung eines VPNs.
Open link?
- Link öffnen?
alert title
@@ -5895,11 +5828,6 @@ Fehler: %@
Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren.
token info
-
- Please wait for group moderators to review your request to join the group.
- Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können.
- snd group event chat item
-
Please wait for token activation to complete.
Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist.
@@ -6353,11 +6281,6 @@ swipe action
Kontaktanfrage ablehnen
No comment provided by engineer.
-
- Reject member?
- Mitglied ablehnen?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Relais-Server werden nur genutzt, wenn sie benötigt werden. Ihre IP-Adresse kann von Anderen erfasst werden.
@@ -6468,11 +6391,6 @@ swipe action
Grund der Meldung?
No comment provided by engineer.
-
- Report sent to moderators
- Meldung wurde an die Moderatoren gesendet
- alert title
-
Report spam: only group moderators will see it.
Spam melden: Nur Gruppenmoderatoren werden es sehen.
@@ -6588,16 +6506,6 @@ swipe action
Nutzungsbedingungen einsehen
No comment provided by engineer.
-
- Review members
- Überprüfung der Mitglieder
- admission stage
-
-
- Review members before admitting ("knocking").
- Überprüfung der Mitglieder vor der Aufnahme ("Anklopfen").
- admission stage description
-
Revoke
Widerrufen
@@ -6654,11 +6562,6 @@ chat item action
Speichern (und Kontakte benachrichtigen)
alert button
-
- Save admission settings?
- Speichern der Aufnahme-Einstellungen?
- alert title
-
Save and notify contact
Speichern und Kontakt benachrichtigen
@@ -7174,11 +7077,6 @@ chat item action
Anstelle der System-Authentifizierung festlegen.
No comment provided by engineer.
-
- Set member admission
- Aufnahme von Mitgliedern festlegen
- No comment provided by engineer.
-
Set message expiration in chats.
Verfallsdatum von Nachrichten in Chats festlegen.
@@ -8941,11 +8839,6 @@ Verbindungsanfrage wiederholen?
Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.
alert message
-
- You can view your reports in Chat with admins.
- Sie können Ihre Meldungen im Chat mit den Administratoren sehen.
- alert message
-
You can't send messages!
Sie können keine Nachrichten versenden!
@@ -9185,7 +9078,7 @@ Verbindungsanfrage wiederholen?
Your profile is stored on your device and only shared with your contacts.
- Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.
+ Das Profil wird nur mit Ihren Kontakten geteilt.
No comment provided by engineer.
@@ -9248,11 +9141,6 @@ Verbindungsanfrage wiederholen?
Danach die gewünschte Aktion auswählen:
No comment provided by engineer.
-
- accepted %@
- %@ übernommen
- rcv group event chat item
-
accepted call
Anruf angenommen
@@ -9263,11 +9151,6 @@ Verbindungsanfrage wiederholen?
Einladung angenommen
chat list item title
-
- accepted you
- hat Sie übernommen
- rcv group event chat item
-
admin
Admin
@@ -9288,11 +9171,6 @@ Verbindungsanfrage wiederholen?
Verschlüsselung zustimmen…
chat item text
-
- all
- alle
- member criteria value
-
all members
Alle Mitglieder
@@ -9379,11 +9257,6 @@ marked deleted chat item preview text
Anrufen…
call status
-
- can't send messages
- Es können keine Nachrichten gesendet werden
- No comment provided by engineer.
-
cancelled %@
abgebrochen %@
@@ -9489,16 +9362,6 @@ marked deleted chat item preview text
Der Kontaktname wurde von %1$@ auf %2$@ geändert
profile update event chat item
-
- contact deleted
- Kontakt gelöscht
- No comment provided by engineer.
-
-
- contact disabled
- Kontakt deaktiviert
- No comment provided by engineer.
-
contact has e2e encryption
Kontakt nutzt E2E-Verschlüsselung
@@ -9509,11 +9372,6 @@ marked deleted chat item preview text
Kontakt nutzt keine E2E-Verschlüsselung
No comment provided by engineer.
-
- contact not ready
- Kontakt nicht bereit
- No comment provided by engineer.
-
creator
Ersteller
@@ -9685,11 +9543,6 @@ pref value
Gruppe gelöscht
No comment provided by engineer.
-
- group is deleted
- Gruppe wird gelöscht
- No comment provided by engineer.
-
group profile updated
Gruppenprofil aktualisiert
@@ -9815,11 +9668,6 @@ pref value
ist der Gruppe beigetreten
rcv group event chat item
-
- member has old version
- Das Mitglied hat eine alte App-Version
- No comment provided by engineer.
-
message
Nachricht
@@ -9885,11 +9733,6 @@ pref value
Kein Text
copied message info in history
-
- not synchronized
- Nicht synchronisiert
- No comment provided by engineer.
-
observer
Beobachter
@@ -9900,7 +9743,6 @@ pref value
Aus
enabled status
group pref value
-member criteria value
time to disappear
@@ -9953,11 +9795,6 @@ time to disappear
ausstehende Genehmigung
No comment provided by engineer.
-
- pending review
- Ausstehende Überprüfung
- No comment provided by engineer.
-
quantum resistant e2e encryption
Quantum-resistente E2E-Verschlüsselung
@@ -9998,11 +9835,6 @@ time to disappear
Die Kontaktadresse wurde entfernt
profile update event chat item
-
- removed from group
- Von der Gruppe entfernt
- No comment provided by engineer.
-
removed profile picture
Das Profil-Bild wurde entfernt
@@ -10013,26 +9845,11 @@ time to disappear
hat Sie aus der Gruppe entfernt
rcv group event chat item
-
- request to join rejected
- Beitrittsanfrage abgelehnt
- No comment provided by engineer.
-
requested to connect
Zur Verbindung aufgefordert
chat list item title
-
- review
- Überprüfung
- No comment provided by engineer.
-
-
- reviewed by admins
- Von Administratoren überprüft
- No comment provided by engineer.
-
saved
abgespeichert
@@ -10222,11 +10039,6 @@ Zuletzt empfangene Nachricht: %2$@
Profil
No comment provided by engineer.
-
- you accepted this member
- Sie haben dieses Mitglied übernommen
- snd group event chat item
-
you are invited to group
Sie sind zu der Gruppe eingeladen
diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
index 5982f620b8..fd71e0dee6 100644
--- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
+++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff
@@ -565,16 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- Accept as member
- alert action
-
-
- Accept as observer
- Accept as observer
- alert action
-
Accept conditions
Accept conditions
@@ -596,11 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- Accept member
- alert title
-
Accepted conditions
Accepted conditions
@@ -1597,26 +1582,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- Chat with admins
- chat toolbar
-
-
- Chat with member
- Chat with member
- No comment provided by engineer.
-
Chats
Chats
No comment provided by engineer.
-
- Chats with members
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Check messages every 20 min.
@@ -2432,11 +2402,6 @@ swipe action
Delete chat profile?
No comment provided by engineer.
-
- Delete chat with member?
- Delete chat with member?
- alert title
-
Delete chat?
Delete chat?
@@ -2860,7 +2825,7 @@ swipe action
Don't show again
Don't show again
- alert action
+ No comment provided by engineer.
Done
@@ -3178,11 +3143,6 @@ chat item action
Error accepting contact request
No comment provided by engineer.
-
- Error accepting member
- Error accepting member
- alert title
-
Error adding member(s)
Error adding member(s)
@@ -3278,11 +3238,6 @@ chat item action
Error deleting chat database
No comment provided by engineer.
-
- Error deleting chat with member
- Error deleting chat with member
- alert title
-
Error deleting chat!
Error deleting chat!
@@ -3391,7 +3346,7 @@ chat item action
Error removing member
Error removing member
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4775,11 +4730,6 @@ This is your link for group %@!
Member
No comment provided by engineer.
-
- Member admission
- Member admission
- No comment provided by engineer.
-
Member inactive
Member inactive
@@ -4815,11 +4765,6 @@ This is your link for group %@!
Member will be removed from group - this cannot be undone!
No comment provided by engineer.
-
- Member will join the group, accept member?
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Members can add message reactions.
@@ -5240,11 +5185,6 @@ This is your link for group %@!
New member role
No comment provided by engineer.
-
- New member wants to join the group.
- New member wants to join the group.
- rcv group event chat item
-
New message
New message
@@ -5285,11 +5225,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No chats with members
- No comment provided by engineer.
-
No contacts selected
No contacts selected
@@ -5482,8 +5417,7 @@ This is your link for group %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5895,11 +5829,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
Please wait for token activation to complete.
@@ -6353,11 +6282,6 @@ swipe action
Reject contact request
No comment provided by engineer.
-
- Reject member?
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Relay server is only used if necessary. Another party can observe your IP address.
@@ -6468,11 +6392,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
Report spam: only group moderators will see it.
@@ -6588,16 +6507,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Revoke
@@ -6654,11 +6563,6 @@ chat item action
Save (and notify contacts)
alert button
-
- Save admission settings?
- Save admission settings?
- alert title
-
Save and notify contact
Save and notify contact
@@ -7174,11 +7078,6 @@ chat item action
Set it instead of system authentication.
No comment provided by engineer.
-
- Set member admission
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
Set message expiration in chats.
@@ -8941,11 +8840,6 @@ Repeat join request?
You can view invitation link again in connection details.
alert message
-
- You can view your reports in Chat with admins.
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
You can't send messages!
@@ -9248,11 +9142,6 @@ Repeat connection request?
above, then choose:
No comment provided by engineer.
-
- accepted %@
- accepted %@
- rcv group event chat item
-
accepted call
accepted call
@@ -9263,11 +9152,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- accepted you
- rcv group event chat item
-
admin
admin
@@ -9288,11 +9172,6 @@ Repeat connection request?
agreeing encryption…
chat item text
-
- all
- all
- member criteria value
-
all members
all members
@@ -9379,11 +9258,6 @@ marked deleted chat item preview text
calling…
call status
-
- can't send messages
- can't send messages
- No comment provided by engineer.
-
cancelled %@
cancelled %@
@@ -9489,16 +9363,6 @@ marked deleted chat item preview text
contact %1$@ changed to %2$@
profile update event chat item
-
- contact deleted
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
contact has e2e encryption
@@ -9509,11 +9373,6 @@ marked deleted chat item preview text
contact has no e2e encryption
No comment provided by engineer.
-
- contact not ready
- contact not ready
- No comment provided by engineer.
-
creator
creator
@@ -9685,11 +9544,6 @@ pref value
group deleted
No comment provided by engineer.
-
- group is deleted
- group is deleted
- No comment provided by engineer.
-
group profile updated
group profile updated
@@ -9815,11 +9669,6 @@ pref value
connected
rcv group event chat item
-
- member has old version
- member has old version
- No comment provided by engineer.
-
message
message
@@ -9885,11 +9734,6 @@ pref value
no text
copied message info in history
-
- not synchronized
- not synchronized
- No comment provided by engineer.
-
observer
observer
@@ -9900,7 +9744,6 @@ pref value
off
enabled status
group pref value
-member criteria value
time to disappear
@@ -9953,11 +9796,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
quantum resistant e2e encryption
@@ -9998,11 +9836,6 @@ time to disappear
removed contact address
profile update event chat item
-
- removed from group
- removed from group
- No comment provided by engineer.
-
removed profile picture
removed profile picture
@@ -10013,26 +9846,11 @@ time to disappear
removed you
rcv group event chat item
-
- request to join rejected
- request to join rejected
- No comment provided by engineer.
-
requested to connect
requested to connect
chat list item title
-
- review
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- reviewed by admins
- No comment provided by engineer.
-
saved
saved
@@ -10222,11 +10040,6 @@ last received msg: %2$@
you
No comment provided by engineer.
-
- you accepted this member
- you accepted this member
- snd group event chat item
-
you are invited to group
you are invited to group
diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
index 3c3ae9ff46..d39fb61249 100644
--- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
+++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff
@@ -565,14 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Aceptar condiciones
@@ -594,10 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Condiciones aceptadas
@@ -1594,23 +1582,11 @@ set passcode view
El chat será eliminado para tí. ¡No puede deshacerse!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Chats
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Comprobar mensajes cada 20 min.
@@ -2426,10 +2402,6 @@ swipe action
¿Eliminar perfil?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
¿Eliminar chat?
@@ -2853,7 +2825,7 @@ swipe action
Don't show again
No volver a mostrar
- alert action
+ No comment provided by engineer.
Done
@@ -3171,10 +3143,6 @@ chat item action
Error al aceptar solicitud del contacto
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Error al añadir miembro(s)
@@ -3270,10 +3238,6 @@ chat item action
Error al eliminar base de datos
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
¡Error al eliminar chat!
@@ -3382,7 +3346,7 @@ chat item action
Error removing member
Error al expulsar miembro
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4766,10 +4730,6 @@ This is your link for group %@!
Miembro
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Miembro inactivo
@@ -4805,10 +4765,6 @@ This is your link for group %@!
El miembro será expulsado del grupo. ¡No puede deshacerse!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Los miembros pueden añadir reacciones a los mensajes.
@@ -5229,10 +5185,6 @@ This is your link for group %@!
Nuevo rol de miembro
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Mensaje nuevo
@@ -5273,10 +5225,6 @@ This is your link for group %@!
Sin chats en la lista %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Ningún contacto seleccionado
@@ -5469,8 +5417,7 @@ This is your link for group %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5881,10 +5828,6 @@ Error: %@
Por favor, intenta desactivar y reactivar las notificaciones.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
Por favor, espera a que el token de activación se complete.
@@ -6338,10 +6281,6 @@ swipe action
Rechazar solicitud de contacto
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP.
@@ -6452,10 +6391,6 @@ swipe action
¿Motivo del informe?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
Informar de spam: sólo los moderadores del grupo lo verán.
@@ -6571,14 +6506,6 @@ swipe action
Revisar condiciones
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Revocar
@@ -6635,10 +6562,6 @@ chat item action
Guardar (y notificar contactos)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Guardar y notificar contacto
@@ -7154,10 +7077,6 @@ chat item action
Úsalo en lugar de la autenticación del sistema.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
Establece el vencimiento para los mensajes en los chats.
@@ -8920,10 +8839,6 @@ Repeat join request?
Podrás ver el enlace de invitación en detalles de conexión.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
¡No puedes enviar mensajes!
@@ -9226,10 +9141,6 @@ Repeat connection request?
y después elige:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
llamada aceptada
@@ -9240,10 +9151,6 @@ Repeat connection request?
invitación aceptada
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
administrador
@@ -9264,10 +9171,6 @@ Repeat connection request?
acordando cifrado…
chat item text
-
- all
- member criteria value
-
all members
todos los miembros
@@ -9354,10 +9257,6 @@ marked deleted chat item preview text
llamando…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
cancelado %@
@@ -9463,14 +9362,6 @@ marked deleted chat item preview text
el contacto %1$@ ha cambiado a %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
el contacto dispone de cifrado de extremo a extremo
@@ -9481,10 +9372,6 @@ marked deleted chat item preview text
el contacto no dispone de cifrado de extremo a extremo
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
creador
@@ -9656,10 +9543,6 @@ pref value
grupo eliminado
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
perfil de grupo actualizado
@@ -9785,10 +9668,6 @@ pref value
conectado
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
mensaje
@@ -9854,10 +9733,6 @@ pref value
sin texto
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
observador
@@ -9868,7 +9743,6 @@ pref value
desactivado
enabled status
group pref value
-member criteria value
time to disappear
@@ -9921,10 +9795,6 @@ time to disappear
pendiente de aprobación
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
cifrado e2e resistente a tecnología cuántica
@@ -9965,10 +9835,6 @@ time to disappear
dirección de contacto eliminada
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
ha eliminado la imagen del perfil
@@ -9979,23 +9845,11 @@ time to disappear
te ha expulsado
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
solicitado para conectar
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
guardado
@@ -10185,10 +10039,6 @@ last received msg: %2$@
tu
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
has sido invitado a un grupo
diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
index 7c93c5b0bb..a54666bb10 100644
--- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
+++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff
@@ -527,14 +527,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
No comment provided by engineer.
@@ -555,10 +547,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
No comment provided by engineer.
@@ -1449,23 +1437,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Keskustelut
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
No comment provided by engineer.
@@ -2198,10 +2174,6 @@ swipe action
Poista keskusteluprofiili?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2593,7 +2565,7 @@ swipe action
Don't show again
Älä näytä uudelleen
- alert action
+ No comment provided by engineer.
Done
@@ -2886,10 +2858,6 @@ chat item action
Virhe kontaktipyynnön hyväksymisessä
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Virhe lisättäessä jäseniä
@@ -2976,10 +2944,6 @@ chat item action
Virhe keskustelujen tietokannan poistamisessa
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Virhe keskutelun poistamisessa!
@@ -3080,7 +3044,7 @@ chat item action
Error removing member
Virhe poistettaessa jäsentä
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4347,10 +4311,6 @@ This is your link for group %@!
Jäsen
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
item status text
@@ -4382,10 +4342,6 @@ This is your link for group %@!
Jäsen poistetaan ryhmästä - tätä ei voi perua!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Ryhmän jäsenet voivat lisätä viestireaktioita.
@@ -4760,10 +4716,6 @@ This is your link for group %@!
Uusi jäsenrooli
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Uusi viesti
@@ -4800,10 +4752,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Kontakteja ei ole valittu
@@ -4974,8 +4922,7 @@ This is your link for group %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5349,10 +5296,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -5764,10 +5707,6 @@ swipe action
Hylkää yhteyspyyntö
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Välityspalvelinta käytetään vain tarvittaessa. Toinen osapuoli voi tarkkailla IP-osoitettasi.
@@ -5866,10 +5805,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -5973,14 +5908,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Peruuta
@@ -6033,10 +5960,6 @@ chat item action
Tallenna (ja ilmoita kontakteille)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Tallenna ja ilmoita kontaktille
@@ -6507,10 +6430,6 @@ chat item action
Aseta se järjestelmän todennuksen sijaan.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8098,10 +8017,6 @@ Repeat join request?
You can view invitation link again in connection details.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Et voi lähettää viestejä!
@@ -8388,10 +8303,6 @@ Repeat connection request?
edellä, valitse sitten:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
hyväksytty puhelu
@@ -8401,10 +8312,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
ylläpitäjä
@@ -8424,10 +8331,6 @@ Repeat connection request?
hyväksyy salausta…
chat item text
-
- all
- member criteria value
-
all members
feature role
@@ -8505,10 +8408,6 @@ marked deleted chat item preview text
soittaa…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
peruutettu %@
@@ -8612,14 +8511,6 @@ marked deleted chat item preview text
contact %1$@ changed to %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
kontaktilla on e2e-salaus
@@ -8630,10 +8521,6 @@ marked deleted chat item preview text
kontaktilla ei ole e2e-salausta
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
luoja
@@ -8800,10 +8687,6 @@ pref value
ryhmä poistettu
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
ryhmäprofiili päivitetty
@@ -8926,10 +8809,6 @@ pref value
yhdistetty
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
No comment provided by engineer.
@@ -8993,10 +8872,6 @@ pref value
ei tekstiä
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
tarkkailija
@@ -9007,7 +8882,6 @@ pref value
pois
enabled status
group pref value
-member criteria value
time to disappear
@@ -9055,10 +8929,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
chat item text
@@ -9096,10 +8966,6 @@ time to disappear
removed contact address
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profile update event chat item
@@ -9109,22 +8975,10 @@ time to disappear
poisti sinut
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
No comment provided by engineer.
@@ -9295,10 +9149,6 @@ last received msg: %2$@
you
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
sinut on kutsuttu ryhmään
diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
index 80b3428cfe..59bde0650e 100644
--- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
+++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff
@@ -565,14 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Accepter les conditions
@@ -594,10 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Conditions acceptées
@@ -1592,23 +1580,11 @@ set passcode view
Le discussion sera supprimé pour vous - il n'est pas possible de revenir en arrière !
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Discussions
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Consulter les messages toutes les 20 minutes.
@@ -2424,10 +2400,6 @@ swipe action
Supprimer le profil du chat ?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
Supprimer la discussion ?
@@ -2851,7 +2823,7 @@ swipe action
Don't show again
Ne plus afficher
- alert action
+ No comment provided by engineer.
Done
@@ -3169,10 +3141,6 @@ chat item action
Erreur de validation de la demande de contact
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Erreur lors de l'ajout de membre·s
@@ -3268,10 +3236,6 @@ chat item action
Erreur lors de la suppression de la base de données du chat
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Erreur lors de la suppression du chat !
@@ -3380,7 +3344,7 @@ chat item action
Error removing member
Erreur lors de la suppression d'un membre
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4748,10 +4712,6 @@ Voici votre lien pour le groupe %@ !
Membre
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Membre inactif
@@ -4786,10 +4746,6 @@ Voici votre lien pour le groupe %@ !
Ce membre sera retiré du groupe - impossible de revenir en arrière !
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Les membres du groupe peuvent ajouter des réactions aux messages.
@@ -5204,10 +5160,6 @@ Voici votre lien pour le groupe %@ !
Nouveau rôle
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Nouveau message
@@ -5245,10 +5197,6 @@ Voici votre lien pour le groupe %@ !
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Aucun contact sélectionné
@@ -5435,8 +5383,7 @@ Voici votre lien pour le groupe %@ !
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5843,10 +5790,6 @@ Erreur : %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6291,10 +6234,6 @@ swipe action
Rejeter la demande de contact
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Le serveur relais n'est utilisé que si nécessaire. Un tiers peut observer votre adresse IP.
@@ -6400,10 +6339,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6514,14 +6449,6 @@ swipe action
Vérifier les conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Révoquer
@@ -6578,10 +6505,6 @@ chat item action
Enregistrer (et en informer les contacts)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Enregistrer et en informer le contact
@@ -7094,10 +7017,6 @@ chat item action
Il permet de remplacer l'authentification du système.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8843,10 +8762,6 @@ Répéter la demande d'adhésion ?
Vous pouvez à nouveau consulter le lien d'invitation dans les détails de la connexion.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Vous ne pouvez pas envoyer de messages !
@@ -9148,10 +9063,6 @@ Répéter la demande de connexion ?
ci-dessus, puis choisissez :
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
appel accepté
@@ -9162,10 +9073,6 @@ Répéter la demande de connexion ?
invitation acceptée
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
admin
@@ -9186,10 +9093,6 @@ Répéter la demande de connexion ?
négociation du chiffrement…
chat item text
-
- all
- member criteria value
-
all members
tous les membres
@@ -9275,10 +9178,6 @@ marked deleted chat item preview text
appel…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
annulé %@
@@ -9384,14 +9283,6 @@ marked deleted chat item preview text
le contact %1$@ est devenu %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
Ce contact a le chiffrement de bout en bout
@@ -9402,10 +9293,6 @@ marked deleted chat item preview text
Ce contact n'a pas le chiffrement de bout en bout
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
créateur
@@ -9577,10 +9464,6 @@ pref value
groupe supprimé
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
mise à jour du profil de groupe
@@ -9706,10 +9589,6 @@ pref value
est connecté·e
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
message
@@ -9774,10 +9653,6 @@ pref value
aucun texte
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
observateur
@@ -9788,7 +9663,6 @@ pref value
off
enabled status
group pref value
-member criteria value
time to disappear
@@ -9839,10 +9713,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
chiffrement e2e résistant post-quantique
@@ -9882,10 +9752,6 @@ time to disappear
suppression de l'adresse de contact
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
suppression de la photo de profil
@@ -9896,23 +9762,11 @@ time to disappear
vous a retiré
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
demande à se connecter
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
enregistré
@@ -10102,10 +9956,6 @@ dernier message reçu : %2$@
vous
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
vous êtes invité·e au groupe
diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
index 5fd4c21027..78bee138e4 100644
--- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
+++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
@@ -565,16 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- Befogadás tagként
- alert action
-
-
- Accept as observer
- Befogadás megfigyelőként
- alert action
-
Accept conditions
Feltételek elfogadása
@@ -596,11 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- Tag befogadása
- alert title
-
Accepted conditions
Elfogadott feltételek
@@ -1597,26 +1582,11 @@ set passcode view
A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza!
No comment provided by engineer.
-
- Chat with admins
- Csevegés az adminisztrátorokkal
- chat toolbar
-
-
- Chat with member
- Csevegés a taggal
- No comment provided by engineer.
-
Chats
Csevegések
No comment provided by engineer.
-
- Chats with members
- Csevegés a tagokkal
- No comment provided by engineer.
-
Check messages every 20 min.
Üzenetek ellenőrzése 20 percenként.
@@ -2270,7 +2240,7 @@ Ez a saját egyszer használható meghívója!
Database IDs and Transport isolation option.
- Adatbázis-azonosítók és átvitelelkülönítési beállítások.
+ Adatbázis-azonosítók és átvitel-izolációs beállítások.
No comment provided by engineer.
@@ -2368,7 +2338,7 @@ Ez a saját egyszer használható meghívója!
Decryption error
- Titkosításvisszafejtési hiba
+ Titkosítás visszafejtési hiba
message decrypt error item
@@ -2432,11 +2402,6 @@ swipe action
Törli a csevegési profilt?
No comment provided by engineer.
-
- Delete chat with member?
- Törli a taggal való csevegést?
- alert title
-
Delete chat?
Törli a csevegést?
@@ -2709,7 +2674,7 @@ swipe action
Different names, avatars and transport isolation.
- Különböző nevek, profilképek és átvitelizoláció.
+ Különböző nevek, profilképek és átvitel-izoláció.
No comment provided by engineer.
@@ -2860,7 +2825,7 @@ swipe action
Don't show again
Ne mutasd újra
- alert action
+ No comment provided by engineer.
Done
@@ -3178,11 +3143,6 @@ chat item action
Hiba történt a meghívási kérés elfogadásakor
No comment provided by engineer.
-
- Error accepting member
- Hiba a tag befogadásakor
- alert title
-
Error adding member(s)
Hiba történt a tag(ok) hozzáadásakor
@@ -3278,11 +3238,6 @@ chat item action
Hiba történt a csevegési adatbázis törlésekor
No comment provided by engineer.
-
- Error deleting chat with member
- Hiba a taggal való csevegés törlésekor
- alert title
-
Error deleting chat!
Hiba történt a csevegés törlésekor!
@@ -3310,7 +3265,7 @@ chat item action
Error deleting user profile
- Hiba történt a felhasználói profil törlésekor
+ Hiba történt a felhasználó-profil törlésekor
No comment provided by engineer.
@@ -3391,7 +3346,7 @@ chat item action
Error removing member
Hiba történt a tag eltávolításakor
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4494,7 +4449,7 @@ További fejlesztések hamarosan!
It can happen when you or your connection used the old database backup.
- Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.
+ Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt.
No comment provided by engineer.
@@ -4504,7 +4459,7 @@ További fejlesztések hamarosan!
3. The connection was compromised.
Ez akkor fordulhat elő, ha:
1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.
-2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.
+2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.
3. A kapcsolat sérült.
No comment provided by engineer.
@@ -4775,11 +4730,6 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Tag
No comment provided by engineer.
-
- Member admission
- Tagbefogadás
- No comment provided by engineer.
-
Member inactive
Inaktív tag
@@ -4815,11 +4765,6 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!
No comment provided by engineer.
-
- Member will join the group, accept member?
- A tag csatlakozni akar a csoporthoz, befogadja a tagot?
- alert message
-
Members can add message reactions.
A tagok reakciókat adhatnak hozzá az üzenetekhez.
@@ -4997,12 +4942,12 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.
- Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.
+ Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.
No comment provided by engineer.
Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.
- Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.
+ Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.
No comment provided by engineer.
@@ -5240,11 +5185,6 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Új tag szerepköre
No comment provided by engineer.
-
- New member wants to join the group.
- Új tag szeretne csatlakozni a csoporthoz.
- rcv group event chat item
-
New message
Új üzenet
@@ -5285,11 +5225,6 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Nincsenek csevegések a(z) %@ nevű listában
No comment provided by engineer.
-
- No chats with members
- Nincsenek csevegések a tagokkal
- No comment provided by engineer.
-
No contacts selected
Nincs partner kijelölve
@@ -5412,7 +5347,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
No user identifiers.
- Nincsenek felhasználói azonosítók.
+ Nincsenek felhasználó-azonosítók.
No comment provided by engineer.
@@ -5482,8 +5417,7 @@ Ez a saját hivatkozása a(z) %@ nevű csoporthoz!
Ok
Rendben
- alert action
-alert button
+ alert button
Old database
@@ -5641,7 +5575,6 @@ VPN engedélyezése szükséges.
Open link?
- Megnyitja a hivatkozást?
alert title
@@ -5895,11 +5828,6 @@ Hiba: %@
Próbálja meg letiltani és újra engedélyezni az értesítéseket.
token info
-
- Please wait for group moderators to review your request to join the group.
- Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérelmét.
- snd group event chat item
-
Please wait for token activation to complete.
Várjon, amíg a token aktiválása befejeződik.
@@ -6353,19 +6281,14 @@ swipe action
Meghívási kérés elutasítása
No comment provided by engineer.
-
- Reject member?
- Elutasítja a tagot?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
- A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét.
+ A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet.
No comment provided by engineer.
Relay server protects your IP address, but it can observe the duration of the call.
- A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.
+ A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát.
No comment provided by engineer.
@@ -6468,11 +6391,6 @@ swipe action
Jelentés indoklása?
No comment provided by engineer.
-
- Report sent to moderators
- A jelentés el lett küldve a moderátoroknak
- alert title
-
Report spam: only group moderators will see it.
Kéretlen tartalom jelentése: csak a csoport moderátorai látják.
@@ -6588,16 +6506,6 @@ swipe action
Feltételek felülvizsgálata
No comment provided by engineer.
-
- Review members
- Tagok áttekintése
- admission stage
-
-
- Review members before admitting ("knocking").
- Tagok áttekintése a befogadás előtt (kopogtatás).
- admission stage description
-
Revoke
Visszavonás
@@ -6654,11 +6562,6 @@ chat item action
Mentés (és a partnerek értesítése)
alert button
-
- Save admission settings?
- Elmenti a befogadási beállításokat?
- alert title
-
Save and notify contact
Mentés és a partner értesítése
@@ -7174,11 +7077,6 @@ chat item action
Beállítás a rendszer-hitelesítés helyett.
No comment provided by engineer.
-
- Set member admission
- Tagbefogadás beállítása
- No comment provided by engineer.
-
Set message expiration in chats.
Üzenetek eltűnési idejének módosítása a csevegésekben.
@@ -7437,7 +7335,7 @@ chat item action
SimpleX protocols reviewed by Trail of Bits.
- A SimpleX-protokollokat a Trail of Bits auditálta.
+ A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva.
No comment provided by engineer.
@@ -8129,7 +8027,7 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
Transport isolation
- Átvitelelkülönítés
+ Átvitel-izoláció
No comment provided by engineer.
@@ -8441,7 +8339,7 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
Use private routing with unknown servers when IP address is not protected.
- Használjon privát útválasztást az ismeretlen kiszolgálókkal, ha az IP-cím nem védett.
+ Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett.
No comment provided by engineer.
@@ -8726,12 +8624,12 @@ A kapcsolódáshoz kérje meg a partnerét, hogy hozzon létre egy másik kapcso
Without Tor or VPN, your IP address will be visible to file servers.
- Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára.
+ Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára.
No comment provided by engineer.
Without Tor or VPN, your IP address will be visible to these XFTP relays: %@.
- Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@.
+ Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@.
alert message
@@ -8941,11 +8839,6 @@ Megismétli a meghívási kérést?
A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél.
alert message
-
- You can view your reports in Chat with admins.
- A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben.
- alert message
-
You can't send messages!
Nem lehet üzeneteket küldeni!
@@ -9185,7 +9078,7 @@ Megismétli a meghívási kérést?
Your profile is stored on your device and only shared with your contacts.
- A profilja az eszközén van tárolva és csak a partnereivel van megosztva.
+ A profilja csak a partnereivel van megosztva.
No comment provided by engineer.
@@ -9248,11 +9141,6 @@ Megismétli a meghívási kérést?
gombra fent, majd válassza ki:
No comment provided by engineer.
-
- accepted %@
- befogadta őt: %@
- rcv group event chat item
-
accepted call
fogadott hívás
@@ -9263,11 +9151,6 @@ Megismétli a meghívási kérést?
elfogadott meghívó
chat list item title
-
- accepted you
- befogadta Önt
- rcv group event chat item
-
admin
adminisztrátor
@@ -9288,11 +9171,6 @@ Megismétli a meghívási kérést?
titkosítás elfogadása…
chat item text
-
- all
- összes
- member criteria value
-
all members
összes tag
@@ -9379,11 +9257,6 @@ marked deleted chat item preview text
hívás…
call status
-
- can't send messages
- nem lehet üzeneteket küldeni
- No comment provided by engineer.
-
cancelled %@
%@ visszavonva
@@ -9489,16 +9362,6 @@ marked deleted chat item preview text
%1$@ a következőre módosította a nevét: %2$@
profile update event chat item
-
- contact deleted
- partner törölve
- No comment provided by engineer.
-
-
- contact disabled
- partner letiltva
- No comment provided by engineer.
-
contact has e2e encryption
a partner e2e titkosítással rendelkezik
@@ -9509,11 +9372,6 @@ marked deleted chat item preview text
a partner nem rendelkezik e2e titkosítással
No comment provided by engineer.
-
- contact not ready
- a kapcsolat nem áll készen
- No comment provided by engineer.
-
creator
készítő
@@ -9685,11 +9543,6 @@ pref value
a csoport törölve
No comment provided by engineer.
-
- group is deleted
- csoport törölve
- No comment provided by engineer.
-
group profile updated
csoportprofil frissítve
@@ -9815,11 +9668,6 @@ pref value
kapcsolódott
rcv group event chat item
-
- member has old version
- a tag régi verziót használ
- No comment provided by engineer.
-
message
üzenet
@@ -9885,11 +9733,6 @@ pref value
nincs szöveg
copied message info in history
-
- not synchronized
- nincs szinkronizálva
- No comment provided by engineer.
-
observer
megfigyelő
@@ -9900,7 +9743,6 @@ pref value
kikapcsolva
enabled status
group pref value
-member criteria value
time to disappear
@@ -9953,11 +9795,6 @@ time to disappear
jóváhagyásra vár
No comment provided by engineer.
-
- pending review
- függőben lévő áttekintés
- No comment provided by engineer.
-
quantum resistant e2e encryption
végpontok közötti kvantumbiztos titkosítás
@@ -9998,11 +9835,6 @@ time to disappear
eltávolította a kapcsolattartási címet
profile update event chat item
-
- removed from group
- eltávolítva a csoportból
- No comment provided by engineer.
-
removed profile picture
eltávolította a profilképét
@@ -10013,26 +9845,11 @@ time to disappear
eltávolította Önt
rcv group event chat item
-
- request to join rejected
- csatlakozási kérelem elutasítva
- No comment provided by engineer.
-
requested to connect
Függőben lévő meghívási kérelem
chat list item title
-
- review
- áttekintés
- No comment provided by engineer.
-
-
- reviewed by admins
- áttekintve a moderátorok által
- No comment provided by engineer.
-
saved
mentett
@@ -10222,11 +10039,6 @@ utoljára fogadott üzenet: %2$@
Ön
No comment provided by engineer.
-
- you accepted this member
- Ön befogadta ezt a tagot
- snd group event chat item
-
you are invited to group
Ön meghívást kapott a csoportba
diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
index d3c2a139cc..cf5f61918f 100644
--- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
+++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff
@@ -565,16 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- Accetta come membro
- alert action
-
-
- Accept as observer
- Accetta come osservatore
- alert action
-
Accept conditions
Accetta le condizioni
@@ -596,11 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- Accetta membro
- alert title
-
Accepted conditions
Condizioni accettate
@@ -1597,26 +1582,11 @@ set passcode view
La chat verrà eliminata solo per te, non è reversibile!
No comment provided by engineer.
-
- Chat with admins
- Chat con amministratori
- chat toolbar
-
-
- Chat with member
- Chatta con il membro
- No comment provided by engineer.
-
Chats
Chat
No comment provided by engineer.
-
- Chats with members
- Chat con membri
- No comment provided by engineer.
-
Check messages every 20 min.
Controlla i messaggi ogni 20 min.
@@ -2432,11 +2402,6 @@ swipe action
Eliminare il profilo di chat?
No comment provided by engineer.
-
- Delete chat with member?
- Eliminare la chat con il membro?
- alert title
-
Delete chat?
Eliminare la chat?
@@ -2860,7 +2825,7 @@ swipe action
Don't show again
Non mostrare più
- alert action
+ No comment provided by engineer.
Done
@@ -3178,11 +3143,6 @@ chat item action
Errore nell'accettazione della richiesta di contatto
No comment provided by engineer.
-
- Error accepting member
- Errore di accettazione del membro
- alert title
-
Error adding member(s)
Errore di aggiunta membro/i
@@ -3278,11 +3238,6 @@ chat item action
Errore nell'eliminazione del database della chat
No comment provided by engineer.
-
- Error deleting chat with member
- Errore di eliminazione della chat con il membro
- alert title
-
Error deleting chat!
Errore nell'eliminazione della chat!
@@ -3391,7 +3346,7 @@ chat item action
Error removing member
Errore nella rimozione del membro
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4775,11 +4730,6 @@ Questo è il tuo link per il gruppo %@!
Membro
No comment provided by engineer.
-
- Member admission
- Ammissione del membro
- No comment provided by engineer.
-
Member inactive
Membro inattivo
@@ -4815,11 +4765,6 @@ Questo è il tuo link per il gruppo %@!
Il membro verrà rimosso dal gruppo, non è reversibile!
No comment provided by engineer.
-
- Member will join the group, accept member?
- Il membro entrerà nel gruppo, accettarlo?
- alert message
-
Members can add message reactions.
I membri del gruppo possono aggiungere reazioni ai messaggi.
@@ -5240,11 +5185,6 @@ Questo è il tuo link per il gruppo %@!
Nuovo ruolo del membro
No comment provided by engineer.
-
- New member wants to join the group.
- Un nuovo membro vuole entrare nel gruppo.
- rcv group event chat item
-
New message
Nuovo messaggio
@@ -5285,11 +5225,6 @@ Questo è il tuo link per il gruppo %@!
Nessuna chat nell'elenco %@
No comment provided by engineer.
-
- No chats with members
- Nessuna chat con membri
- No comment provided by engineer.
-
No contacts selected
Nessun contatto selezionato
@@ -5482,8 +5417,7 @@ Questo è il tuo link per il gruppo %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5641,7 +5575,6 @@ Richiede l'attivazione della VPN.
Open link?
- Aprire il link?
alert title
@@ -5895,11 +5828,6 @@ Errore: %@
Prova a disattivare e riattivare le notifiche.
token info
-
- Please wait for group moderators to review your request to join the group.
- Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo.
- snd group event chat item
-
Please wait for token activation to complete.
Attendi il completamento dell'attivazione del token.
@@ -6353,11 +6281,6 @@ swipe action
Rifiuta la richiesta di contatto
No comment provided by engineer.
-
- Reject member?
- Rifiutare il membro?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Il server relay viene usato solo se necessario. Un altro utente può osservare il tuo indirizzo IP.
@@ -6468,11 +6391,6 @@ swipe action
Motivo della segnalazione?
No comment provided by engineer.
-
- Report sent to moderators
- Segnalazione inviata ai moderatori
- alert title
-
Report spam: only group moderators will see it.
Segnala spam: solo i moderatori del gruppo lo vedranno.
@@ -6588,16 +6506,6 @@ swipe action
Leggi le condizioni
No comment provided by engineer.
-
- Review members
- Revisiona i membri
- admission stage
-
-
- Review members before admitting ("knocking").
- Revisiona i membri prima di ammetterli ("bussare").
- admission stage description
-
Revoke
Revoca
@@ -6654,11 +6562,6 @@ chat item action
Salva (e avvisa i contatti)
alert button
-
- Save admission settings?
- Salvare le impostazioni di ammissione?
- alert title
-
Save and notify contact
Salva e avvisa il contatto
@@ -7174,11 +7077,6 @@ chat item action
Impostalo al posto dell'autenticazione di sistema.
No comment provided by engineer.
-
- Set member admission
- Imposta l'ammissione del membro
- No comment provided by engineer.
-
Set message expiration in chats.
Imposta la scadenza dei messaggi nelle chat.
@@ -8941,11 +8839,6 @@ Ripetere la richiesta di ingresso?
Puoi vedere di nuovo il link di invito nei dettagli di connessione.
alert message
-
- You can view your reports in Chat with admins.
- Puoi vedere le tue segnalazioni nella chat con gli amministratori.
- alert message
-
You can't send messages!
Non puoi inviare messaggi!
@@ -9248,11 +9141,6 @@ Ripetere la richiesta di connessione?
sopra, quindi scegli:
No comment provided by engineer.
-
- accepted %@
- %@ accettato
- rcv group event chat item
-
accepted call
chiamata accettata
@@ -9263,11 +9151,6 @@ Ripetere la richiesta di connessione?
invito accettato
chat list item title
-
- accepted you
- ti ha accettato/a
- rcv group event chat item
-
admin
amministratore
@@ -9288,11 +9171,6 @@ Ripetere la richiesta di connessione?
concordando la crittografia…
chat item text
-
- all
- tutti
- member criteria value
-
all members
tutti i membri
@@ -9379,11 +9257,6 @@ marked deleted chat item preview text
chiamata…
call status
-
- can't send messages
- impossibile inviare messaggi
- No comment provided by engineer.
-
cancelled %@
annullato %@
@@ -9489,16 +9362,6 @@ marked deleted chat item preview text
contatto %1$@ cambiato in %2$@
profile update event chat item
-
- contact deleted
- contatto eliminato
- No comment provided by engineer.
-
-
- contact disabled
- contatto disattivato
- No comment provided by engineer.
-
contact has e2e encryption
il contatto ha la crittografia e2e
@@ -9509,11 +9372,6 @@ marked deleted chat item preview text
il contatto non ha la crittografia e2e
No comment provided by engineer.
-
- contact not ready
- contatto non pronto
- No comment provided by engineer.
-
creator
creatore
@@ -9685,11 +9543,6 @@ pref value
gruppo eliminato
No comment provided by engineer.
-
- group is deleted
- il gruppo è eliminato
- No comment provided by engineer.
-
group profile updated
profilo del gruppo aggiornato
@@ -9815,11 +9668,6 @@ pref value
si è connesso/a
rcv group event chat item
-
- member has old version
- il membro ha una versione vecchia
- No comment provided by engineer.
-
message
messaggio
@@ -9885,11 +9733,6 @@ pref value
nessun testo
copied message info in history
-
- not synchronized
- non sincronizzato
- No comment provided by engineer.
-
observer
osservatore
@@ -9900,7 +9743,6 @@ pref value
off
enabled status
group pref value
-member criteria value
time to disappear
@@ -9953,11 +9795,6 @@ time to disappear
in attesa di approvazione
No comment provided by engineer.
-
- pending review
- in attesa di revisione
- No comment provided by engineer.
-
quantum resistant e2e encryption
crittografia e2e resistente alla quantistica
@@ -9998,11 +9835,6 @@ time to disappear
indirizzo di contatto rimosso
profile update event chat item
-
- removed from group
- rimosso dal gruppo
- No comment provided by engineer.
-
removed profile picture
immagine del profilo rimossa
@@ -10013,26 +9845,11 @@ time to disappear
ti ha rimosso/a
rcv group event chat item
-
- request to join rejected
- richiesta di entrare rifiutata
- No comment provided by engineer.
-
requested to connect
richiesto di connettersi
chat list item title
-
- review
- revisiona
- No comment provided by engineer.
-
-
- reviewed by admins
- revisionato dagli amministratori
- No comment provided by engineer.
-
saved
salvato
@@ -10222,11 +10039,6 @@ ultimo msg ricevuto: %2$@
tu
No comment provided by engineer.
-
- you accepted this member
- hai accettato questo membro
- snd group event chat item
-
you are invited to group
sei stato/a invitato/a al gruppo
diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
index 2a7bfa8df1..27134216a7 100644
--- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
+++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff
@@ -561,14 +561,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
No comment provided by engineer.
@@ -589,10 +581,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
No comment provided by engineer.
@@ -1500,23 +1488,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
チャット
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
No comment provided by engineer.
@@ -2268,10 +2244,6 @@ swipe action
チャットのプロフィールを削除しますか?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2665,7 +2637,7 @@ swipe action
Don't show again
次から表示しない
- alert action
+ No comment provided by engineer.
Done
@@ -2959,10 +2931,6 @@ chat item action
連絡先リクエストの承諾にエラー発生
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
メンバー追加にエラー発生
@@ -3050,10 +3018,6 @@ chat item action
チャットデータベース削除にエラー発生
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
チャット削除にエラー発生!
@@ -3153,7 +3117,7 @@ chat item action
Error removing member
メンバー除名にエラー発生
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4420,10 +4384,6 @@ This is your link for group %@!
メンバー
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
item status text
@@ -4455,10 +4415,6 @@ This is your link for group %@!
メンバーをグループから除名する (※元に戻せません※)!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
グループメンバーはメッセージへのリアクションを追加できます。
@@ -4836,10 +4792,6 @@ This is your link for group %@!
新しいメンバーの役割
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
新しいメッセージ
@@ -4876,10 +4828,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
連絡先が選択されてません
@@ -5050,8 +4998,7 @@ This is your link for group %@!
Ok
OK
- alert action
-alert button
+ alert button
Old database
@@ -5426,10 +5373,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -5841,10 +5784,6 @@ swipe action
連絡要求を拒否する
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
中継サーバーは必要な場合にのみ使用されます。 別の当事者があなたの IP アドレスを監視できます。
@@ -5943,10 +5882,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6050,14 +5985,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
取り消す
@@ -6110,10 +6037,6 @@ chat item action
保存(連絡先に通知)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
保存して、連絡先にに知らせる
@@ -6577,10 +6500,6 @@ chat item action
システム認証の代わりに設定します。
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8169,10 +8088,6 @@ Repeat join request?
You can view invitation link again in connection details.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
メッセージを送信できませんでした!
@@ -8459,10 +8374,6 @@ Repeat connection request?
上で選んでください:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
受けた通話
@@ -8472,10 +8383,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
管理者
@@ -8495,10 +8402,6 @@ Repeat connection request?
暗号化に同意しています…
chat item text
-
- all
- member criteria value
-
all members
feature role
@@ -8576,10 +8479,6 @@ marked deleted chat item preview text
発信中…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
キャンセルされました %@
@@ -8683,14 +8582,6 @@ marked deleted chat item preview text
contact %1$@ changed to %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
連絡先はエンドツーエンド暗号化があります
@@ -8701,10 +8592,6 @@ marked deleted chat item preview text
連絡先はエンドツーエンド暗号化がありません
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
作成者
@@ -8871,10 +8758,6 @@ pref value
グループ削除済み
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
グループのプロフィールが更新されました
@@ -8997,10 +8880,6 @@ pref value
接続中
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
No comment provided by engineer.
@@ -9064,10 +8943,6 @@ pref value
テキストなし
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
オブザーバー
@@ -9078,7 +8953,6 @@ pref value
オフ
enabled status
group pref value
-member criteria value
time to disappear
@@ -9126,10 +9000,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
chat item text
@@ -9167,10 +9037,6 @@ time to disappear
removed contact address
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profile update event chat item
@@ -9180,22 +9046,10 @@ time to disappear
あなたを除名しました
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
No comment provided by engineer.
@@ -9366,10 +9220,6 @@ last received msg: %2$@
you
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
グループ招待が届きました
diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
index d0b430cf02..4008c57ac0 100644
--- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
+++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff
@@ -565,14 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Accepteer voorwaarden
@@ -594,10 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Geaccepteerde voorwaarden
@@ -1593,23 +1581,11 @@ set passcode view
De chat wordt voor je verwijderd - dit kan niet ongedaan worden gemaakt!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Chats
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Controleer uw berichten elke 20 minuten.
@@ -2425,10 +2401,6 @@ swipe action
Chatprofiel verwijderen?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
Chat verwijderen?
@@ -2852,7 +2824,7 @@ swipe action
Don't show again
Niet meer weergeven
- alert action
+ No comment provided by engineer.
Done
@@ -3170,10 +3142,6 @@ chat item action
Fout bij het accepteren van een contactverzoek
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Fout bij het toevoegen van leden
@@ -3269,10 +3237,6 @@ chat item action
Fout bij het verwijderen van de chat database
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Fout bij verwijderen gesprek!
@@ -3381,7 +3345,7 @@ chat item action
Error removing member
Fout bij verwijderen van lid
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4765,10 +4729,6 @@ Dit is jouw link voor groep %@!
Lid
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Lid inactief
@@ -4804,10 +4764,6 @@ Dit is jouw link voor groep %@!
Lid wordt uit de groep verwijderd, dit kan niet ongedaan worden gemaakt!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Groepsleden kunnen bericht reacties toevoegen.
@@ -5228,10 +5184,6 @@ Dit is jouw link voor groep %@!
Nieuwe leden rol
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
nieuw bericht
@@ -5272,10 +5224,6 @@ Dit is jouw link voor groep %@!
Geen chats in lijst %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Geen contacten geselecteerd
@@ -5468,8 +5416,7 @@ Dit is jouw link voor groep %@!
Ok
OK
- alert action
-alert button
+ alert button
Old database
@@ -5880,10 +5827,6 @@ Fout: %@
Probeer meldingen uit en weer in te schakelen.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
Wacht tot de tokenactivering voltooid is.
@@ -6337,10 +6280,6 @@ swipe action
Contactverzoek afwijzen
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Relay server wordt alleen gebruikt als dat nodig is. Een andere partij kan uw IP-adres zien.
@@ -6451,10 +6390,6 @@ swipe action
Reden melding?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
Spam melden: alleen groepsmoderators kunnen het zien.
@@ -6570,14 +6505,6 @@ swipe action
Voorwaarden bekijken
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Intrekken
@@ -6634,10 +6561,6 @@ chat item action
Bewaar (en informeer contacten)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Opslaan en Contact melden
@@ -7153,10 +7076,6 @@ chat item action
Stel het in in plaats van systeemverificatie.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
Stel de berichtvervaldatum in chats in.
@@ -8913,10 +8832,6 @@ Deelnameverzoek herhalen?
U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Je kunt geen berichten versturen!
@@ -9219,10 +9134,6 @@ Verbindingsverzoek herhalen?
hier boven, kies dan:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
geaccepteerde oproep
@@ -9233,10 +9144,6 @@ Verbindingsverzoek herhalen?
geaccepteerde uitnodiging
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
Beheerder
@@ -9257,10 +9164,6 @@ Verbindingsverzoek herhalen?
versleuteling overeenkomen…
chat item text
-
- all
- member criteria value
-
all members
alle leden
@@ -9347,10 +9250,6 @@ marked deleted chat item preview text
bellen…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
geannuleerd %@
@@ -9456,14 +9355,6 @@ marked deleted chat item preview text
contactpersoon %1$@ gewijzigd in %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
contact heeft e2e-codering
@@ -9474,10 +9365,6 @@ marked deleted chat item preview text
contact heeft geen e2e versleuteling
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
creator
@@ -9649,10 +9536,6 @@ pref value
groep verwijderd
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
groep profiel bijgewerkt
@@ -9778,10 +9661,6 @@ pref value
is toegetreden
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
bericht
@@ -9847,10 +9726,6 @@ pref value
geen tekst
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
Waarnemer
@@ -9861,7 +9736,6 @@ pref value
uit
enabled status
group pref value
-member criteria value
time to disappear
@@ -9914,10 +9788,6 @@ time to disappear
in afwachting van goedkeuring
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
quantum bestendige e2e-codering
@@ -9958,10 +9828,6 @@ time to disappear
contactadres verwijderd
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profielfoto verwijderd
@@ -9972,23 +9838,11 @@ time to disappear
heeft je verwijderd
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
verzocht om verbinding te maken
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
opgeslagen
@@ -10178,10 +10032,6 @@ laatst ontvangen bericht: %2$@
jij
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
je bent uitgenodigd voor de groep
diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
index 3255489efd..175c8b4112 100644
--- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
+++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff
@@ -565,14 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Zaakceptuj warunki
@@ -594,10 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Zaakceptowano warunki
@@ -1587,23 +1575,11 @@ set passcode view
Czat zostanie usunięty dla Ciebie – tej operacji nie można cofnąć!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Czaty
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Sprawdzaj wiadomości co 20 min.
@@ -2398,10 +2374,6 @@ swipe action
Usunąć profil czatu?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2815,7 +2787,7 @@ swipe action
Don't show again
Nie pokazuj ponownie
- alert action
+ No comment provided by engineer.
Done
@@ -3128,10 +3100,6 @@ chat item action
Błąd przyjmowania prośby o kontakt
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Błąd dodawania członka(ów)
@@ -3223,10 +3191,6 @@ chat item action
Błąd usuwania bazy danych czatu
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Błąd usuwania czatu!
@@ -3333,7 +3297,7 @@ chat item action
Error removing member
Błąd usuwania członka
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4677,10 +4641,6 @@ To jest twój link do grupy %@!
Członek
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Członek nieaktywny
@@ -4713,10 +4673,6 @@ To jest twój link do grupy %@!
Członek zostanie usunięty z grupy - nie można tego cofnąć!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Członkowie grupy mogą dodawać reakcje wiadomości.
@@ -5127,10 +5083,6 @@ To jest twój link do grupy %@!
Nowa rola członka
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Nowa wiadomość
@@ -5167,10 +5119,6 @@ To jest twój link do grupy %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Nie wybrano kontaktów
@@ -5350,8 +5298,7 @@ To jest twój link do grupy %@!
Ok
Ok
- alert action
-alert button
+ alert button
Old database
@@ -5751,10 +5698,6 @@ Błąd: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6197,10 +6140,6 @@ swipe action
Odrzuć prośbę kontaktu
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Serwer przekaźnikowy jest używany tylko w razie potrzeby. Inna strona może obserwować Twój adres IP.
@@ -6306,10 +6245,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6419,14 +6354,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Odwołaj
@@ -6483,10 +6410,6 @@ chat item action
Zapisz (i powiadom kontakty)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Zapisz i powiadom kontakt
@@ -6995,10 +6918,6 @@ chat item action
Ustaw go zamiast uwierzytelniania systemowego.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8711,10 +8630,6 @@ Powtórzyć prośbę dołączenia?
Możesz zobaczyć link zaproszenia ponownie w szczegółach połączenia.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Nie możesz wysyłać wiadomości!
@@ -9015,10 +8930,6 @@ Powtórzyć prośbę połączenia?
powyżej, a następnie wybierz:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
zaakceptowane połączenie
@@ -9028,10 +8939,6 @@ Powtórzyć prośbę połączenia?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
administrator
@@ -9052,10 +8959,6 @@ Powtórzyć prośbę połączenia?
uzgadnianie szyfrowania…
chat item text
-
- all
- member criteria value
-
all members
wszyscy członkowie
@@ -9141,10 +9044,6 @@ marked deleted chat item preview text
dzwonie…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
anulowany %@
@@ -9250,14 +9149,6 @@ marked deleted chat item preview text
kontakt %1$@ zmieniony na %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
kontakt posiada szyfrowanie e2e
@@ -9268,10 +9159,6 @@ marked deleted chat item preview text
kontakt nie posiada szyfrowania e2e
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
twórca
@@ -9443,10 +9330,6 @@ pref value
grupa usunięta
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
zaktualizowano profil grupy
@@ -9572,10 +9455,6 @@ pref value
połączony
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
wiadomość
@@ -9640,10 +9519,6 @@ pref value
brak tekstu
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
obserwator
@@ -9654,7 +9529,6 @@ pref value
wyłączony
enabled status
group pref value
-member criteria value
time to disappear
@@ -9705,10 +9579,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
kwantowo odporne szyfrowanie e2e
@@ -9748,10 +9618,6 @@ time to disappear
usunięto adres kontaktu
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
usunięto zdjęcie profilu
@@ -9762,22 +9628,10 @@ time to disappear
usunął cię
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
zapisane
@@ -9967,10 +9821,6 @@ ostatnia otrzymana wiadomość: %2$@
Ty
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
jesteś zaproszony do grupy
diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
index 2ec1130718..419fa75375 100644
--- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
+++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff
@@ -167,7 +167,7 @@
%d hours
- %d час.
+ %d ч.
time interval
@@ -565,16 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- Принять в группу
- alert action
-
-
- Accept as observer
- Принять как читателя
- alert action
-
Accept conditions
Принять условия
@@ -596,11 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- Принять члена
- alert title
-
Accepted conditions
Принятые условия
@@ -813,7 +798,6 @@ swipe action
All servers
- Все серверы
No comment provided by engineer.
@@ -1597,26 +1581,11 @@ set passcode view
Разговор будет удален для Вас - это действие нельзя отменить!
No comment provided by engineer.
-
- Chat with admins
- Чат с админами
- chat toolbar
-
-
- Chat with member
- Чат с членом группы
- No comment provided by engineer.
-
Chats
Чаты
No comment provided by engineer.
-
- Chats with members
- Чаты с членами группы
- No comment provided by engineer.
-
Check messages every 20 min.
Проверять сообщения каждые 20 минут.
@@ -2432,11 +2401,6 @@ swipe action
Удалить профиль?
No comment provided by engineer.
-
- Delete chat with member?
- Удалить чат с членом группы?
- alert title
-
Delete chat?
Удалить разговор?
@@ -2724,7 +2688,6 @@ swipe action
Direct messages between members are prohibited.
- Прямые сообщения между членами запрещены.
No comment provided by engineer.
@@ -2829,7 +2792,6 @@ swipe action
Do not send history to new members.
- Не отправлять историю новым членам.
No comment provided by engineer.
@@ -2860,7 +2822,7 @@ swipe action
Don't show again
Не показывать
- alert action
+ No comment provided by engineer.
Done
@@ -2955,7 +2917,6 @@ chat item action
Enable Flux in Network & servers settings for better metadata privacy.
- Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных.
No comment provided by engineer.
@@ -3178,14 +3139,8 @@ chat item action
Ошибка при принятии запроса на соединение
No comment provided by engineer.
-
- Error accepting member
- Ошибка вступления члена группы
- alert title
-
Error adding member(s)
- Ошибка при добавлении членов группы
No comment provided by engineer.
@@ -3250,7 +3205,6 @@ chat item action
Error creating member contact
- Ошибка при создании контакта
No comment provided by engineer.
@@ -3278,11 +3232,6 @@ chat item action
Ошибка при удалении данных чата
No comment provided by engineer.
-
- Error deleting chat with member
- Ошибка при удалении чата с членом группы
- alert title
-
Error deleting chat!
Ошибка при удалении чата!
@@ -3390,8 +3339,7 @@ chat item action
Error removing member
- Ошибка при удалении члена группы
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -3455,7 +3403,6 @@ chat item action
Error sending member contact invitation
- Ошибка при отправке приглашения члену
No comment provided by engineer.
@@ -3791,7 +3738,6 @@ snd error text
Fix not supported by group member
- Починка не поддерживается членом группы.
No comment provided by engineer.
@@ -3925,7 +3871,6 @@ Error: %2$@
Fully decentralized – visible only to members.
- Группа полностью децентрализована – она видна только членам.
No comment provided by engineer.
@@ -4035,7 +3980,6 @@ Error: %2$@
Group profile is stored on members' devices, not on the servers.
- Профиль группы хранится на устройствах членов, а не на серверах.
No comment provided by engineer.
@@ -4045,7 +3989,6 @@ Error: %2$@
Group will be deleted for all members - this cannot be undone!
- Группа будет удалена для всех членов - это действие нельзя отменить!
No comment provided by engineer.
@@ -4110,7 +4053,6 @@ Error: %2$@
History is not sent to new members.
- История не отправляется новым членам.
No comment provided by engineer.
@@ -4458,7 +4400,6 @@ More improvements are coming soon!
Invite members
- Пригласить членов группы
No comment provided by engineer.
@@ -4771,17 +4712,10 @@ This is your link for group %@!
Member
- Член группы
- No comment provided by engineer.
-
-
- Member admission
- Приём членов в группу
No comment provided by engineer.
Member inactive
- Член неактивен
item status text
@@ -4796,72 +4730,54 @@ This is your link for group %@!
Member role will be changed to "%@". All group members will be notified.
- Роль члена будет изменена на "%@". Все члены группы получат уведомление.
No comment provided by engineer.
Member role will be changed to "%@". The member will receive a new invitation.
- Роль члена будет изменена на "%@". Будет отправлено новое приглашение.
No comment provided by engineer.
Member will be removed from chat - this cannot be undone!
- Член будет удален из разговора - это действие нельзя отменить!
No comment provided by engineer.
Member will be removed from group - this cannot be undone!
- Член группы будет удален - это действие нельзя отменить!
No comment provided by engineer.
-
- Member will join the group, accept member?
- Участник хочет присоединиться к группе. Принять?
- alert message
-
Members can add message reactions.
- Члены могут добавлять реакции на сообщения.
No comment provided by engineer.
Members can irreversibly delete sent messages. (24 hours)
- Члены могут необратимо удалять отправленные сообщения. (24 часа)
No comment provided by engineer.
Members can report messsages to moderators.
- Члены группы могут пожаловаться модераторам.
No comment provided by engineer.
Members can send SimpleX links.
- Члены могут отправлять ссылки SimpleX.
No comment provided by engineer.
Members can send direct messages.
- Члены могут посылать прямые сообщения.
No comment provided by engineer.
Members can send disappearing messages.
- Члены могут посылать исчезающие сообщения.
No comment provided by engineer.
Members can send files and media.
- Члены могут слать файлы и медиа.
No comment provided by engineer.
Members can send voice messages.
- Члены могут отправлять голосовые сообщения.
No comment provided by engineer.
Mention members 👋
- Упоминайте участников 👋
No comment provided by engineer.
Message may be delivered later if member becomes active.
- Сообщение может быть доставлено позже, если член группы станет активным.
item status description
@@ -5236,14 +5151,8 @@ This is your link for group %@!
New member role
- Роль члена группы
No comment provided by engineer.
-
- New member wants to join the group.
- Новый участник хочет присоединиться к группе.
- rcv group event chat item
-
New message
Новое сообщение
@@ -5284,11 +5193,6 @@ This is your link for group %@!
Нет чатов в списке %@
No comment provided by engineer.
-
- No chats with members
- Нет чатов с членами группы
- No comment provided by engineer.
-
No contacts selected
Контакты не выбраны
@@ -5463,9 +5367,6 @@ This is your link for group %@!
Now admins can:
- delete members' messages.
- disable members ("observer" role)
- Теперь админы могут:
-- удалять сообщения членов.
-- приостанавливать членов (роль наблюдатель)
No comment provided by engineer.
@@ -5481,8 +5382,7 @@ This is your link for group %@!
Ok
Ок
- alert action
-alert button
+ alert button
Old database
@@ -5640,7 +5540,6 @@ Requires compatible VPN.
Open link?
- Открыть ссылку?
alert title
@@ -5757,7 +5656,6 @@ Requires compatible VPN.
Past member %@
- Бывший член %@
past/unknown group member
@@ -5894,11 +5792,6 @@ Error: %@
Попробуйте выключить и снова включить уведомления.
token info
-
- Please wait for group moderators to review your request to join the group.
- Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление.
- snd group event chat item
-
Please wait for token activation to complete.
Пожалуйста, дождитесь завершения активации токена.
@@ -6071,7 +5964,6 @@ Error: %@
Prohibit sending direct messages to members.
- Запретить посылать прямые сообщения членам группы.
No comment provided by engineer.
@@ -6352,11 +6244,6 @@ swipe action
Отклонить запрос
No comment provided by engineer.
-
- Reject member?
- Отклонить участника?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Relay сервер используется только при необходимости. Другая сторона может видеть Ваш IP адрес.
@@ -6384,12 +6271,10 @@ swipe action
Remove member
- Удалить члена группы
No comment provided by engineer.
Remove member?
- Удалить члена группы?
No comment provided by engineer.
@@ -6467,11 +6352,6 @@ swipe action
Причина сообщения?
No comment provided by engineer.
-
- Report sent to moderators
- Жалоба отправлена модераторам
- alert title
-
Report spam: only group moderators will see it.
Пожаловаться на спам: увидят только модераторы группы.
@@ -6587,16 +6467,6 @@ swipe action
Посмотреть условия
No comment provided by engineer.
-
- Review members
- Одобрять членов
- admission stage
-
-
- Review members before admitting ("knocking").
- Одобрять членов для вступления в группу.
- admission stage description
-
Revoke
Отозвать
@@ -6653,11 +6523,6 @@ chat item action
Сохранить (и уведомить контакты)
alert button
-
- Save admission settings?
- Сохранить настройки вступления?
- alert title
-
Save and notify contact
Сохранить и уведомить контакт
@@ -6665,7 +6530,6 @@ chat item action
Save and notify group members
- Сохранить и уведомить членов группы
No comment provided by engineer.
@@ -6950,7 +6814,6 @@ chat item action
Send up to 100 last messages to new members.
- Отправить до 100 последних сообщений новым членам.
No comment provided by engineer.
@@ -7173,11 +7036,6 @@ chat item action
Установите код вместо системной аутентификации.
No comment provided by engineer.
-
- Set member admission
- Приём членов в группу
- No comment provided by engineer.
-
Set message expiration in chats.
Установите срок хранения сообщений в чатах.
@@ -7200,7 +7058,6 @@ chat item action
Set the message shown to new members!
- Установить сообщение для новых членов группы!
No comment provided by engineer.
@@ -7291,7 +7148,6 @@ chat item action
Short link
- Короткая ссылка
No comment provided by engineer.
@@ -7396,7 +7252,6 @@ chat item action
SimpleX channel link
- SimpleX ссылка канала
simplex link type
@@ -7841,22 +7696,18 @@ It can happen because of some bug or when the connection is compromised.
The message will be deleted for all members.
- Сообщение будет удалено для всех членов группы.
No comment provided by engineer.
The message will be marked as moderated for all members.
- Сообщение будет помечено как удаленное для всех членов группы.
No comment provided by engineer.
The messages will be deleted for all members.
- Сообщения будут удалены для всех членов группы.
No comment provided by engineer.
The messages will be marked as moderated for all members.
- Сообщения будут помечены как удаленные для всех членов группы.
No comment provided by engineer.
@@ -7966,7 +7817,6 @@ It can happen because of some bug or when the connection is compromised.
This group has over %lld members, delivery receipts are not sent.
- В этой группе более %lld членов, отчёты о доставке не отправляются.
No comment provided by engineer.
@@ -7986,7 +7836,6 @@ It can happen because of some bug or when the connection is compromised.
This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link.
- Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку.
No comment provided by engineer.
@@ -8178,17 +8027,14 @@ You will be prompted to complete authentication before this feature is enabled.<
Unblock member
- Разблокировать члена группы
No comment provided by engineer.
Unblock member for all?
- Разблокировать члена для всех?
No comment provided by engineer.
Unblock member?
- Разблокировать члена группы?
No comment provided by engineer.
@@ -8290,12 +8136,10 @@ To connect, please ask your contact to create another connection link and check
Unsupported connection link
- Ссылка не поддерживается
No comment provided by engineer.
Up to 100 last messages are sent to new members.
- До 100 последних сообщений отправляются новым членам.
No comment provided by engineer.
@@ -8390,7 +8234,6 @@ To connect, please ask your contact to create another connection link and check
Use TCP port 443 for preset servers only.
- Использовать TCP-порт 443 только для серверов по умолчанию.
No comment provided by engineer.
@@ -8460,7 +8303,6 @@ To connect, please ask your contact to create another connection link and check
Use short links (BETA)
- Короткие ссылки (БЕТА)
No comment provided by engineer.
@@ -8907,7 +8749,6 @@ Repeat join request?
You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it.
- Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились.
No comment provided by engineer.
@@ -8940,11 +8781,6 @@ Repeat join request?
Вы можете увидеть ссылку-приглашение снова открыв соединение.
alert message
-
- You can view your reports in Chat with admins.
- Вы можете найти Ваши жалобы в Чате с админами.
- alert message
-
You can't send messages!
Вы не можете отправлять сообщения!
@@ -8989,7 +8825,6 @@ Repeat connection request?
You joined this group. Connecting to inviting group member.
- Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы.
No comment provided by engineer.
@@ -9059,7 +8894,6 @@ Repeat connection request?
You will connect to all group members.
- Вы соединитесь со всеми членами группы.
No comment provided by engineer.
@@ -9247,11 +9081,6 @@ Repeat connection request?
наверху, затем выберите:
No comment provided by engineer.
-
- accepted %@
- принят %@
- rcv group event chat item
-
accepted call
принятый звонок
@@ -9262,11 +9091,6 @@ Repeat connection request?
принятое приглашение
chat list item title
-
- accepted you
- Вы приняты
- rcv group event chat item
-
admin
админ
@@ -9287,14 +9111,8 @@ Repeat connection request?
шифрование согласовывается…
chat item text
-
- all
- все
- member criteria value
-
all members
- все члены
feature role
@@ -9378,11 +9196,6 @@ marked deleted chat item preview text
входящий звонок…
call status
-
- can't send messages
- нельзя отправлять
- No comment provided by engineer.
-
cancelled %@
отменил(a) %@
@@ -9488,16 +9301,6 @@ marked deleted chat item preview text
контакт %1$@ изменён на %2$@
profile update event chat item
-
- contact deleted
- контакт удален
- No comment provided by engineer.
-
-
- contact disabled
- контакт выключен
- No comment provided by engineer.
-
contact has e2e encryption
у контакта есть e2e шифрование
@@ -9508,11 +9311,6 @@ marked deleted chat item preview text
у контакта нет e2e шифрования
No comment provided by engineer.
-
- contact not ready
- контакт не готов
- No comment provided by engineer.
-
creator
создатель
@@ -9684,11 +9482,6 @@ pref value
группа удалена
No comment provided by engineer.
-
- group is deleted
- группа удалена
- No comment provided by engineer.
-
group profile updated
профиль группы обновлен
@@ -9801,12 +9594,10 @@ pref value
member
- член группы
member role
member %1$@ changed to %2$@
- член %1$@ изменился на %2$@
profile update event chat item
@@ -9814,11 +9605,6 @@ pref value
соединен(а)
rcv group event chat item
-
- member has old version
- член имеет старую версию
- No comment provided by engineer.
-
message
написать
@@ -9884,11 +9670,6 @@ pref value
нет текста
copied message info in history
-
- not synchronized
- не синхронизирован
- No comment provided by engineer.
-
observer
читатель
@@ -9899,7 +9680,6 @@ pref value
нет
enabled status
group pref value
-member criteria value
time to disappear
@@ -9952,11 +9732,6 @@ time to disappear
ожидает утверждения
No comment provided by engineer.
-
- pending review
- ожидает одобрения
- No comment provided by engineer.
-
quantum resistant e2e encryption
квантово-устойчивое e2e шифрование
@@ -9997,11 +9772,6 @@ time to disappear
удалён адрес контакта
profile update event chat item
-
- removed from group
- удален из группы
- No comment provided by engineer.
-
removed profile picture
удалена картинка профиля
@@ -10012,26 +9782,11 @@ time to disappear
удалил(а) Вас из группы
rcv group event chat item
-
- request to join rejected
- запрос на вступление отклонён
- No comment provided by engineer.
-
requested to connect
запрошено соединение
chat list item title
-
- review
- рассмотрение
- No comment provided by engineer.
-
-
- reviewed by admins
- одобрен админами
- No comment provided by engineer.
-
saved
сохранено
@@ -10221,11 +9976,6 @@ last received msg: %2$@
Вы
No comment provided by engineer.
-
- you accepted this member
- Вы приняли этого члена
- snd group event chat item
-
you are invited to group
Вы приглашены в группу
@@ -10369,7 +10119,6 @@ last received msg: %2$@
From %d chat(s)
- Из %d чатов
notification body
diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
index 528219b13a..671dd87d7d 100644
--- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
+++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff
@@ -520,14 +520,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
No comment provided by engineer.
@@ -547,10 +539,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
No comment provided by engineer.
@@ -1441,23 +1429,11 @@ set passcode view
Chat will be deleted for you - this cannot be undone!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
แชท
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
No comment provided by engineer.
@@ -2186,10 +2162,6 @@ swipe action
ลบโปรไฟล์แชทไหม?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
No comment provided by engineer.
@@ -2579,7 +2551,7 @@ swipe action
Don't show again
ไม่ต้องแสดงอีก
- alert action
+ No comment provided by engineer.
Done
@@ -2871,10 +2843,6 @@ chat item action
เกิดข้อผิดพลาดในการรับคำขอติดต่อ
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
เกิดข้อผิดพลาดในการเพิ่มสมาชิก
@@ -2960,10 +2928,6 @@ chat item action
เกิดข้อผิดพลาดในการลบฐานข้อมูลแชท
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
เกิดข้อผิดพลาดในการลบแชท!
@@ -3064,7 +3028,7 @@ chat item action
Error removing member
เกิดข้อผิดพลาดในการลบสมาชิก
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4329,10 +4293,6 @@ This is your link for group %@!
สมาชิก
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
item status text
@@ -4364,10 +4324,6 @@ This is your link for group %@!
สมาชิกจะถูกลบออกจากกลุ่ม - ไม่สามารถยกเลิกได้!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
สมาชิกกลุ่มสามารถเพิ่มการแสดงปฏิกิริยาต่อข้อความได้
@@ -4741,10 +4697,6 @@ This is your link for group %@!
บทบาทของสมาชิกใหม่
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
ข้อความใหม่
@@ -4781,10 +4733,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
ไม่ได้เลือกผู้ติดต่อ
@@ -4954,8 +4902,7 @@ This is your link for group %@!
Ok
ตกลง
- alert action
-alert button
+ alert button
Old database
@@ -5327,10 +5274,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -5740,10 +5683,6 @@ swipe action
ปฏิเสธคำขอติดต่อ
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
ใช้เซิร์ฟเวอร์รีเลย์ในกรณีที่จำเป็นเท่านั้น บุคคลอื่นสามารถสังเกตที่อยู่ IP ของคุณได้
@@ -5842,10 +5781,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -5949,14 +5884,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
ถอน
@@ -6009,10 +5936,6 @@ chat item action
บันทึก (และแจ้งผู้ติดต่อ)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
บันทึกและแจ้งผู้ติดต่อ
@@ -6481,10 +6404,6 @@ chat item action
ตั้งแทนการรับรองความถูกต้องของระบบ
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8067,10 +7986,6 @@ Repeat join request?
You can view invitation link again in connection details.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
คุณไม่สามารถส่งข้อความได้!
@@ -8355,10 +8270,6 @@ Repeat connection request?
ด้านบน จากนั้นเลือก:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
รับสายแล้ว
@@ -8368,10 +8279,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
ผู้ดูแลระบบ
@@ -8391,10 +8298,6 @@ Repeat connection request?
เห็นด้วยกับการ encryption…
chat item text
-
- all
- member criteria value
-
all members
feature role
@@ -8472,10 +8375,6 @@ marked deleted chat item preview text
กำลังโทร…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
ยกเลิก %@
@@ -8579,14 +8478,6 @@ marked deleted chat item preview text
contact %1$@ changed to %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
ผู้ติดต่อมีการ encrypt จากต้นจนจบ
@@ -8597,10 +8488,6 @@ marked deleted chat item preview text
ผู้ติดต่อไม่มีการ encrypt จากต้นจนจบ
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
ผู้สร้าง
@@ -8766,10 +8653,6 @@ pref value
ลบกลุ่มแล้ว
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
อัปเดตโปรไฟล์กลุ่มแล้ว
@@ -8892,10 +8775,6 @@ pref value
เชื่อมต่อสำเร็จ
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
No comment provided by engineer.
@@ -8959,10 +8838,6 @@ pref value
ไม่มีข้อความ
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
ผู้สังเกตการณ์
@@ -8973,7 +8848,6 @@ pref value
ปิด
enabled status
group pref value
-member criteria value
time to disappear
@@ -9021,10 +8895,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
chat item text
@@ -9062,10 +8932,6 @@ time to disappear
removed contact address
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profile update event chat item
@@ -9075,22 +8941,10 @@ time to disappear
ลบคุณออกแล้ว
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
No comment provided by engineer.
@@ -9261,10 +9115,6 @@ last received msg: %2$@
you
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
คุณได้รับเชิญให้เข้าร่วมกลุ่ม
diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
index d17a272016..bbee40c2b9 100644
--- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
+++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
@@ -563,14 +563,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Koşulları kabul et
@@ -592,10 +584,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Kabul edilmiş koşullar
@@ -1569,23 +1557,11 @@ set passcode view
Sohbet senden silinecek - bu geri alınamaz!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Sohbetler
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Her 20 dakikada mesajları kontrol et.
@@ -2388,10 +2364,6 @@ swipe action
Sohbet profili silinsin mi?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
Sohbet silinsin mi?
@@ -2808,7 +2780,7 @@ swipe action
Don't show again
Yeniden gösterme
- alert action
+ No comment provided by engineer.
Done
@@ -3123,10 +3095,6 @@ chat item action
Bağlantı isteği kabul edilirken hata oluştu
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Üye(ler) eklenirken hata oluştu
@@ -3219,10 +3187,6 @@ chat item action
Sohbet veritabanı silinirken sorun oluştu
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Sohbet silinirken hata oluştu!
@@ -3330,7 +3294,7 @@ chat item action
Error removing member
Kişiyi silerken sorun oluştu
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4689,10 +4653,6 @@ Bu senin grup için bağlantın %@!
Kişi
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Üye inaktif
@@ -4726,10 +4686,6 @@ Bu senin grup için bağlantın %@!
Üye gruptan çıkarılacaktır - bu geri alınamaz!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Grup üyeleri mesaj tepkileri ekleyebilir.
@@ -5140,10 +5096,6 @@ Bu senin grup için bağlantın %@!
Yeni üye rolü
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Yeni mesaj
@@ -5180,10 +5132,6 @@ Bu senin grup için bağlantın %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Hiçbir kişi seçilmedi
@@ -5363,8 +5311,7 @@ Bu senin grup için bağlantın %@!
Ok
Tamam
- alert action
-alert button
+ alert button
Old database
@@ -5764,10 +5711,6 @@ Hata: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6210,10 +6153,6 @@ swipe action
Bağlanma isteğini reddet
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir.
@@ -6319,10 +6258,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6432,14 +6367,6 @@ swipe action
Review conditions
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
İptal et
@@ -6496,10 +6423,6 @@ chat item action
Kaydet (ve kişilere bildir)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Kaydet ve kişilere bildir
@@ -7008,10 +6931,6 @@ chat item action
Sistem kimlik doğrulaması yerine ayarla.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8727,10 +8646,6 @@ Katılma isteği tekrarlansın mı?
Bağlantı detaylarından davet bağlantısını yeniden görüntüleyebilirsin.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Mesajlar gönderemezsiniz!
@@ -9030,10 +8945,6 @@ Bağlantı isteği tekrarlansın mı?
yukarı çıkın, ardından seçin:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
kabul edilen arama
@@ -9043,10 +8954,6 @@ Bağlantı isteği tekrarlansın mı?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
yönetici
@@ -9067,10 +8974,6 @@ Bağlantı isteği tekrarlansın mı?
şifreleme kabul ediliyor…
chat item text
-
- all
- member criteria value
-
all members
bütün üyeler
@@ -9156,10 +9059,6 @@ marked deleted chat item preview text
aranıyor…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
%@ iptal edildi
@@ -9265,14 +9164,6 @@ marked deleted chat item preview text
%1$@ kişisi %2$@ olarak değişti
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
kişi uçtan uca şifrelemeye sahiptir
@@ -9283,10 +9174,6 @@ marked deleted chat item preview text
kişi uçtan uca şifrelemeye sahip değildir
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
oluşturan
@@ -9458,10 +9345,6 @@ pref value
grup silindi
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
grup profili güncellendi
@@ -9587,10 +9470,6 @@ pref value
bağlanıldı
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
mesaj
@@ -9655,10 +9534,6 @@ pref value
metin yok
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
gözlemci
@@ -9669,7 +9544,6 @@ pref value
kapalı
enabled status
group pref value
-member criteria value
time to disappear
@@ -9720,10 +9594,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
kuantuma dayanıklı e2e şifreleme
@@ -9763,10 +9633,6 @@ time to disappear
kişi adresi silindi
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
profil fotoğrafı silindi
@@ -9777,22 +9643,10 @@ time to disappear
sen kaldırıldın
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
kaydedildi
@@ -9982,10 +9836,6 @@ son alınan msj: %2$@
sen
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
gruba davet edildiniz
diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
index 687393cfab..c0375e3b02 100644
--- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
+++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff
@@ -563,14 +563,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
Прийняти умови
@@ -592,10 +584,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
Прийняті умови
@@ -1569,23 +1557,11 @@ set passcode view
Чат буде видалено для вас - цю дію неможливо скасувати!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
Чати
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
Перевіряйте повідомлення кожні 20 хв.
@@ -2388,10 +2364,6 @@ swipe action
Видалити профіль чату?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
Видалити чат?
@@ -2809,7 +2781,7 @@ swipe action
Don't show again
Більше не показувати
- alert action
+ No comment provided by engineer.
Done
@@ -3124,10 +3096,6 @@ chat item action
Помилка при прийнятті запиту на контакт
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
Помилка додавання користувача(ів)
@@ -3220,10 +3188,6 @@ chat item action
Помилка видалення бази даних чату
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
Помилка видалення чату!
@@ -3331,7 +3295,7 @@ chat item action
Error removing member
Помилка видалення учасника
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4690,10 +4654,6 @@ This is your link for group %@!
Учасник
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
Користувач неактивний
@@ -4728,10 +4688,6 @@ This is your link for group %@!
Учасник буде видалений з групи - це неможливо скасувати!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
Учасники групи можуть додавати реакції на повідомлення.
@@ -5146,10 +5102,6 @@ This is your link for group %@!
Нова роль учасника
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
Нове повідомлення
@@ -5187,10 +5139,6 @@ This is your link for group %@!
No chats in list %@
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
Не вибрано жодного контакту
@@ -5377,8 +5325,7 @@ This is your link for group %@!
Ok
Гаразд
- alert action
-alert button
+ alert button
Old database
@@ -5785,10 +5732,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6233,10 +6176,6 @@ swipe action
Відхилити запит на контакт
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
Релейний сервер використовується тільки в разі потреби. Інша сторона може бачити вашу IP-адресу.
@@ -6342,10 +6281,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6456,14 +6391,6 @@ swipe action
Умови перегляду
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
Відкликати
@@ -6520,10 +6447,6 @@ chat item action
Зберегти (і повідомити контактам)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
Зберегти та повідомити контакт
@@ -7036,10 +6959,6 @@ chat item action
Встановіть його замість аутентифікації системи.
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8785,10 +8704,6 @@ Repeat join request?
Ви можете переглянути посилання на запрошення ще раз у деталях підключення.
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
Ви не можете надсилати повідомлення!
@@ -9090,10 +9005,6 @@ Repeat connection request?
вище, а потім обирайте:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
прийнято виклик
@@ -9104,10 +9015,6 @@ Repeat connection request?
прийняте запрошення
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
адмін
@@ -9128,10 +9035,6 @@ Repeat connection request?
узгодження шифрування…
chat item text
-
- all
- member criteria value
-
all members
всі учасники
@@ -9217,10 +9120,6 @@ marked deleted chat item preview text
дзвоніть…
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
скасовано %@
@@ -9326,14 +9225,6 @@ marked deleted chat item preview text
контакт %1$@ змінено на %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
контакт має шифрування e2e
@@ -9344,10 +9235,6 @@ marked deleted chat item preview text
контакт не має шифрування e2e
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
творець
@@ -9519,10 +9406,6 @@ pref value
групу видалено
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
оновлено профіль групи
@@ -9648,10 +9531,6 @@ pref value
з'єднаний
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
повідомлення
@@ -9716,10 +9595,6 @@ pref value
без тексту
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
спостерігач
@@ -9730,7 +9605,6 @@ pref value
вимкнено
enabled status
group pref value
-member criteria value
time to disappear
@@ -9781,10 +9655,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
квантово-стійке шифрування e2e
@@ -9824,10 +9694,6 @@ time to disappear
видалено контактну адресу
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
видалено зображення профілю
@@ -9838,23 +9704,11 @@ time to disappear
прибрали вас
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
запит на підключення
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
збережено
@@ -10044,10 +9898,6 @@ last received msg: %2$@
ти
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
вас запрошують до групи
diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
index 06ce8d4950..d5411f86e3 100644
--- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
+++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
@@ -565,14 +565,6 @@ time interval
accept incoming call via notification
swipe action
-
- Accept as member
- alert action
-
-
- Accept as observer
- alert action
-
Accept conditions
接受条款
@@ -594,10 +586,6 @@ swipe action
accept contact request via notification
swipe action
-
- Accept member
- alert title
-
Accepted conditions
已接受的条款
@@ -1594,23 +1582,11 @@ set passcode view
将为你删除聊天 - 此操作无法撤销!
No comment provided by engineer.
-
- Chat with admins
- chat toolbar
-
-
- Chat with member
- No comment provided by engineer.
-
Chats
聊天
No comment provided by engineer.
-
- Chats with members
- No comment provided by engineer.
-
Check messages every 20 min.
每 20 分钟检查消息。
@@ -2425,10 +2401,6 @@ swipe action
删除聊天资料?
No comment provided by engineer.
-
- Delete chat with member?
- alert title
-
Delete chat?
删除聊天?
@@ -2852,7 +2824,7 @@ swipe action
Don't show again
不再显示
- alert action
+ No comment provided by engineer.
Done
@@ -3170,10 +3142,6 @@ chat item action
接受联系人请求错误
No comment provided by engineer.
-
- Error accepting member
- alert title
-
Error adding member(s)
添加成员错误
@@ -3268,10 +3236,6 @@ chat item action
删除聊天数据库错误
No comment provided by engineer.
-
- Error deleting chat with member
- alert title
-
Error deleting chat!
删除聊天错误!
@@ -3380,7 +3344,7 @@ chat item action
Error removing member
删除成员错误
- alert title
+ No comment provided by engineer.
Error reordering lists
@@ -4764,10 +4728,6 @@ This is your link for group %@!
成员
No comment provided by engineer.
-
- Member admission
- No comment provided by engineer.
-
Member inactive
成员不活跃
@@ -4803,10 +4763,6 @@ This is your link for group %@!
成员将被移出群组——此操作无法撤消!
No comment provided by engineer.
-
- Member will join the group, accept member?
- alert message
-
Members can add message reactions.
群组成员可以添加信息回应。
@@ -5227,10 +5183,6 @@ This is your link for group %@!
新成员角色
No comment provided by engineer.
-
- New member wants to join the group.
- rcv group event chat item
-
New message
新消息
@@ -5271,10 +5223,6 @@ This is your link for group %@!
列表 %@ 中无聊天
No comment provided by engineer.
-
- No chats with members
- No comment provided by engineer.
-
No contacts selected
未选择联系人
@@ -5467,8 +5415,7 @@ This is your link for group %@!
Ok
好的
- alert action
-alert button
+ alert button
Old database
@@ -5876,10 +5823,6 @@ Error: %@
Please try to disable and re-enable notfications.
token info
-
- Please wait for group moderators to review your request to join the group.
- snd group event chat item
-
Please wait for token activation to complete.
token info
@@ -6322,10 +6265,6 @@ swipe action
拒绝联系人请求
No comment provided by engineer.
-
- Reject member?
- alert title
-
Relay server is only used if necessary. Another party can observe your IP address.
中继服务器仅在必要时使用。其他人可能会观察到您的IP地址。
@@ -6430,10 +6369,6 @@ swipe action
Report reason?
No comment provided by engineer.
-
- Report sent to moderators
- alert title
-
Report spam: only group moderators will see it.
report reason
@@ -6544,14 +6479,6 @@ swipe action
审阅条款
No comment provided by engineer.
-
- Review members
- admission stage
-
-
- Review members before admitting ("knocking").
- admission stage description
-
Revoke
吊销
@@ -6607,10 +6534,6 @@ chat item action
保存(并通知联系人)
alert button
-
- Save admission settings?
- alert title
-
Save and notify contact
保存并通知联系人
@@ -7118,10 +7041,6 @@ chat item action
设置它以代替系统身份验证。
No comment provided by engineer.
-
- Set member admission
- No comment provided by engineer.
-
Set message expiration in chats.
No comment provided by engineer.
@@ -8831,10 +8750,6 @@ Repeat join request?
您可以在连接详情中再次查看邀请链接。
alert message
-
- You can view your reports in Chat with admins.
- alert message
-
You can't send messages!
您无法发送消息!
@@ -9130,10 +9045,6 @@ Repeat connection request?
上面,然后选择:
No comment provided by engineer.
-
- accepted %@
- rcv group event chat item
-
accepted call
已接受通话
@@ -9143,10 +9054,6 @@ Repeat connection request?
accepted invitation
chat list item title
-
- accepted you
- rcv group event chat item
-
admin
管理员
@@ -9167,10 +9074,6 @@ Repeat connection request?
同意加密…
chat item text
-
- all
- member criteria value
-
all members
所有成员
@@ -9256,10 +9159,6 @@ marked deleted chat item preview text
呼叫中……
call status
-
- can't send messages
- No comment provided by engineer.
-
cancelled %@
已取消 %@
@@ -9365,14 +9264,6 @@ marked deleted chat item preview text
联系人 %1$@ 已更改为 %2$@
profile update event chat item
-
- contact deleted
- No comment provided by engineer.
-
-
- contact disabled
- No comment provided by engineer.
-
contact has e2e encryption
联系人具有端到端加密
@@ -9383,10 +9274,6 @@ marked deleted chat item preview text
联系人没有端到端加密
No comment provided by engineer.
-
- contact not ready
- No comment provided by engineer.
-
creator
创建者
@@ -9558,10 +9445,6 @@ pref value
群组已删除
No comment provided by engineer.
-
- group is deleted
- No comment provided by engineer.
-
group profile updated
群组资料已更新
@@ -9687,10 +9570,6 @@ pref value
已连接
rcv group event chat item
-
- member has old version
- No comment provided by engineer.
-
message
消息
@@ -9755,10 +9634,6 @@ pref value
无文本
copied message info in history
-
- not synchronized
- No comment provided by engineer.
-
observer
观察者
@@ -9769,7 +9644,6 @@ pref value
关闭
enabled status
group pref value
-member criteria value
time to disappear
@@ -9820,10 +9694,6 @@ time to disappear
pending approval
No comment provided by engineer.
-
- pending review
- No comment provided by engineer.
-
quantum resistant e2e encryption
抗量子端到端加密
@@ -9863,10 +9733,6 @@ time to disappear
删除了联系地址
profile update event chat item
-
- removed from group
- No comment provided by engineer.
-
removed profile picture
删除了资料图片
@@ -9877,22 +9743,10 @@ time to disappear
已将您移除
rcv group event chat item
-
- request to join rejected
- No comment provided by engineer.
-
requested to connect
chat list item title
-
- review
- No comment provided by engineer.
-
-
- reviewed by admins
- No comment provided by engineer.
-
saved
已保存
@@ -10082,10 +9936,6 @@ last received msg: %2$@
您
No comment provided by engineer.
-
- you accepted this member
- snd group event chat item
-
you are invited to group
您被邀请加入群组
diff --git a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings
index cf082a166d..7205b37e7f 100644
--- a/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings
+++ b/apps/ios/SimpleX NSE/ru.lproj/Localizable.strings
@@ -1,9 +1,6 @@
/* notification body */
"%d new events" = "%d новых сообщений";
-/* notification body */
-"From %d chat(s)" = "Из %d чатов";
-
/* notification body */
"From: %@" = "От: %@";
diff --git a/apps/ios/SimpleX SE/ShareAPI.swift b/apps/ios/SimpleX SE/ShareAPI.swift
index 2b3e8068ae..3e901c73eb 100644
--- a/apps/ios/SimpleX SE/ShareAPI.swift
+++ b/apps/ios/SimpleX SE/ShareAPI.swift
@@ -67,7 +67,6 @@ func apiSendMessages(
: SEChatCommand.apiSendMessages(
type: chatInfo.chatType,
id: chatInfo.apiId,
- scope: chatInfo.groupChatScope(),
live: false,
ttl: nil,
composedMessages: composedMessages
@@ -124,7 +123,7 @@ enum SEChatCommand: ChatCmdProtocol {
case apiSetEncryptLocalFiles(enable: Bool)
case apiGetChats(userId: Int64)
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
- case apiSendMessages(type: ChatType, id: Int64, scope: GroupChatScope?, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
+ case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
var cmdString: String {
switch self {
@@ -140,27 +139,15 @@ enum SEChatCommand: ChatCmdProtocol {
case let .apiCreateChatItems(noteFolderId, composedMessages):
let msgs = encodeJSON(composedMessages)
return "/_create *\(noteFolderId) json \(msgs)"
- case let .apiSendMessages(type, id, scope, live, ttl, composedMessages):
+ case let .apiSendMessages(type, id, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
- return "/_send \(ref(type, id, scope: scope)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
+ return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
}
}
- func ref(_ type: ChatType, _ id: Int64, scope: GroupChatScope?) -> String {
- "\(type.rawValue)\(id)\(scopeRef(scope: scope))"
- }
-
- func scopeRef(scope: GroupChatScope?) -> String {
- switch (scope) {
- case .none: ""
- case let .memberSupport(groupMemberId_):
- if let groupMemberId = groupMemberId_ {
- "(_support:\(groupMemberId))"
- } else {
- "(_support)"
- }
- }
+ func ref(_ type: ChatType, _ id: Int64) -> String {
+ "\(type.rawValue)\(id)"
}
}
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index 42e36a78c9..9326ae9abe 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -168,19 +168,14 @@
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 648679AA2BC96A74006456E7 /* ChatItemForwardingView.swift */; };
649BCDA0280460FD00C3A862 /* ComposeImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCD9F280460FD00C3A862 /* ComposeImageView.swift */; };
649BCDA22805D6EF00C3A862 /* CIImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 649BCDA12805D6EF00C3A862 /* CIImageView.swift */; };
- 64A779F62DBFB9F200FDEF2F /* MemberAdmissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */; };
- 64A779F82DBFDBF200FDEF2F /* MemberSupportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */; };
- 64A779FC2DC1040000FDEF2F /* SecondaryChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */; };
- 64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */; };
- 64A77A022DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */; };
64AA1C6927EE10C800AC7277 /* ContextItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6827EE10C800AC7277 /* ContextItemView.swift */; };
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */; };
64C06EB52A0A4A7C00792D4D /* ChatItemInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */; };
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
- 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */; };
- 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */; };
+ 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */; };
+ 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */; };
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
@@ -532,19 +527,14 @@
6493D667280ED77F007A76FB /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; };
649BCD9F280460FD00C3A862 /* ComposeImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeImageView.swift; sourceTree = ""; };
649BCDA12805D6EF00C3A862 /* CIImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIImageView.swift; sourceTree = ""; };
- 64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberAdmissionView.swift; sourceTree = ""; };
- 64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberSupportView.swift; sourceTree = ""; };
- 64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryChatView.swift; sourceTree = ""; };
- 64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemberSupportChatToolbar.swift; sourceTree = ""; };
- 64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextPendingMemberActionsView.swift; sourceTree = ""; };
64AA1C6827EE10C800AC7277 /* ContextItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextItemView.swift; sourceTree = ""; };
64AA1C6B27F3537400AC7277 /* DeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedItemView.swift; sourceTree = ""; };
64C06EB42A0A4A7C00792D4D /* ChatItemInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemInfoView.swift; sourceTree = ""; };
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = ""; };
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; };
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
- 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a"; sourceTree = ""; };
- 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a"; sourceTree = ""; };
+ 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a"; sourceTree = ""; };
+ 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a"; sourceTree = ""; };
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = ""; };
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = ""; };
@@ -702,8 +692,8 @@
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
- 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a in Frameworks */,
- 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a in Frameworks */,
+ 64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a in Frameworks */,
+ 64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a in Frameworks */,
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -788,8 +778,8 @@
64C829992D54AEEE006B9E89 /* libffi.a */,
64C829982D54AEED006B9E89 /* libgmp.a */,
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
- 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F-ghc9.6.3.a */,
- 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.2-K4qWCwk6PxbL8qHn42QC4F.a */,
+ 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss-ghc9.6.3.a */,
+ 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.3.6.0-64eNxtIoLF9BaOhAoPagss.a */,
);
path = Libraries;
sourceTree = "";
@@ -1086,7 +1076,6 @@
644EFFDD292BCD9D00525D5B /* ComposeVoiceView.swift */,
D72A9087294BD7A70047C86D /* NativeTextEditor.swift */,
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */,
- 64A77A012DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift */,
);
path = ComposeMessage;
sourceTree = "";
@@ -1128,10 +1117,6 @@
6448BBB528FA9D56000D2AB9 /* GroupLinkView.swift */,
1841516F0CE5992B0EDFB377 /* GroupWelcomeView.swift */,
B70CE9E52D4BE5930080F36D /* GroupMentions.swift */,
- 64A779F52DBFB9F200FDEF2F /* MemberAdmissionView.swift */,
- 64A779F72DBFDBF200FDEF2F /* MemberSupportView.swift */,
- 64A779FB2DC1040000FDEF2F /* SecondaryChatView.swift */,
- 64A779FD2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift */,
);
path = Group;
sourceTree = "";
@@ -1446,10 +1431,8 @@
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */,
6440CA03288AECA70062C672 /* AddGroupMembersView.swift in Sources */,
640743612CD360E600158442 /* ChooseServerOperators.swift in Sources */,
- 64A779FE2DC3AFF200FDEF2F /* MemberSupportChatToolbar.swift in Sources */,
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */,
5C55A923283CEDE600C4E99E /* SoundPlayer.swift in Sources */,
- 64A779F82DBFDBF200FDEF2F /* MemberSupportView.swift in Sources */,
5C93292F29239A170090FFF9 /* ProtocolServersView.swift in Sources */,
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
B79ADAFF2CE4EF930083DFFD /* AddressCreationCard.swift in Sources */,
@@ -1484,7 +1467,6 @@
5CB634AF29E4BB7D0066AD6B /* SetAppPasscodeView.swift in Sources */,
5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */,
5CBE6C12294487F7002D9531 /* VerifyCodeView.swift in Sources */,
- 64A779FC2DC1040000FDEF2F /* SecondaryChatView.swift in Sources */,
3CDBCF4227FAE51000354CDD /* ComposeLinkView.swift in Sources */,
648679AB2BC96A74006456E7 /* ChatItemForwardingView.swift in Sources */,
3CDBCF4827FF621E00354CDD /* CILinkView.swift in Sources */,
@@ -1527,7 +1509,6 @@
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */,
647F090E288EA27B00644C40 /* GroupMemberInfoView.swift in Sources */,
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */,
- 64A77A022DC4AD6100FDEF2F /* ContextPendingMemberActionsView.swift in Sources */,
8C74C3EA2C1B90AF00039E77 /* ThemeManager.swift in Sources */,
5C7505A227B65FDB00BE3227 /* CIMetaView.swift in Sources */,
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */,
@@ -1606,7 +1587,6 @@
1841560FD1CD447955474C1D /* UserProfilesView.swift in Sources */,
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */,
8CC4ED902BD7B8530078AEE8 /* CallAudioDeviceManager.swift in Sources */,
- 64A779F62DBFB9F200FDEF2F /* MemberAdmissionView.swift in Sources */,
18415C6C56DBCEC2CBBD2F11 /* WebRTCClient.swift in Sources */,
8CB15EA02CFDA30600C28209 /* ChatItemsMerger.swift in Sources */,
184152CEF68D2336FC2EBCB0 /* CallViewRenderers.swift in Sources */,
@@ -1991,7 +1971,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2016,7 +1996,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES_THIN;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
@@ -2041,7 +2021,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -2066,7 +2046,7 @@
"@executable_path/Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
OTHER_LDFLAGS = "-Wl,-stack_size,0x1000000";
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
PRODUCT_NAME = SimpleX;
@@ -2083,11 +2063,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2103,11 +2083,11 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
GENERATE_INFOPLIST_FILE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.Tests-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2128,7 +2108,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -2143,7 +2123,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2165,7 +2145,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -2180,7 +2160,7 @@
"@executable_path/../../Frameworks",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -2202,7 +2182,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2228,7 +2208,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2253,7 +2233,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -2279,7 +2259,7 @@
"$(PROJECT_DIR)/Libraries/sim",
);
LLVM_LTO = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
SDKROOT = iphoneos;
@@ -2304,7 +2284,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2319,7 +2299,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -2338,7 +2318,7 @@
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CODE_SIGN_ENTITLEMENTS = "SimpleX SE/SimpleX SE.entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 279;
+ CURRENT_PROJECT_VERSION = 282;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
@@ -2353,7 +2333,7 @@
"@executable_path/../../Frameworks",
);
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
- MARKETING_VERSION = 6.4;
+ MARKETING_VERSION = 6.3.6;
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-SE";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift
index 9a82c912dd..88246465e1 100644
--- a/apps/ios/SimpleXChat/ChatTypes.swift
+++ b/apps/ios/SimpleXChat/ChatTypes.swift
@@ -15,9 +15,6 @@ public let CREATE_MEMBER_CONTACT_VERSION = 2
// version to receive reports (MCReport)
public let REPORTS_VERSION = 12
-// support group knocking (MsgScope)
-public let GROUP_KNOCKING_VERSION = 15
-
public let contentModerationPostLink = URL(string: "https://simplex.chat/blog/20250114-simplex-network-large-groups-privacy-preserving-content-moderation.html#preventing-server-abuse-without-compromising-e2e-encryption")!
public struct User: Identifiable, Decodable, UserLike, NamedChat, Hashable {
@@ -1200,7 +1197,7 @@ public enum GroupFeatureEnabled: String, Codable, Identifiable, Hashable {
public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case direct(contact: Contact)
- case group(groupInfo: GroupInfo, groupChatScope: GroupChatScopeInfo?)
+ case group(groupInfo: GroupInfo)
case local(noteFolder: NoteFolder)
case contactRequest(contactRequest: UserContactRequest)
case contactConnection(contactConnection: PendingContactConnection)
@@ -1214,7 +1211,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.localDisplayName
- case let .group(groupInfo, _): return groupInfo.localDisplayName
+ case let .group(groupInfo): return groupInfo.localDisplayName
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.localDisplayName
case let .contactConnection(contactConnection): return contactConnection.localDisplayName
@@ -1227,7 +1224,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.displayName
- case let .group(groupInfo, _): return groupInfo.displayName
+ case let .group(groupInfo): return groupInfo.displayName
case .local: return ChatInfo.privateNotesChatName
case let .contactRequest(contactRequest): return contactRequest.displayName
case let .contactConnection(contactConnection): return contactConnection.displayName
@@ -1240,7 +1237,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.fullName
- case let .group(groupInfo, _): return groupInfo.fullName
+ case let .group(groupInfo): return groupInfo.fullName
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.fullName
case let .contactConnection(contactConnection): return contactConnection.fullName
@@ -1253,7 +1250,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.image
- case let .group(groupInfo, _): return groupInfo.image
+ case let .group(groupInfo): return groupInfo.image
case .local: return nil
case let .contactRequest(contactRequest): return contactRequest.image
case let .contactConnection(contactConnection): return contactConnection.image
@@ -1266,7 +1263,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.localAlias
- case let .group(groupInfo, _): return groupInfo.localAlias
+ case let .group(groupInfo): return groupInfo.localAlias
case .local: return ""
case let .contactRequest(contactRequest): return contactRequest.localAlias
case let .contactConnection(contactConnection): return contactConnection.localAlias
@@ -1279,7 +1276,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.id
- case let .group(groupInfo, _): return groupInfo.id
+ case let .group(groupInfo): return groupInfo.id
case let .local(noteFolder): return noteFolder.id
case let .contactRequest(contactRequest): return contactRequest.id
case let .contactConnection(contactConnection): return contactConnection.id
@@ -1305,7 +1302,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.apiId
- case let .group(groupInfo, _): return groupInfo.apiId
+ case let .group(groupInfo): return groupInfo.apiId
case let .local(noteFolder): return noteFolder.apiId
case let .contactRequest(contactRequest): return contactRequest.apiId
case let .contactConnection(contactConnection): return contactConnection.apiId
@@ -1318,7 +1315,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
get {
switch self {
case let .direct(contact): return contact.ready
- case let .group(groupInfo, _): return groupInfo.ready
+ case let .group(groupInfo): return groupInfo.ready
case let .local(noteFolder): return noteFolder.ready
case let .contactRequest(contactRequest): return contactRequest.ready
case let .contactConnection(contactConnection): return contactConnection.ready
@@ -1339,57 +1336,34 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var userCantSendReason: (composeLabel: LocalizedStringKey, alertMessage: LocalizedStringKey?)? {
get {
switch self {
- 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 {
- switch(groupChatScope) {
- case .none:
- 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
- case let .some(.memberSupport(groupMember_: .some(supportMember))):
- if supportMember.versionRange.maxVersion < GROUP_KNOCKING_VERSION && !supportMember.memberPending {
- return ("member has old version", nil)
- }
- return nil
- case .some(.memberSupport(groupMember_: .none)):
- 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)
+ 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 { userCantSendReason == nil }
+ public var sendMsgEnabled: Bool {
+ 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
+ }
+ }
+ }
public var incognito: Bool {
get {
switch self {
case let .direct(contact): return contact.contactConnIncognito
- case let .group(groupInfo, _): return groupInfo.membership.memberIncognito
+ case let .group(groupInfo): return groupInfo.membership.memberIncognito
case .local: return false
case .contactRequest: return false
case let .contactConnection(contactConnection): return contactConnection.incognito
@@ -1414,7 +1388,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var groupInfo: GroupInfo? {
switch self {
- case let .group(groupInfo, _): return groupInfo
+ case let .group(groupInfo): return groupInfo
default: return nil
}
}
@@ -1431,7 +1405,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case .voice: return cups.voice.enabled.forUser
case .calls: return cups.calls.enabled.forUser
}
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
let prefs = groupInfo.fullGroupPreferences
switch feature {
case .timedMessages: return prefs.timedMessages.on
@@ -1454,7 +1428,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
case let .direct(contact):
let pref = contact.mergedPreferences.timedMessages
return pref.enabled.forUser ? pref.userPreference.preference.ttl : nil
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
let pref = groupInfo.fullGroupPreferences.timedMessages
return pref.on ? pref.ttl : nil
default:
@@ -1479,7 +1453,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
} else {
return .other
}
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
if !groupInfo.fullGroupPreferences.voice.on(for: groupInfo.membership) {
return .groupOwnerCan
} else {
@@ -1510,14 +1484,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
return .other
}
}
-
- public func groupChatScope() -> GroupChatScope? {
- switch self {
- case let .group(_, groupChatScope): groupChatScope?.toChatScope()
- default: nil
- }
- }
-
+
public func ntfsEnabled(chatItem: ChatItem) -> Bool {
ntfsEnabled(chatItem.meta.userMention)
}
@@ -1533,7 +1500,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatSettings: ChatSettings? {
switch self {
case let .direct(contact): return contact.chatSettings
- case let .group(groupInfo, _): return groupInfo.chatSettings
+ case let .group(groupInfo): return groupInfo.chatSettings
default: return nil
}
}
@@ -1549,7 +1516,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatTags: [Int64]? {
switch self {
case let .direct(contact): return contact.chatTags
- case let .group(groupInfo, _): return groupInfo.chatTags
+ case let .group(groupInfo): return groupInfo.chatTags
default: return nil
}
}
@@ -1557,7 +1524,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
var createdAt: Date {
switch self {
case let .direct(contact): return contact.createdAt
- case let .group(groupInfo, _): return groupInfo.createdAt
+ case let .group(groupInfo): return groupInfo.createdAt
case let .local(noteFolder): return noteFolder.createdAt
case let .contactRequest(contactRequest): return contactRequest.createdAt
case let .contactConnection(contactConnection): return contactConnection.createdAt
@@ -1568,7 +1535,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var updatedAt: Date {
switch self {
case let .direct(contact): return contact.updatedAt
- case let .group(groupInfo, _): return groupInfo.updatedAt
+ case let .group(groupInfo): return groupInfo.updatedAt
case let .local(noteFolder): return noteFolder.updatedAt
case let .contactRequest(contactRequest): return contactRequest.updatedAt
case let .contactConnection(contactConnection): return contactConnection.updatedAt
@@ -1579,7 +1546,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public var chatTs: Date {
switch self {
case let .direct(contact): return contact.chatTs ?? contact.updatedAt
- case let .group(groupInfo, _): return groupInfo.chatTs ?? groupInfo.updatedAt
+ case let .group(groupInfo): return groupInfo.chatTs ?? groupInfo.updatedAt
case let .local(noteFolder): return noteFolder.chatTs
case let .contactRequest(contactRequest): return contactRequest.updatedAt
case let .contactConnection(contactConnection): return contactConnection.updatedAt
@@ -1595,7 +1562,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
} else {
ChatTTL.userDefault(globalTTL)
}
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
return if let ciTTL = groupInfo.chatItemTTL {
ChatTTL.chat(ChatItemTTL(ciTTL))
} else {
@@ -1615,7 +1582,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
public static var sampleData: ChatInfo.SampleData = SampleData(
direct: ChatInfo.direct(contact: Contact.sampleData),
- group: ChatInfo.group(groupInfo: GroupInfo.sampleData, groupChatScope: nil),
+ group: ChatInfo.group(groupInfo: GroupInfo.sampleData),
local: ChatInfo.local(noteFolder: NoteFolder.sampleData),
contactRequest: ChatInfo.contactRequest(contactRequest: UserContactRequest.sampleData),
contactConnection: ChatInfo.contactConnection(contactConnection: PendingContactConnection.getSampleData())
@@ -1645,13 +1612,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
}
public struct ChatStats: Decodable, Hashable {
- public init(
- unreadCount: Int = 0,
- unreadMentions: Int = 0,
- reportsCount: Int = 0,
- minUnreadItemId: Int64 = 0,
- unreadChat: Bool = false
- ) {
+ public init(unreadCount: Int = 0, unreadMentions: Int = 0, reportsCount: Int = 0, minUnreadItemId: Int64 = 0, unreadChat: Bool = false) {
self.unreadCount = unreadCount
self.unreadMentions = unreadMentions
self.reportsCount = reportsCount
@@ -1668,32 +1629,6 @@ public struct ChatStats: Decodable, Hashable {
public var unreadChat: Bool = false
}
-public enum GroupChatScope: Decodable {
- case memberSupport(groupMemberId_: Int64?)
-}
-
-public func sameChatScope(_ scope1: GroupChatScope, _ scope2: GroupChatScope) -> Bool {
- switch (scope1, scope2) {
- case let (.memberSupport(groupMemberId1_), .memberSupport(groupMemberId2_)):
- return groupMemberId1_ == groupMemberId2_
- }
-}
-
-public enum GroupChatScopeInfo: Decodable, Hashable {
- case memberSupport(groupMember_: GroupMember?)
-
- public func toChatScope() -> GroupChatScope {
- switch self {
- case let .memberSupport(groupMember_):
- if let groupMember = groupMember_ {
- return .memberSupport(groupMemberId_: groupMember.groupMemberId)
- } else {
- return .memberSupport(groupMemberId_: nil)
- }
- }
- }
-}
-
public struct Contact: Identifiable, Decodable, NamedChat, Hashable {
public var contactId: Int64
var localDisplayName: ContactName
@@ -1720,6 +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 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 } }
@@ -1898,6 +1843,8 @@ 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 } }
public var image: String? { get { profile.image } }
@@ -1929,6 +1876,8 @@ 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) }
}
@@ -2053,11 +2002,24 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
var updatedAt: Date
var chatTs: Date?
public var uiThemes: ThemeModeOverrides?
- public var membersRequireAttention: Int
public var id: ChatId { get { "#\(groupId)" } }
public var apiId: Int64 { get { groupId } }
public var ready: Bool { get { true } }
+ 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 } }
@@ -2070,17 +2032,13 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
}
public var canDelete: Bool {
- return membership.memberRole == .owner || !membership.memberCurrentOrPending
+ return membership.memberRole == .owner || !membership.memberCurrent
}
public var canAddMembers: Bool {
return membership.memberRole >= .admin && membership.memberActive
}
- public var canModerate: Bool {
- return membership.memberRole >= .moderator && membership.memberActive
- }
-
public static let sampleData = GroupInfo(
groupId: 1,
localDisplayName: "team",
@@ -2090,7 +2048,6 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
chatSettings: ChatSettings.defaults,
createdAt: .now,
updatedAt: .now,
- membersRequireAttention: 0,
chatTags: [],
localAlias: ""
)
@@ -2102,20 +2059,12 @@ public struct GroupRef: Decodable, Hashable {
}
public struct GroupProfile: Codable, NamedChat, Hashable {
- public init(
- displayName: String,
- fullName: String,
- description: String? = nil,
- image: String? = nil,
- groupPreferences: GroupPreferences? = nil,
- memberAdmission: GroupMemberAdmission? = nil
- ) {
+ public init(displayName: String, fullName: String, description: String? = nil, image: String? = nil, groupPreferences: GroupPreferences? = nil) {
self.displayName = displayName
self.fullName = fullName
self.description = description
self.image = image
self.groupPreferences = groupPreferences
- self.memberAdmission = memberAdmission
}
public var displayName: String
@@ -2123,48 +2072,14 @@ public struct GroupProfile: Codable, NamedChat, Hashable {
public var description: String?
public var image: String?
public var groupPreferences: GroupPreferences?
- public var memberAdmission: GroupMemberAdmission?
public var localAlias: String { "" }
- public var memberAdmission_: GroupMemberAdmission {
- get { self.memberAdmission ?? GroupMemberAdmission() }
- set { memberAdmission = newValue }
- }
-
public static let sampleData = GroupProfile(
displayName: "team",
fullName: "My Team"
)
}
-public struct GroupMemberAdmission: Codable, Hashable {
- public var review: MemberCriteria?
-
- public init(
- review: MemberCriteria? = nil
- ) {
- self.review = review
- }
-
- public static let sampleData = GroupMemberAdmission(
- review: .all
- )
-}
-
-public enum MemberCriteria: String, Codable, Identifiable, Hashable {
- case all
-
- public static var values: [MemberCriteria] { [.all] }
-
- public var id: Self { self }
-
- public var text: String {
- switch self {
- case .all: return NSLocalizedString("all", comment: "member criteria value")
- }
- }
-}
-
public struct BusinessChatInfo: Decodable, Hashable {
public var chatType: BusinessChatType
public var businessId: String
@@ -2191,7 +2106,6 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
public var memberContactId: Int64?
public var memberContactProfileId: Int64
public var activeConn: Connection?
- public var supportChat: GroupSupportChat?
public var memberChatVRange: VersionRange
public var id: String { "#\(groupId) @\(groupMemberId)" }
@@ -2263,7 +2177,6 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
case .memUnknown: return false
case .memInvited: return false
case .memPendingApproval: return true
- case .memPendingReview: return true
case .memIntroduced: return false
case .memIntroInvited: return false
case .memAccepted: return false
@@ -2283,7 +2196,6 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
case .memUnknown: return false
case .memInvited: return false
case .memPendingApproval: return false
- case .memPendingReview: return false
case .memIntroduced: return true
case .memIntroInvited: return true
case .memAccepted: return true
@@ -2294,18 +2206,6 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
}
}
- public var memberPending: Bool {
- switch memberStatus {
- case .memPendingApproval: return true
- case .memPendingReview: return true
- default: return false
- }
- }
-
- public var memberCurrentOrPending: Bool {
- memberCurrent || memberPending
- }
-
public func canBeRemoved(groupInfo: GroupInfo) -> Bool {
let userRole = groupInfo.membership.memberRole
return memberStatus != .memRemoved && memberStatus != .memLeft
@@ -2359,13 +2259,6 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
)
}
-public struct GroupSupportChat: Codable, Hashable {
- public var chatTs: Date
- public var unread: Int
- public var memberAttention: Int
- public var mentions: Int
-}
-
public struct GroupMemberSettings: Codable, Hashable {
public var showMessages: Bool
}
@@ -2435,7 +2328,6 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case memUnknown = "unknown"
case memInvited = "invited"
case memPendingApproval = "pending_approval"
- case memPendingReview = "pending_review"
case memIntroduced = "introduced"
case memIntroInvited = "intro-inv"
case memAccepted = "accepted"
@@ -2453,7 +2345,6 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case .memUnknown: return "unknown status"
case .memInvited: return "invited"
case .memPendingApproval: return "pending approval"
- case .memPendingReview: return "pending review"
case .memIntroduced: return "connecting (introduced)"
case .memIntroInvited: return "connecting (introduction invitation)"
case .memAccepted: return "connecting (accepted)"
@@ -2473,7 +2364,6 @@ public enum GroupMemberStatus: String, Decodable, Hashable {
case .memUnknown: return "unknown"
case .memInvited: return "invited"
case .memPendingApproval: return "pending"
- case .memPendingReview: return "review"
case .memIntroduced: return "connecting"
case .memIntroInvited: return "connecting"
case .memAccepted: return "connecting"
@@ -2496,6 +2386,8 @@ 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 { "" } }
public var image: String? { get { nil } }
@@ -2755,15 +2647,12 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
case .userDeleted: nil
case .groupDeleted: nil
case .memberCreatedContact: nil
- case .newMemberPendingReview: nil
default: .rcvGroupEvent
}
case let .sndGroupEvent(event):
switch event {
case .userRole: nil
case .userLeft: nil
- case .memberAccepted: nil
- case .userPendingReview: nil
default: .sndGroupEvent
}
default:
@@ -2796,8 +2685,6 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
switch rcvGroupEvent {
case .groupUpdated: return false
case .memberConnected: return false
- case .memberAccepted: return false
- case .userAccepted: return false
case .memberRole: return false
case .memberBlocked: return false
case .userRole: return true
@@ -2809,7 +2696,6 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
case .invitedViaGroupLink: return false
case .memberCreatedContact: return false
case .memberProfileUpdated: return false
- case .newMemberPendingReview: return true
}
case .sndGroupEvent: return false
case .rcvConnEvent: return false
@@ -2878,12 +2764,12 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
public func memberToModerate(_ chatInfo: ChatInfo) -> (GroupInfo, GroupMember?)? {
switch (chatInfo, chatDir) {
- case let (.group(groupInfo, _), .groupRcv(groupMember)):
+ case let (.group(groupInfo), .groupRcv(groupMember)):
let m = groupInfo.membership
return m.memberRole >= .admin && m.memberRole >= groupMember.memberRole && meta.itemDeleted == nil
? (groupInfo, groupMember)
: nil
- case let (.group(groupInfo, _), .groupSnd):
+ case let (.group(groupInfo), .groupSnd):
let m = groupInfo.membership
return m.memberRole >= .admin ? (groupInfo, nil) : nil
default: return nil
@@ -3265,21 +3151,6 @@ public enum CIStatus: Decodable, Hashable {
}
}
- // as in corresponds to SENT response from agent, opposed to `sent` which means snd status
- public var isSent: Bool {
- switch self {
- case .sndNew: false
- case .sndSent: true
- case .sndRcvd: false
- case .sndErrorAuth: true
- case .sndError: true
- case .sndWarning: true
- case .rcvNew: false
- case .rcvRead: false
- case .invalid: false
- }
- }
-
public func statusIcon(_ metaColor: Color, _ paleMetaColor: Color, _ primaryColor: Color = .accentColor) -> (Image, Color)? {
switch self {
case .sndNew: nil
@@ -3333,17 +3204,6 @@ public enum CIStatus: Decodable, Hashable {
}
}
-public func shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus) -> Bool {
- switch (oldStatus, newStatus) {
- case (.sndRcvd, let new) where !new.isSndRcvd:
- return true
- case (let old, .sndNew) where old.isSent:
- return true
- default:
- return false
- }
-}
-
public enum SndError: Decodable, Hashable {
case auth
case quota
@@ -4243,16 +4103,6 @@ extension MsgContent: Encodable {
}
}
-public enum MsgContentTag: String {
- case text
- case link
- case image
- case video
- case voice
- case file
- case report
-}
-
public struct FormattedText: Decodable, Hashable {
public var text: String
public var format: Format?
@@ -4590,8 +4440,6 @@ public enum RcvDirectEvent: Decodable, Hashable {
public enum RcvGroupEvent: Decodable, Hashable {
case memberAdded(groupMemberId: Int64, profile: Profile)
case memberConnected
- case memberAccepted(groupMemberId: Int64, profile: Profile)
- case userAccepted
case memberLeft
case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole)
case memberBlocked(groupMemberId: Int64, profile: Profile, blocked: Bool)
@@ -4603,16 +4451,12 @@ public enum RcvGroupEvent: Decodable, Hashable {
case invitedViaGroupLink
case memberCreatedContact
case memberProfileUpdated(fromProfile: Profile, toProfile: Profile)
- case newMemberPendingReview
var text: String {
switch self {
case let .memberAdded(_, profile):
return String.localizedStringWithFormat(NSLocalizedString("invited %@", comment: "rcv group event chat item"), profile.profileViewName)
case .memberConnected: return NSLocalizedString("member connected", comment: "rcv group event chat item")
- case let .memberAccepted(_, profile):
- return String.localizedStringWithFormat(NSLocalizedString("accepted %@", comment: "rcv group event chat item"), profile.profileViewName)
- case .userAccepted: return NSLocalizedString("accepted you", comment: "rcv group event chat item")
case .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item")
case let .memberRole(_, profile, role):
return String.localizedStringWithFormat(NSLocalizedString("changed role of %@ to %@", comment: "rcv group event chat item"), profile.profileViewName, role.text)
@@ -4632,7 +4476,6 @@ public enum RcvGroupEvent: Decodable, Hashable {
case .invitedViaGroupLink: return NSLocalizedString("invited via your group link", comment: "rcv group event chat item")
case .memberCreatedContact: return NSLocalizedString("connected directly", comment: "rcv group event chat item")
case let .memberProfileUpdated(fromProfile, toProfile): return profileUpdatedText(fromProfile, toProfile)
- case .newMemberPendingReview: return NSLocalizedString("New member wants to join the group.", comment: "rcv group event chat item")
}
}
@@ -4657,8 +4500,6 @@ public enum SndGroupEvent: Decodable, Hashable {
case memberDeleted(groupMemberId: Int64, profile: Profile)
case userLeft
case groupUpdated(groupProfile: GroupProfile)
- case memberAccepted(groupMemberId: Int64, profile: Profile)
- case userPendingReview
var text: String {
switch self {
@@ -4676,9 +4517,6 @@ public enum SndGroupEvent: Decodable, Hashable {
return String.localizedStringWithFormat(NSLocalizedString("you removed %@", comment: "snd group event chat item"), profile.profileViewName)
case .userLeft: return NSLocalizedString("you left", comment: "snd group event chat item")
case .groupUpdated: return NSLocalizedString("group profile updated", comment: "snd group event chat item")
- case .memberAccepted: return NSLocalizedString("you accepted this member", comment: "snd group event chat item")
- case .userPendingReview:
- return NSLocalizedString("Please wait for group moderators to review your request to join the group.", comment: "snd group event chat item")
}
}
}
diff --git a/apps/ios/SimpleXChat/ChatUtils.swift b/apps/ios/SimpleXChat/ChatUtils.swift
index 6f80629932..6cbc76ec98 100644
--- a/apps/ios/SimpleXChat/ChatUtils.swift
+++ b/apps/ios/SimpleXChat/ChatUtils.swift
@@ -16,7 +16,7 @@ public protocol ChatLike {
extension ChatLike {
public func groupFeatureEnabled(_ feature: GroupFeature) -> Bool {
- if case let .group(groupInfo, _) = self.chatInfo {
+ if case let .group(groupInfo) = self.chatInfo {
let p = groupInfo.fullGroupPreferences
return switch feature {
case .timedMessages: p.timedMessages.on
@@ -82,9 +82,9 @@ public func foundChat(_ chat: ChatLike, _ searchStr: String) -> Bool {
private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
switch cInfo {
- case let .direct(contact): cInfo.sendMsgEnabled && !contact.nextSendGrpInv
- case .group: cInfo.sendMsgEnabled
- case .local: cInfo.sendMsgEnabled
+ case let .direct(contact): contact.sendMsgEnabled && !contact.nextSendGrpInv
+ case let .group(groupInfo): groupInfo.sendMsgEnabled
+ case let .local(noteFolder): noteFolder.sendMsgEnabled
case .contactRequest: false
case .contactConnection: false
case .invalidJSON: false
@@ -94,7 +94,7 @@ private func canForwardToChat(_ cInfo: ChatInfo) -> Bool {
public func chatIconName(_ cInfo: ChatInfo) -> String {
switch cInfo {
case .direct: "person.crop.circle.fill"
- case let .group(groupInfo, _):
+ case let .group(groupInfo):
switch groupInfo.businessChat?.chatType {
case .none: "person.2.circle.fill"
case .business: "briefcase.circle.fill"
diff --git a/apps/ios/SimpleXChat/Notifications.swift b/apps/ios/SimpleXChat/Notifications.swift
index 70db4476d5..5579449caa 100644
--- a/apps/ios/SimpleXChat/Notifications.swift
+++ b/apps/ios/SimpleXChat/Notifications.swift
@@ -62,7 +62,7 @@ public func createContactConnectedNtf(_ user: any UserLike, _ contact: Contact,
public func createMessageReceivedNtf(_ user: any UserLike, _ cInfo: ChatInfo, _ cItem: ChatItem, _ badgeCount: Int) -> UNMutableNotificationContent {
let previewMode = ntfPreviewModeGroupDefault.get()
var title: String
- if case let .group(groupInfo, _) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
+ if case let .group(groupInfo) = cInfo, case let .groupRcv(groupMember) = cItem.chatDir {
title = groupMsgNtfTitle(groupInfo, groupMember, hideContent: previewMode == .hidden)
} else {
title = previewMode == .hidden ? contactHidden : "\(cInfo.chatViewName):"
diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings
index 1f4ff88f78..e4bc8f2150 100644
--- a/apps/ios/bg.lproj/Localizable.strings
+++ b/apps/ios/bg.lproj/Localizable.strings
@@ -1485,7 +1485,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Не активирай";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Не показвай отново";
/* No comment provided by engineer. */
@@ -1777,7 +1777,7 @@ chat item action */
/* alert title */
"Error receiving file" = "Грешка при получаване на файл";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Грешка при отстраняване на член";
/* No comment provided by engineer. */
@@ -2747,7 +2747,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "изключено";
@@ -2760,8 +2759,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "предлага %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ок";
/* No comment provided by engineer. */
@@ -3778,6 +3776,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Старата база данни не бе премахната по време на миграцията, тя може да бъде изтрита.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти.";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Втората отметка, която пропуснахме! ✅";
@@ -4445,10 +4446,10 @@ chat item action */
"Your profile **%@** will be shared." = "Вашият профил **%@** ще бъде споделен.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Профилът се споделя само с вашите контакти.";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Вашият профил се съхранява на вашето устройство и се споделя само с вашите контакти. SimpleX сървърите не могат да видят вашия профил.";
+"Your profile, contacts and delivered messages are stored on your device." = "Вашият профил, контакти и доставени съобщения се съхраняват на вашето устройство.";
/* No comment provided by engineer. */
"Your random profile" = "Вашият автоматично генериран профил";
diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings
index a3a6ca8215..08a94615a3 100644
--- a/apps/ios/cs.lproj/Localizable.strings
+++ b/apps/ios/cs.lproj/Localizable.strings
@@ -1127,7 +1127,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Nepovolovat";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Znovu neukazuj";
/* No comment provided by engineer. */
@@ -1367,7 +1367,7 @@ swipe action */
/* alert title */
"Error receiving file" = "Chyba při příjmu souboru";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Chyba při odebrání člena";
/* No comment provided by engineer. */
@@ -2145,7 +2145,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "vypnuto";
@@ -2158,8 +2157,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "nabídl %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -2990,6 +2988,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stará databáze nebyla během přenášení odstraněna, lze ji smazat.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty.";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Druhé zaškrtnutí jsme přehlédli! ✅";
@@ -3471,10 +3472,10 @@ chat item action */
"Your profile **%@** will be shared." = "Váš profil **%@** bude sdílen.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Profil je sdílen pouze s vašimi kontakty.";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Váš profil je uložen ve vašem zařízení a sdílen pouze s vašimi kontakty. Servery SimpleX nevidí váš profil.";
+"Your profile, contacts and delivered messages are stored on your device." = "Váš profil, kontakty a doručené zprávy jsou uloženy ve vašem zařízení.";
/* No comment provided by engineer. */
"Your random profile" = "Váš náhodný profil";
diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings
index 4be4ad96ba..8da7835c43 100644
--- a/apps/ios/de.lproj/Localizable.strings
+++ b/apps/ios/de.lproj/Localizable.strings
@@ -345,12 +345,6 @@ accept incoming call via notification
swipe action */
"Accept" = "Annehmen";
-/* alert action */
-"Accept as member" = "Als Mitglied übernehmen";
-
-/* alert action */
-"Accept as observer" = "Als Beobachter übernehmen";
-
/* No comment provided by engineer. */
"Accept conditions" = "Nutzungsbedingungen akzeptieren";
@@ -364,12 +358,6 @@ swipe action */
swipe action */
"Accept incognito" = "Inkognito akzeptieren";
-/* alert title */
-"Accept member" = "Mitglied übernehmen";
-
-/* rcv group event chat item */
-"accepted %@" = "%@ übernommen";
-
/* call status */
"accepted call" = "Anruf angenommen";
@@ -379,9 +367,6 @@ swipe action */
/* chat list item title */
"accepted invitation" = "Einladung angenommen";
-/* rcv group event chat item */
-"accepted you" = "hat Sie übernommen";
-
/* No comment provided by engineer. */
"Acknowledged" = "Bestätigt";
@@ -478,9 +463,6 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "Verschlüsselung zustimmen…";
-/* member criteria value */
-"all" = "alle";
-
/* No comment provided by engineer. */
"All" = "Alle";
@@ -923,9 +905,6 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Mitglied kann nicht benachrichtigt werden";
-/* No comment provided by engineer. */
-"can't send messages" = "Es können keine Nachrichten gesendet werden";
-
/* alert action
alert button */
"Cancel" = "Abbrechen";
@@ -1063,18 +1042,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "Der Chat wird für Sie gelöscht. Dies kann nicht rückgängig gemacht werden!";
-/* chat toolbar */
-"Chat with admins" = "Chat mit Administratoren";
-
-/* No comment provided by engineer. */
-"Chat with member" = "Chat mit einem Mitglied";
-
/* No comment provided by engineer. */
"Chats" = "Chats";
-/* No comment provided by engineer. */
-"Chats with members" = "Chats mit Mitgliedern";
-
/* No comment provided by engineer. */
"Check messages every 20 min." = "Alle 20min Nachrichten überprüfen.";
@@ -1363,15 +1333,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Der Kontakt ist bereits vorhanden";
-/* No comment provided by engineer. */
-"contact deleted" = "Kontakt gelöscht";
-
/* No comment provided by engineer. */
"Contact deleted!" = "Kontakt gelöscht!";
-/* No comment provided by engineer. */
-"contact disabled" = "Kontakt deaktiviert";
-
/* No comment provided by engineer. */
"contact has e2e encryption" = "Kontakt nutzt E2E-Verschlüsselung";
@@ -1390,9 +1354,6 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Kontaktname";
-/* No comment provided by engineer. */
-"contact not ready" = "Kontakt nicht bereit";
-
/* No comment provided by engineer. */
"Contact preferences" = "Kontakt-Präferenzen";
@@ -1641,9 +1602,6 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Chat-Profil löschen?";
-/* alert title */
-"Delete chat with member?" = "Chat mit dem Mitglied löschen?";
-
/* No comment provided by engineer. */
"Delete chat?" = "Chat löschen?";
@@ -1914,7 +1872,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Verpassen Sie keine wichtigen Nachrichten.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Nicht nochmals anzeigen";
/* No comment provided by engineer. */
@@ -2158,9 +2116,6 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Fehler beim Annehmen der Kontaktanfrage";
-/* alert title */
-"Error accepting member" = "Fehler beim Übernehmen des Mitglieds";
-
/* No comment provided by engineer. */
"Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern";
@@ -2218,9 +2173,6 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Fehler beim Löschen der Chat-Datenbank";
-/* alert title */
-"Error deleting chat with member" = "Fehler beim Löschen des Chats mit dem Mitglied";
-
/* No comment provided by engineer. */
"Error deleting chat!" = "Fehler beim Löschen des Chats!";
@@ -2284,7 +2236,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Fehler beim Registrieren für Benachrichtigungen";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Fehler beim Entfernen des Mitglieds";
/* alert title */
@@ -2658,9 +2610,6 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "Die Gruppeneinladung ist nicht mehr gültig, da sie vom Absender entfernt wurde.";
-/* No comment provided by engineer. */
-"group is deleted" = "Gruppe wird gelöscht";
-
/* No comment provided by engineer. */
"Group link" = "Gruppen-Link";
@@ -3189,15 +3138,9 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "Der Mitgliedsname von %1$@ wurde auf %2$@ geändert";
-/* No comment provided by engineer. */
-"Member admission" = "Aufnahme von Mitgliedern";
-
/* rcv group event chat item */
"member connected" = "ist der Gruppe beigetreten";
-/* No comment provided by engineer. */
-"member has old version" = "Das Mitglied hat eine alte App-Version";
-
/* item status text */
"Member inactive" = "Mitglied inaktiv";
@@ -3219,9 +3162,6 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "Das Mitglied wird aus der Gruppe entfernt. Dies kann nicht rückgängig gemacht werden!";
-/* alert message */
-"Member will join the group, accept member?" = "Ein Mitglied wird der Gruppe beitreten. Übernehmen?";
-
/* No comment provided by engineer. */
"Members can add message reactions." = "Gruppenmitglieder können eine Reaktion auf Nachrichten geben.";
@@ -3492,9 +3432,6 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Neue Mitgliedsrolle";
-/* rcv group event chat item */
-"New member wants to join the group." = "Ein neues Mitglied will der Gruppe beitreten.";
-
/* notification */
"new message" = "Neue Nachricht";
@@ -3534,9 +3471,6 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Keine Chats in der Liste %@";
-/* No comment provided by engineer. */
-"No chats with members" = "Keine Chats mit Mitgliedern";
-
/* No comment provided by engineer. */
"No contacts selected" = "Keine Kontakte ausgewählt";
@@ -3621,9 +3555,6 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Nicht kompatibel!";
-/* No comment provided by engineer. */
-"not synchronized" = "Nicht synchronisiert";
-
/* No comment provided by engineer. */
"Notes" = "Anmerkungen";
@@ -3656,7 +3587,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "Aus";
@@ -3669,8 +3599,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "angeboten %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -3766,9 +3695,6 @@ alert button */
/* No comment provided by engineer. */
"Open group" = "Gruppe öffnen";
-/* alert title */
-"Open link?" = "Link öffnen?";
-
/* authentication reason */
"Open migration to another device" = "Migration auf ein anderes Gerät öffnen";
@@ -3871,9 +3797,6 @@ alert button */
/* No comment provided by engineer. */
"pending approval" = "ausstehende Genehmigung";
-/* No comment provided by engineer. */
-"pending review" = "Ausstehende Überprüfung";
-
/* No comment provided by engineer. */
"Periodic" = "Periodisch";
@@ -3943,9 +3866,6 @@ alert button */
/* token info */
"Please try to disable and re-enable notfications." = "Bitte versuchen Sie, die Benachrichtigungen zu deaktivieren und wieder zu aktivieren.";
-/* snd group event chat item */
-"Please wait for group moderators to review your request to join the group." = "Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können.";
-
/* token info */
"Please wait for token activation to complete." = "Bitte warten Sie, bis die Token-Aktivierung abgeschlossen ist.";
@@ -4226,9 +4146,6 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Kontaktanfrage ablehnen";
-/* alert title */
-"Reject member?" = "Mitglied ablehnen?";
-
/* No comment provided by engineer. */
"rejected" = "abgelehnt";
@@ -4268,9 +4185,6 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "Die Kontaktadresse wurde entfernt";
-/* No comment provided by engineer. */
-"removed from group" = "Von der Gruppe entfernt";
-
/* profile update event chat item */
"removed profile picture" = "Das Profil-Bild wurde entfernt";
@@ -4319,9 +4233,6 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Grund der Meldung?";
-/* alert title */
-"Report sent to moderators" = "Meldung wurde an die Moderatoren gesendet";
-
/* report reason */
"Report spam: only group moderators will see it." = "Spam melden: Nur Gruppenmoderatoren werden es sehen.";
@@ -4337,9 +4248,6 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Meldungen";
-/* No comment provided by engineer. */
-"request to join rejected" = "Beitrittsanfrage abgelehnt";
-
/* chat list item title */
"requested to connect" = "Zur Verbindung aufgefordert";
@@ -4394,21 +4302,9 @@ swipe action */
/* chat item action */
"Reveal" = "Aufdecken";
-/* No comment provided by engineer. */
-"review" = "Überprüfung";
-
/* No comment provided by engineer. */
"Review conditions" = "Nutzungsbedingungen einsehen";
-/* admission stage */
-"Review members" = "Überprüfung der Mitglieder";
-
-/* admission stage description */
-"Review members before admitting (\"knocking\")." = "Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\").";
-
-/* No comment provided by engineer. */
-"reviewed by admins" = "Von Administratoren überprüft";
-
/* No comment provided by engineer. */
"Revoke" = "Widerrufen";
@@ -4437,9 +4333,6 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Speichern (und Kontakte benachrichtigen)";
-/* alert title */
-"Save admission settings?" = "Speichern der Aufnahme-Einstellungen?";
-
/* alert button */
"Save and notify contact" = "Speichern und Kontakt benachrichtigen";
@@ -4776,9 +4669,6 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen.";
-/* No comment provided by engineer. */
-"Set member admission" = "Aufnahme von Mitgliedern festlegen";
-
/* No comment provided by engineer. */
"Set message expiration in chats." = "Verfallsdatum von Nachrichten in Chats festlegen.";
@@ -5210,6 +5100,9 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Die alte Datenbank wurde während der Migration nicht entfernt. Sie kann gelöscht werden.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Das Profil wird nur mit Ihren Kontakten geteilt.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Dieselben Nutzungsbedingungen gelten auch für den Betreiber **%@**.";
@@ -5819,9 +5712,6 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Sie haben die Verbindung akzeptiert";
-/* snd group event chat item */
-"you accepted this member" = "Sie haben dieses Mitglied übernommen";
-
/* No comment provided by engineer. */
"You allow" = "Sie erlauben";
@@ -5933,9 +5823,6 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Den Einladungslink können Sie in den Details der Verbindung nochmals sehen.";
-/* alert message */
-"You can view your reports in Chat with admins." = "Sie können Ihre Meldungen im Chat mit den Administratoren sehen.";
-
/* No comment provided by engineer. */
"You can't send messages!" = "Sie können keine Nachrichten versenden!";
@@ -6104,15 +5991,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Ihr Profil **%@** wird geteilt.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ihr Profil wird auf Ihrem Gerät gespeichert und nur mit Ihren Kontakten geteilt. SimpleX-Server können Ihr Profil nicht einsehen.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ihr Profil wurde geändert. Wenn Sie es speichern, wird das aktualisierte Profil an alle Ihre Kontakte gesendet.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Ihr Profil, Ihre Kontakte und zugestellten Nachrichten werden auf Ihrem Gerät gespeichert.";
+
/* No comment provided by engineer. */
"Your random profile" = "Ihr Zufallsprofil";
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index a8782c401f..28ba0f0642 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -1872,7 +1872,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "No pierdas los mensajes importantes.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "No volver a mostrar";
/* No comment provided by engineer. */
@@ -2236,7 +2236,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Error al registrarse para notificaciones";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Error al expulsar miembro";
/* alert title */
@@ -3587,7 +3587,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "desactivado";
@@ -3600,8 +3599,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "ofrecido %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -5102,6 +5100,9 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "La base de datos antigua no se eliminó durante la migración, puede eliminarse.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Las mismas condiciones se aplicarán al operador **%@**.";
@@ -5990,15 +5991,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "El perfil **%@** será compartido.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "El perfil sólo se comparte con tus contactos.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Tu perfil es almacenado en tu dispositivo y solamente se comparte con tus contactos. Los servidores SimpleX no pueden ver tu perfil.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Tu perfil ha sido modificado. Si lo guardas la actualización será enviada a todos tus contactos.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Tu perfil, contactos y mensajes se almacenan en tu dispositivo.";
+
/* No comment provided by engineer. */
"Your random profile" = "Tu perfil aleatorio";
diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings
index 8e489f7a71..4891c7fb26 100644
--- a/apps/ios/fi.lproj/Localizable.strings
+++ b/apps/ios/fi.lproj/Localizable.strings
@@ -1073,7 +1073,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Älä salli";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Älä näytä uudelleen";
/* No comment provided by engineer. */
@@ -1307,7 +1307,7 @@ swipe action */
/* alert title */
"Error receiving file" = "Virhe tiedoston vastaanottamisessa";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Virhe poistettaessa jäsentä";
/* No comment provided by engineer. */
@@ -2079,7 +2079,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "pois";
@@ -2092,8 +2091,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "tarjottu %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -2912,6 +2910,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Vanhaa tietokantaa ei poistettu siirron aikana, se voidaan kuitenkin poistaa.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa.";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Toinen kuittaus, joka uupui! ✅";
@@ -3390,10 +3391,10 @@ chat item action */
"Your profile **%@** will be shared." = "Profiilisi **%@** jaetaan.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Profiili jaetaan vain kontaktiesi kanssa.";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi.";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiilisi tallennetaan laitteeseesi ja jaetaan vain yhteystietojesi kanssa. SimpleX-palvelimet eivät näe profiiliasi.";
+"Your profile, contacts and delivered messages are stored on your device." = "Profiilisi, kontaktisi ja toimitetut viestit tallennetaan laitteellesi.";
/* No comment provided by engineer. */
"Your random profile" = "Satunnainen profiilisi";
diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings
index 9b570a5ae9..4dd75039dc 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -1863,7 +1863,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne manquez pas les messages importants.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Ne plus afficher";
/* No comment provided by engineer. */
@@ -2227,7 +2227,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Erreur lors de l'inscription aux notifications";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Erreur lors de la suppression d'un membre";
/* alert title */
@@ -3479,7 +3479,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "off";
@@ -3492,8 +3491,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "propose %1$@ : %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -4885,6 +4883,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "L'ancienne base de données n'a pas été supprimée lors de la migration, elle peut être supprimée.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Les mêmes conditions s'appliquent à l'opérateur **%@**.";
@@ -5740,15 +5741,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Votre profil **%@** sera partagé.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Le profil n'est partagé qu'avec vos contacts.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Votre profil est stocké sur votre appareil et est seulement partagé avec vos contacts. Les serveurs SimpleX ne peuvent pas voir votre profil.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Votre profil a été modifié. Si vous l'enregistrez, le profil mis à jour sera envoyé à tous vos contacts.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Votre profil, vos contacts et les messages reçus sont stockés sur votre appareil.";
+
/* No comment provided by engineer. */
"Your random profile" = "Votre profil aléatoire";
diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings
index 9a1da01665..5a9b6b4e38 100644
--- a/apps/ios/hu.lproj/Localizable.strings
+++ b/apps/ios/hu.lproj/Localizable.strings
@@ -345,12 +345,6 @@ accept incoming call via notification
swipe action */
"Accept" = "Elfogadás";
-/* alert action */
-"Accept as member" = "Befogadás tagként";
-
-/* alert action */
-"Accept as observer" = "Befogadás megfigyelőként";
-
/* No comment provided by engineer. */
"Accept conditions" = "Feltételek elfogadása";
@@ -364,12 +358,6 @@ swipe action */
swipe action */
"Accept incognito" = "Elfogadás inkognitóban";
-/* alert title */
-"Accept member" = "Tag befogadása";
-
-/* rcv group event chat item */
-"accepted %@" = "befogadta őt: %@";
-
/* call status */
"accepted call" = "fogadott hívás";
@@ -379,9 +367,6 @@ swipe action */
/* chat list item title */
"accepted invitation" = "elfogadott meghívó";
-/* rcv group event chat item */
-"accepted you" = "befogadta Önt";
-
/* No comment provided by engineer. */
"Acknowledged" = "Visszaigazolt";
@@ -478,9 +463,6 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "titkosítás elfogadása…";
-/* member criteria value */
-"all" = "összes";
-
/* No comment provided by engineer. */
"All" = "Összes";
@@ -923,9 +905,6 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Nem lehet üzenetet küldeni a tagnak";
-/* No comment provided by engineer. */
-"can't send messages" = "nem lehet üzeneteket küldeni";
-
/* alert action
alert button */
"Cancel" = "Mégse";
@@ -1063,18 +1042,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "A csevegés törölve lesz az Ön számára – ez a művelet nem vonható vissza!";
-/* chat toolbar */
-"Chat with admins" = "Csevegés az adminisztrátorokkal";
-
-/* No comment provided by engineer. */
-"Chat with member" = "Csevegés a taggal";
-
/* No comment provided by engineer. */
"Chats" = "Csevegések";
-/* No comment provided by engineer. */
-"Chats with members" = "Csevegés a tagokkal";
-
/* No comment provided by engineer. */
"Check messages every 20 min." = "Üzenetek ellenőrzése 20 percenként.";
@@ -1363,15 +1333,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "A partner már létezik";
-/* No comment provided by engineer. */
-"contact deleted" = "partner törölve";
-
/* No comment provided by engineer. */
"Contact deleted!" = "Partner törölve!";
-/* No comment provided by engineer. */
-"contact disabled" = "partner letiltva";
-
/* No comment provided by engineer. */
"contact has e2e encryption" = "a partner e2e titkosítással rendelkezik";
@@ -1390,9 +1354,6 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Csak név";
-/* No comment provided by engineer. */
-"contact not ready" = "a kapcsolat nem áll készen";
-
/* No comment provided by engineer. */
"Contact preferences" = "Partnerbeállítások";
@@ -1544,7 +1505,7 @@ set passcode view */
"Database ID: %d" = "Adatbázis-azonosító: %d";
/* No comment provided by engineer. */
-"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitelelkülönítési beállítások.";
+"Database IDs and Transport isolation option." = "Adatbázis-azonosítók és átvitel-izolációs beállítások.";
/* No comment provided by engineer. */
"Database is encrypted using a random passphrase, you can change it." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva, amelyet szabadon módosíthat.";
@@ -1589,7 +1550,7 @@ set passcode view */
"Decentralized" = "Decentralizált";
/* message decrypt error item */
-"Decryption error" = "Titkosításvisszafejtési hiba";
+"Decryption error" = "Titkosítás visszafejtési hiba";
/* No comment provided by engineer. */
"decryption errors" = "visszafejtési hibák";
@@ -1641,9 +1602,6 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Törli a csevegési profilt?";
-/* alert title */
-"Delete chat with member?" = "Törli a taggal való csevegést?";
-
/* No comment provided by engineer. */
"Delete chat?" = "Törli a csevegést?";
@@ -1819,7 +1777,7 @@ swipe action */
"different migration in the app/database: %@ / %@" = "különböző átköltöztetés az alkalmazásban/adatbázisban: %@ / %@";
/* No comment provided by engineer. */
-"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitelizoláció.";
+"Different names, avatars and transport isolation." = "Különböző nevek, profilképek és átvitel-izoláció.";
/* connection level description */
"direct" = "közvetlen";
@@ -1914,7 +1872,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Ne maradjon le a fontos üzenetekről.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Ne mutasd újra";
/* No comment provided by engineer. */
@@ -2158,9 +2116,6 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Hiba történt a meghívási kérés elfogadásakor";
-/* alert title */
-"Error accepting member" = "Hiba a tag befogadásakor";
-
/* No comment provided by engineer. */
"Error adding member(s)" = "Hiba történt a tag(ok) hozzáadásakor";
@@ -2218,9 +2173,6 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Hiba történt a csevegési adatbázis törlésekor";
-/* alert title */
-"Error deleting chat with member" = "Hiba a taggal való csevegés törlésekor";
-
/* No comment provided by engineer. */
"Error deleting chat!" = "Hiba történt a csevegés törlésekor!";
@@ -2237,7 +2189,7 @@ chat item action */
"Error deleting token" = "Hiba történt a token törlésekor";
/* No comment provided by engineer. */
-"Error deleting user profile" = "Hiba történt a felhasználói profil törlésekor";
+"Error deleting user profile" = "Hiba történt a felhasználó-profil törlésekor";
/* No comment provided by engineer. */
"Error downloading the archive" = "Hiba történt az archívum letöltésekor";
@@ -2284,7 +2236,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Hiba történt az értesítések regisztrálásakor";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Hiba történt a tag eltávolításakor";
/* alert title */
@@ -2658,9 +2610,6 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "A csoportmeghívó már nem érvényes, a küldője eltávolította.";
-/* No comment provided by engineer. */
-"group is deleted" = "csoport törölve";
-
/* No comment provided by engineer. */
"Group link" = "Csoporthivatkozás";
@@ -3007,10 +2956,10 @@ snd error text */
"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegési profilon belül több névtelen kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük.";
/* No comment provided by engineer. */
-"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.";
+"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt.";
/* No comment provided by engineer. */
-"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.\n3. A kapcsolat sérült.";
+"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült.";
/* No comment provided by engineer. */
"It protects your IP address and connections." = "Védi az IP-címét és a kapcsolatait.";
@@ -3189,15 +3138,9 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "%1$@ a következőre módosította a nevét: %2$@";
-/* No comment provided by engineer. */
-"Member admission" = "Tagbefogadás";
-
/* rcv group event chat item */
"member connected" = "kapcsolódott";
-/* No comment provided by engineer. */
-"member has old version" = "a tag régi verziót használ";
-
/* item status text */
"Member inactive" = "Inaktív tag";
@@ -3219,9 +3162,6 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "A tag el lesz távolítva a csoportból – ez a művelet nem vonható vissza!";
-/* alert message */
-"Member will join the group, accept member?" = "A tag csatlakozni akar a csoporthoz, befogadja a tagot?";
-
/* No comment provided by engineer. */
"Members can add message reactions." = "A tagok reakciókat adhatnak hozzá az üzenetekhez.";
@@ -3334,10 +3274,10 @@ snd error text */
"Messages were deleted after you selected them." = "Az üzeneteket törölték miután kijelölte őket.";
/* No comment provided by engineer. */
-"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
+"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.";
/* No comment provided by engineer. */
-"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.";
+"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzenetek, a fájlok és a hívások **végpontok közötti kvantumbiztos titkosítással**, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.";
/* No comment provided by engineer. */
"Migrate device" = "Eszköz átköltöztetése";
@@ -3492,9 +3432,6 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Új tag szerepköre";
-/* rcv group event chat item */
-"New member wants to join the group." = "Új tag szeretne csatlakozni a csoporthoz.";
-
/* notification */
"new message" = "új üzenet";
@@ -3534,9 +3471,6 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Nincsenek csevegések a(z) %@ nevű listában";
-/* No comment provided by engineer. */
-"No chats with members" = "Nincsenek csevegések a tagokkal";
-
/* No comment provided by engineer. */
"No contacts selected" = "Nincs partner kijelölve";
@@ -3616,14 +3550,11 @@ snd error text */
"No unread chats" = "Nincsenek olvasatlan csevegések";
/* No comment provided by engineer. */
-"No user identifiers." = "Nincsenek felhasználói azonosítók.";
+"No user identifiers." = "Nincsenek felhasználó-azonosítók.";
/* No comment provided by engineer. */
"Not compatible!" = "Nem kompatibilis!";
-/* No comment provided by engineer. */
-"not synchronized" = "nincs szinkronizálva";
-
/* No comment provided by engineer. */
"Notes" = "Jegyzetek";
@@ -3656,7 +3587,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "kikapcsolva";
@@ -3669,8 +3599,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "ajánlotta: %1$@, ekkor: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Rendben";
/* No comment provided by engineer. */
@@ -3766,9 +3695,6 @@ alert button */
/* No comment provided by engineer. */
"Open group" = "Csoport megnyitása";
-/* alert title */
-"Open link?" = "Megnyitja a hivatkozást?";
-
/* authentication reason */
"Open migration to another device" = "Átköltöztetés indítása egy másik eszközre";
@@ -3871,9 +3797,6 @@ alert button */
/* No comment provided by engineer. */
"pending approval" = "jóváhagyásra vár";
-/* No comment provided by engineer. */
-"pending review" = "függőben lévő áttekintés";
-
/* No comment provided by engineer. */
"Periodic" = "Időszakos";
@@ -3943,9 +3866,6 @@ alert button */
/* token info */
"Please try to disable and re-enable notfications." = "Próbálja meg letiltani és újra engedélyezni az értesítéseket.";
-/* snd group event chat item */
-"Please wait for group moderators to review your request to join the group." = "Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérelmét.";
-
/* token info */
"Please wait for token activation to complete." = "Várjon, amíg a token aktiválása befejeződik.";
@@ -4226,9 +4146,6 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Meghívási kérés elutasítása";
-/* alert title */
-"Reject member?" = "Elutasítja a tagot?";
-
/* No comment provided by engineer. */
"rejected" = "elutasítva";
@@ -4236,10 +4153,10 @@ swipe action */
"rejected call" = "elutasított hívás";
/* No comment provided by engineer. */
-"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét.";
+"Relay server is only used if necessary. Another party can observe your IP address." = "A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet.";
/* No comment provided by engineer. */
-"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.";
+"Relay server protects your IP address, but it can observe the duration of the call." = "A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát.";
/* No comment provided by engineer. */
"Remove" = "Eltávolítás";
@@ -4268,9 +4185,6 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "eltávolította a kapcsolattartási címet";
-/* No comment provided by engineer. */
-"removed from group" = "eltávolítva a csoportból";
-
/* profile update event chat item */
"removed profile picture" = "eltávolította a profilképét";
@@ -4319,9 +4233,6 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Jelentés indoklása?";
-/* alert title */
-"Report sent to moderators" = "A jelentés el lett küldve a moderátoroknak";
-
/* report reason */
"Report spam: only group moderators will see it." = "Kéretlen tartalom jelentése: csak a csoport moderátorai látják.";
@@ -4337,9 +4248,6 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Jelentések";
-/* No comment provided by engineer. */
-"request to join rejected" = "csatlakozási kérelem elutasítva";
-
/* chat list item title */
"requested to connect" = "Függőben lévő meghívási kérelem";
@@ -4394,21 +4302,9 @@ swipe action */
/* chat item action */
"Reveal" = "Felfedés";
-/* No comment provided by engineer. */
-"review" = "áttekintés";
-
/* No comment provided by engineer. */
"Review conditions" = "Feltételek felülvizsgálata";
-/* admission stage */
-"Review members" = "Tagok áttekintése";
-
-/* admission stage description */
-"Review members before admitting (\"knocking\")." = "Tagok áttekintése a befogadás előtt (kopogtatás).";
-
-/* No comment provided by engineer. */
-"reviewed by admins" = "áttekintve a moderátorok által";
-
/* No comment provided by engineer. */
"Revoke" = "Visszavonás";
@@ -4437,9 +4333,6 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Mentés (és a partnerek értesítése)";
-/* alert title */
-"Save admission settings?" = "Elmenti a befogadási beállításokat?";
-
/* alert button */
"Save and notify contact" = "Mentés és a partner értesítése";
@@ -4776,9 +4669,6 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Beállítás a rendszer-hitelesítés helyett.";
-/* No comment provided by engineer. */
-"Set member admission" = "Tagbefogadás beállítása";
-
/* No comment provided by engineer. */
"Set message expiration in chats." = "Üzenetek eltűnési idejének módosítása a csevegésekben.";
@@ -4940,7 +4830,7 @@ chat item action */
"SimpleX one-time invitation" = "Egyszer használható SimpleX-meghívó";
/* No comment provided by engineer. */
-"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX-protokollokat a Trail of Bits auditálta.";
+"SimpleX protocols reviewed by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva.";
/* No comment provided by engineer. */
"Simplified incognito mode" = "Egyszerűsített inkognitómód";
@@ -5210,6 +5100,9 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem lett eltávolítva az átköltöztetéskor, ezért törölhető.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "A profilja csak a partnereivel van megosztva.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Ugyanezek a feltételek lesznek elfogadva a következő üzemeltető számára is: **%@**.";
@@ -5370,7 +5263,7 @@ report reason */
"Total" = "Összes kapcsolat";
/* No comment provided by engineer. */
-"Transport isolation" = "Átvitelelkülönítés";
+"Transport isolation" = "Átvitel-izoláció";
/* No comment provided by engineer. */
"Transport sessions" = "Munkamenetek átvitele";
@@ -5565,7 +5458,7 @@ report reason */
"Use only local notifications?" = "Csak helyi értesítések használata?";
/* No comment provided by engineer. */
-"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást az ismeretlen kiszolgálókkal, ha az IP-cím nem védett.";
+"Use private routing with unknown servers when IP address is not protected." = "Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett.";
/* No comment provided by engineer. */
"Use private routing with unknown servers." = "Használjon privát útválasztást ismeretlen kiszolgálókkal.";
@@ -5787,10 +5680,10 @@ report reason */
"With reduced battery usage." = "Csökkentett akkumulátor-használattal.";
/* No comment provided by engineer. */
-"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára.";
+"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára.";
/* alert message */
-"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára: %@.";
+"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára: %@.";
/* No comment provided by engineer. */
"Wrong database passphrase" = "Érvénytelen adatbázis-jelmondat";
@@ -5819,9 +5712,6 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Kapcsolat létrehozása";
-/* snd group event chat item */
-"you accepted this member" = "Ön befogadta ezt a tagot";
-
/* No comment provided by engineer. */
"You allow" = "Ön engedélyezi";
@@ -5933,9 +5823,6 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "A meghívási hivatkozást újra megtekintheti a kapcsolat részleteinél.";
-/* alert message */
-"You can view your reports in Chat with admins." = "A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben.";
-
/* No comment provided by engineer. */
"You can't send messages!" = "Nem lehet üzeneteket küldeni!";
@@ -6104,15 +5991,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "A(z) **%@** nevű profilja meg lesz osztva.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "A profilja az eszközén van tárolva és csak a partnereivel van megosztva. A SimpleX-kiszolgálók nem láthatják a profilját.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "A profilja módosult. Ha elmenti, a profilfrissítés el lesz küldve a partnerei számára.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "A profilja, a partnerei és az elküldött üzenetei a saját eszközén vannak tárolva.";
+
/* No comment provided by engineer. */
"Your random profile" = "Véletlenszerű profil";
diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings
index f36b35efc2..b914a06079 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -345,12 +345,6 @@ accept incoming call via notification
swipe action */
"Accept" = "Accetta";
-/* alert action */
-"Accept as member" = "Accetta come membro";
-
-/* alert action */
-"Accept as observer" = "Accetta come osservatore";
-
/* No comment provided by engineer. */
"Accept conditions" = "Accetta le condizioni";
@@ -364,12 +358,6 @@ swipe action */
swipe action */
"Accept incognito" = "Accetta in incognito";
-/* alert title */
-"Accept member" = "Accetta membro";
-
-/* rcv group event chat item */
-"accepted %@" = "%@ accettato";
-
/* call status */
"accepted call" = "chiamata accettata";
@@ -379,9 +367,6 @@ swipe action */
/* chat list item title */
"accepted invitation" = "invito accettato";
-/* rcv group event chat item */
-"accepted you" = "ti ha accettato/a";
-
/* No comment provided by engineer. */
"Acknowledged" = "Riconosciuto";
@@ -478,9 +463,6 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "concordando la crittografia…";
-/* member criteria value */
-"all" = "tutti";
-
/* No comment provided by engineer. */
"All" = "Tutte";
@@ -923,9 +905,6 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Impossibile inviare un messaggio al membro";
-/* No comment provided by engineer. */
-"can't send messages" = "impossibile inviare messaggi";
-
/* alert action
alert button */
"Cancel" = "Annulla";
@@ -1063,18 +1042,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "La chat verrà eliminata solo per te, non è reversibile!";
-/* chat toolbar */
-"Chat with admins" = "Chat con amministratori";
-
-/* No comment provided by engineer. */
-"Chat with member" = "Chatta con il membro";
-
/* No comment provided by engineer. */
"Chats" = "Chat";
-/* No comment provided by engineer. */
-"Chats with members" = "Chat con membri";
-
/* No comment provided by engineer. */
"Check messages every 20 min." = "Controlla i messaggi ogni 20 min.";
@@ -1363,15 +1333,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Il contatto esiste già";
-/* No comment provided by engineer. */
-"contact deleted" = "contatto eliminato";
-
/* No comment provided by engineer. */
"Contact deleted!" = "Contatto eliminato!";
-/* No comment provided by engineer. */
-"contact disabled" = "contatto disattivato";
-
/* No comment provided by engineer. */
"contact has e2e encryption" = "il contatto ha la crittografia e2e";
@@ -1390,9 +1354,6 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Nome del contatto";
-/* No comment provided by engineer. */
-"contact not ready" = "contatto non pronto";
-
/* No comment provided by engineer. */
"Contact preferences" = "Preferenze del contatto";
@@ -1641,9 +1602,6 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Eliminare il profilo di chat?";
-/* alert title */
-"Delete chat with member?" = "Eliminare la chat con il membro?";
-
/* No comment provided by engineer. */
"Delete chat?" = "Eliminare la chat?";
@@ -1914,7 +1872,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Non perdere messaggi importanti.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Non mostrare più";
/* No comment provided by engineer. */
@@ -2158,9 +2116,6 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Errore nell'accettazione della richiesta di contatto";
-/* alert title */
-"Error accepting member" = "Errore di accettazione del membro";
-
/* No comment provided by engineer. */
"Error adding member(s)" = "Errore di aggiunta membro/i";
@@ -2218,9 +2173,6 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Errore nell'eliminazione del database della chat";
-/* alert title */
-"Error deleting chat with member" = "Errore di eliminazione della chat con il membro";
-
/* No comment provided by engineer. */
"Error deleting chat!" = "Errore nell'eliminazione della chat!";
@@ -2284,7 +2236,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Errore di registrazione per le notifiche";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Errore nella rimozione del membro";
/* alert title */
@@ -2658,9 +2610,6 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "L'invito al gruppo non è più valido, è stato rimosso dal mittente.";
-/* No comment provided by engineer. */
-"group is deleted" = "il gruppo è eliminato";
-
/* No comment provided by engineer. */
"Group link" = "Link del gruppo";
@@ -3189,15 +3138,9 @@ snd error text */
/* profile update event chat item */
"member %@ changed to %@" = "il membro %1$@ è diventato %2$@";
-/* No comment provided by engineer. */
-"Member admission" = "Ammissione del membro";
-
/* rcv group event chat item */
"member connected" = "si è connesso/a";
-/* No comment provided by engineer. */
-"member has old version" = "il membro ha una versione vecchia";
-
/* item status text */
"Member inactive" = "Membro inattivo";
@@ -3219,9 +3162,6 @@ snd error text */
/* No comment provided by engineer. */
"Member will be removed from group - this cannot be undone!" = "Il membro verrà rimosso dal gruppo, non è reversibile!";
-/* alert message */
-"Member will join the group, accept member?" = "Il membro entrerà nel gruppo, accettarlo?";
-
/* No comment provided by engineer. */
"Members can add message reactions." = "I membri del gruppo possono aggiungere reazioni ai messaggi.";
@@ -3492,9 +3432,6 @@ snd error text */
/* No comment provided by engineer. */
"New member role" = "Nuovo ruolo del membro";
-/* rcv group event chat item */
-"New member wants to join the group." = "Un nuovo membro vuole entrare nel gruppo.";
-
/* notification */
"new message" = "messaggio nuovo";
@@ -3534,9 +3471,6 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Nessuna chat nell'elenco %@";
-/* No comment provided by engineer. */
-"No chats with members" = "Nessuna chat con membri";
-
/* No comment provided by engineer. */
"No contacts selected" = "Nessun contatto selezionato";
@@ -3621,9 +3555,6 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Non compatibile!";
-/* No comment provided by engineer. */
-"not synchronized" = "non sincronizzato";
-
/* No comment provided by engineer. */
"Notes" = "Note";
@@ -3656,7 +3587,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "off";
@@ -3669,8 +3599,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "offerto %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -3766,9 +3695,6 @@ alert button */
/* No comment provided by engineer. */
"Open group" = "Apri gruppo";
-/* alert title */
-"Open link?" = "Aprire il link?";
-
/* authentication reason */
"Open migration to another device" = "Apri migrazione ad un altro dispositivo";
@@ -3871,9 +3797,6 @@ alert button */
/* No comment provided by engineer. */
"pending approval" = "in attesa di approvazione";
-/* No comment provided by engineer. */
-"pending review" = "in attesa di revisione";
-
/* No comment provided by engineer. */
"Periodic" = "Periodicamente";
@@ -3943,9 +3866,6 @@ alert button */
/* token info */
"Please try to disable and re-enable notfications." = "Prova a disattivare e riattivare le notifiche.";
-/* snd group event chat item */
-"Please wait for group moderators to review your request to join the group." = "Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo.";
-
/* token info */
"Please wait for token activation to complete." = "Attendi il completamento dell'attivazione del token.";
@@ -4226,9 +4146,6 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Rifiuta la richiesta di contatto";
-/* alert title */
-"Reject member?" = "Rifiutare il membro?";
-
/* No comment provided by engineer. */
"rejected" = "rifiutato";
@@ -4268,9 +4185,6 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "indirizzo di contatto rimosso";
-/* No comment provided by engineer. */
-"removed from group" = "rimosso dal gruppo";
-
/* profile update event chat item */
"removed profile picture" = "immagine del profilo rimossa";
@@ -4319,9 +4233,6 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Motivo della segnalazione?";
-/* alert title */
-"Report sent to moderators" = "Segnalazione inviata ai moderatori";
-
/* report reason */
"Report spam: only group moderators will see it." = "Segnala spam: solo i moderatori del gruppo lo vedranno.";
@@ -4337,9 +4248,6 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Segnalazioni";
-/* No comment provided by engineer. */
-"request to join rejected" = "richiesta di entrare rifiutata";
-
/* chat list item title */
"requested to connect" = "richiesto di connettersi";
@@ -4394,21 +4302,9 @@ swipe action */
/* chat item action */
"Reveal" = "Rivela";
-/* No comment provided by engineer. */
-"review" = "revisiona";
-
/* No comment provided by engineer. */
"Review conditions" = "Leggi le condizioni";
-/* admission stage */
-"Review members" = "Revisiona i membri";
-
-/* admission stage description */
-"Review members before admitting (\"knocking\")." = "Revisiona i membri prima di ammetterli (\"bussare\").";
-
-/* No comment provided by engineer. */
-"reviewed by admins" = "revisionato dagli amministratori";
-
/* No comment provided by engineer. */
"Revoke" = "Revoca";
@@ -4437,9 +4333,6 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Salva (e avvisa i contatti)";
-/* alert title */
-"Save admission settings?" = "Salvare le impostazioni di ammissione?";
-
/* alert button */
"Save and notify contact" = "Salva e avvisa il contatto";
@@ -4776,9 +4669,6 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Impostalo al posto dell'autenticazione di sistema.";
-/* No comment provided by engineer. */
-"Set member admission" = "Imposta l'ammissione del membro";
-
/* No comment provided by engineer. */
"Set message expiration in chats." = "Imposta la scadenza dei messaggi nelle chat.";
@@ -5210,6 +5100,9 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Il database vecchio non è stato rimosso durante la migrazione, può essere eliminato.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Le stesse condizioni si applicheranno all'operatore **%@**.";
@@ -5819,9 +5712,6 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Hai accettato la connessione";
-/* snd group event chat item */
-"you accepted this member" = "hai accettato questo membro";
-
/* No comment provided by engineer. */
"You allow" = "Lo consenti";
@@ -5933,9 +5823,6 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Puoi vedere di nuovo il link di invito nei dettagli di connessione.";
-/* alert message */
-"You can view your reports in Chat with admins." = "Puoi vedere le tue segnalazioni nella chat con gli amministratori.";
-
/* No comment provided by engineer. */
"You can't send messages!" = "Non puoi inviare messaggi!";
@@ -6104,15 +5991,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Il profilo è condiviso solo con i tuoi contatti.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti. I server di SimpleX non possono vedere il tuo profilo.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Il tuo profilo è stato cambiato. Se lo salvi, il profilo aggiornato verrà inviato a tutti i tuoi contatti.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Il tuo profilo, i contatti e i messaggi recapitati sono memorizzati sul tuo dispositivo.";
+
/* No comment provided by engineer. */
"Your random profile" = "Il tuo profilo casuale";
diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings
index 481c21eb8b..d214f88e1c 100644
--- a/apps/ios/ja.lproj/Localizable.strings
+++ b/apps/ios/ja.lproj/Localizable.strings
@@ -1277,7 +1277,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "有効にしない";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "次から表示しない";
/* No comment provided by engineer. */
@@ -1514,7 +1514,7 @@ swipe action */
/* alert title */
"Error receiving file" = "ファイル受信にエラー発生";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "メンバー除名にエラー発生";
/* No comment provided by engineer. */
@@ -2295,7 +2295,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "オフ";
@@ -2308,8 +2307,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "提供された %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "OK";
/* No comment provided by engineer. */
@@ -3113,6 +3111,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "古いデータベースは移行時に削除されなかったので、削除することができます。";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "長らくお待たせしました! ✅";
@@ -3591,10 +3592,10 @@ chat item action */
"Your profile **%@** will be shared." = "あなたのプロファイル **%@** が共有されます。";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "プロフィールは連絡先にしか共有されません。";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "プロフィールはデバイスに保存され、連絡先とのみ共有されます。 SimpleX サーバーはあなたのプロファイルを参照できません。";
+"Your profile, contacts and delivered messages are stored on your device." = "あなたのプロフィール、連絡先、送信したメッセージがご自分の端末に保存されます。";
/* No comment provided by engineer. */
"Your random profile" = "あなたのランダム・プロフィール";
diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings
index 5caea12ee2..232de56641 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -1869,7 +1869,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Mis geen belangrijke berichten.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Niet meer weergeven";
/* No comment provided by engineer. */
@@ -2233,7 +2233,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Fout bij registreren voor meldingen";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Fout bij verwijderen van lid";
/* alert title */
@@ -3584,7 +3584,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "uit";
@@ -3597,8 +3596,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "voorgesteld %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "OK";
/* No comment provided by engineer. */
@@ -5093,6 +5091,9 @@ report reason */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "De oude database is niet verwijderd tijdens de migratie, deze kan worden verwijderd.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Dezelfde voorwaarden gelden voor operator **%@**.";
@@ -5969,15 +5970,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Uw profiel **%@** wordt gedeeld.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Het profiel wordt alleen gedeeld met uw contacten.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Uw profiel wordt op uw apparaat opgeslagen en alleen gedeeld met uw contacten. SimpleX servers kunnen uw profiel niet zien.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Je profiel is gewijzigd. Als je het opslaat, wordt het bijgewerkte profiel naar al je contacten verzonden.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Uw profiel, contacten en afgeleverde berichten worden op uw apparaat opgeslagen.";
+
/* No comment provided by engineer. */
"Your random profile" = "Je willekeurige profiel";
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index e3e860e329..31a9b87662 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -1761,7 +1761,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Nie włączaj";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Nie pokazuj ponownie";
/* No comment provided by engineer. */
@@ -2092,7 +2092,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Błąd ponownego łączenia serwerów";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Błąd usuwania członka";
/* No comment provided by engineer. */
@@ -3236,7 +3236,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "wyłączony";
@@ -3249,8 +3248,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "zaoferował %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ok";
/* No comment provided by engineer. */
@@ -4558,6 +4556,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Stara baza danych nie została usunięta podczas migracji, można ją usunąć.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom.";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Drugi tik, który przegapiliśmy! ✅";
@@ -5353,15 +5354,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Twój profil **%@** zostanie udostępniony.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Profil jest udostępniany tylko Twoim kontaktom.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Twój profil jest przechowywany na urządzeniu i udostępniany tylko Twoim kontaktom. Serwery SimpleX nie mogą zobaczyć Twojego profilu.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Twój profil został zmieniony. Jeśli go zapiszesz, zaktualizowany profil zostanie wysłany do wszystkich kontaktów.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Twój profil, kontakty i dostarczone wiadomości są przechowywane na Twoim urządzeniu.";
+
/* No comment provided by engineer. */
"Your random profile" = "Twój losowy profil";
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 759a4c79f4..cb837836ff 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.lproj/Localizable.strings
@@ -164,7 +164,7 @@
"%d file(s) were not downloaded." = "%d файлов не было загружено.";
/* time interval */
-"%d hours" = "%d час.";
+"%d hours" = "%d ч.";
/* alert title */
"%d messages not forwarded" = "%d сообщений не переслано";
@@ -345,12 +345,6 @@ accept incoming call via notification
swipe action */
"Accept" = "Принять";
-/* alert action */
-"Accept as member" = "Принять в группу";
-
-/* alert action */
-"Accept as observer" = "Принять как читателя";
-
/* No comment provided by engineer. */
"Accept conditions" = "Принять условия";
@@ -364,12 +358,6 @@ swipe action */
swipe action */
"Accept incognito" = "Принять инкогнито";
-/* alert title */
-"Accept member" = "Принять члена";
-
-/* rcv group event chat item */
-"accepted %@" = "принят %@";
-
/* call status */
"accepted call" = "принятый звонок";
@@ -379,9 +367,6 @@ swipe action */
/* chat list item title */
"accepted invitation" = "принятое приглашение";
-/* rcv group event chat item */
-"accepted you" = "Вы приняты";
-
/* No comment provided by engineer. */
"Acknowledged" = "Подтверждено";
@@ -478,9 +463,6 @@ swipe action */
/* chat item text */
"agreeing encryption…" = "шифрование согласовывается…";
-/* member criteria value */
-"all" = "все";
-
/* No comment provided by engineer. */
"All" = "Все";
@@ -502,9 +484,6 @@ swipe action */
/* No comment provided by engineer. */
"All group members will remain connected." = "Все члены группы останутся соединены.";
-/* feature role */
-"all members" = "все члены";
-
/* No comment provided by engineer. */
"All messages and files are sent **end-to-end encrypted**, with post-quantum security in direct messages." = "Все сообщения и файлы отправляются с **end-to-end шифрованием**, с постквантовой безопасностью в прямых разговорах.";
@@ -523,9 +502,6 @@ swipe action */
/* No comment provided by engineer. */
"All reports will be archived for you." = "Все сообщения о нарушениях будут заархивированы для вас.";
-/* No comment provided by engineer. */
-"All servers" = "Все серверы";
-
/* No comment provided by engineer. */
"All your contacts will remain connected." = "Все контакты, которые соединились через этот адрес, сохранятся.";
@@ -923,9 +899,6 @@ marked deleted chat item preview text */
/* No comment provided by engineer. */
"Can't message member" = "Не удаётся отправить сообщение члену группы";
-/* No comment provided by engineer. */
-"can't send messages" = "нельзя отправлять";
-
/* alert action
alert button */
"Cancel" = "Отменить";
@@ -1063,18 +1036,9 @@ set passcode view */
/* No comment provided by engineer. */
"Chat will be deleted for you - this cannot be undone!" = "Разговор будет удален для Вас - это действие нельзя отменить!";
-/* chat toolbar */
-"Chat with admins" = "Чат с админами";
-
-/* No comment provided by engineer. */
-"Chat with member" = "Чат с членом группы";
-
/* No comment provided by engineer. */
"Chats" = "Чаты";
-/* No comment provided by engineer. */
-"Chats with members" = "Чаты с членами группы";
-
/* No comment provided by engineer. */
"Check messages every 20 min." = "Проверять сообщения каждые 20 минут.";
@@ -1363,15 +1327,9 @@ set passcode view */
/* No comment provided by engineer. */
"Contact already exists" = "Существующий контакт";
-/* No comment provided by engineer. */
-"contact deleted" = "контакт удален";
-
/* No comment provided by engineer. */
"Contact deleted!" = "Контакт удален!";
-/* No comment provided by engineer. */
-"contact disabled" = "контакт выключен";
-
/* No comment provided by engineer. */
"contact has e2e encryption" = "у контакта есть e2e шифрование";
@@ -1390,9 +1348,6 @@ set passcode view */
/* No comment provided by engineer. */
"Contact name" = "Имена контактов";
-/* No comment provided by engineer. */
-"contact not ready" = "контакт не готов";
-
/* No comment provided by engineer. */
"Contact preferences" = "Предпочтения контакта";
@@ -1641,9 +1596,6 @@ swipe action */
/* No comment provided by engineer. */
"Delete chat profile?" = "Удалить профиль?";
-/* alert title */
-"Delete chat with member?" = "Удалить чат с членом группы?";
-
/* No comment provided by engineer. */
"Delete chat?" = "Удалить разговор?";
@@ -1830,9 +1782,6 @@ swipe action */
/* No comment provided by engineer. */
"Direct messages between members are prohibited in this chat." = "Личные сообщения запрещены в этой группе.";
-/* No comment provided by engineer. */
-"Direct messages between members are prohibited." = "Прямые сообщения между членами запрещены.";
-
/* No comment provided by engineer. */
"Disable (keep overrides)" = "Выключить (кроме исключений)";
@@ -1887,9 +1836,6 @@ swipe action */
/* No comment provided by engineer. */
"Do it later" = "Отложить";
-/* No comment provided by engineer. */
-"Do not send history to new members." = "Не отправлять историю новым членам.";
-
/* No comment provided by engineer. */
"Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку.";
@@ -1914,7 +1860,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "Не пропустите важные сообщения.";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Не показывать";
/* No comment provided by engineer. */
@@ -1987,9 +1933,6 @@ chat item action */
/* No comment provided by engineer. */
"Enable camera access" = "Включить доступ к камере";
-/* No comment provided by engineer. */
-"Enable Flux in Network & servers settings for better metadata privacy." = "Включите Flux в настройках Сеть и серверы для лучшей конфиденциальности метаданных.";
-
/* No comment provided by engineer. */
"Enable for all" = "Включить для всех";
@@ -2158,12 +2101,6 @@ chat item action */
/* No comment provided by engineer. */
"Error accepting contact request" = "Ошибка при принятии запроса на соединение";
-/* alert title */
-"Error accepting member" = "Ошибка вступления члена группы";
-
-/* No comment provided by engineer. */
-"Error adding member(s)" = "Ошибка при добавлении членов группы";
-
/* alert title */
"Error adding server" = "Ошибка добавления сервера";
@@ -2200,9 +2137,6 @@ chat item action */
/* alert title */
"Error creating list" = "Ошибка создания списка";
-/* No comment provided by engineer. */
-"Error creating member contact" = "Ошибка при создании контакта";
-
/* No comment provided by engineer. */
"Error creating message" = "Ошибка создания сообщения";
@@ -2218,9 +2152,6 @@ chat item action */
/* No comment provided by engineer. */
"Error deleting chat database" = "Ошибка при удалении данных чата";
-/* alert title */
-"Error deleting chat with member" = "Ошибка при удалении чата с членом группы";
-
/* No comment provided by engineer. */
"Error deleting chat!" = "Ошибка при удалении чата!";
@@ -2284,9 +2215,6 @@ chat item action */
/* alert title */
"Error registering for notifications" = "Ошибка регистрации для уведомлений";
-/* alert title */
-"Error removing member" = "Ошибка при удалении члена группы";
-
/* alert title */
"Error reordering lists" = "Ошибка сортировки списков";
@@ -2323,9 +2251,6 @@ chat item action */
/* No comment provided by engineer. */
"Error sending email" = "Ошибка отправки email";
-/* No comment provided by engineer. */
-"Error sending member contact invitation" = "Ошибка при отправке приглашения члену";
-
/* No comment provided by engineer. */
"Error sending message" = "Ошибка при отправке сообщения";
@@ -2526,9 +2451,6 @@ snd error text */
/* No comment provided by engineer. */
"Fix not supported by contact" = "Починка не поддерживается контактом";
-/* No comment provided by engineer. */
-"Fix not supported by group member" = "Починка не поддерживается членом группы.";
-
/* No comment provided by engineer. */
"For all moderators" = "Для всех модераторов";
@@ -2607,9 +2529,6 @@ snd error text */
/* No comment provided by engineer. */
"Full name (optional)" = "Полное имя (не обязательно)";
-/* No comment provided by engineer. */
-"Fully decentralized – visible only to members." = "Группа полностью децентрализована – она видна только членам.";
-
/* No comment provided by engineer. */
"Fully re-implemented - work in background!" = "Полностью обновлены - работают в фоне!";
@@ -2658,9 +2577,6 @@ snd error text */
/* No comment provided by engineer. */
"Group invitation is no longer valid, it was removed by sender." = "Приглашение в группу больше не действительно, оно было удалено отправителем.";
-/* No comment provided by engineer. */
-"group is deleted" = "группа удалена";
-
/* No comment provided by engineer. */
"Group link" = "Ссылка группы";
@@ -2679,18 +2595,12 @@ snd error text */
/* No comment provided by engineer. */
"Group profile" = "Профиль группы";
-/* No comment provided by engineer. */
-"Group profile is stored on members' devices, not on the servers." = "Профиль группы хранится на устройствах членов, а не на серверах.";
-
/* snd group event chat item */
"group profile updated" = "профиль группы обновлен";
/* No comment provided by engineer. */
"Group welcome message" = "Приветственное сообщение группы";
-/* No comment provided by engineer. */
-"Group will be deleted for all members - this cannot be undone!" = "Группа будет удалена для всех членов - это действие нельзя отменить!";
-
/* No comment provided by engineer. */
"Group will be deleted for you - this cannot be undone!" = "Группа будет удалена для Вас - это действие нельзя отменить!";
@@ -2727,9 +2637,6 @@ snd error text */
/* No comment provided by engineer. */
"History" = "История";
-/* No comment provided by engineer. */
-"History is not sent to new members." = "История не отправляется новым членам.";
-
/* time unit */
"hours" = "часов";
@@ -2964,9 +2871,6 @@ snd error text */
/* No comment provided by engineer. */
"Invite friends" = "Пригласить друзей";
-/* No comment provided by engineer. */
-"Invite members" = "Пригласить членов группы";
-
/* No comment provided by engineer. */
"Invite to chat" = "Пригласить в разговор";
@@ -3180,75 +3084,15 @@ snd error text */
/* blur media */
"Medium" = "Среднее";
-/* member role */
-"member" = "член группы";
-
-/* No comment provided by engineer. */
-"Member" = "Член группы";
-
-/* profile update event chat item */
-"member %@ changed to %@" = "член %1$@ изменился на %2$@";
-
-/* No comment provided by engineer. */
-"Member admission" = "Приём членов в группу";
-
/* rcv group event chat item */
"member connected" = "соединен(а)";
-/* No comment provided by engineer. */
-"member has old version" = "член имеет старую версию";
-
-/* item status text */
-"Member inactive" = "Член неактивен";
-
/* chat feature */
"Member reports" = "Сообщения о нарушениях";
/* No comment provided by engineer. */
"Member role will be changed to \"%@\". All chat members will be notified." = "Роль участника будет изменена на \"%@\". Все участники разговора получат уведомление.";
-/* No comment provided by engineer. */
-"Member role will be changed to \"%@\". All group members will be notified." = "Роль члена будет изменена на \"%@\". Все члены группы получат уведомление.";
-
-/* No comment provided by engineer. */
-"Member role will be changed to \"%@\". The member will receive a new invitation." = "Роль члена будет изменена на \"%@\". Будет отправлено новое приглашение.";
-
-/* No comment provided by engineer. */
-"Member will be removed from chat - this cannot be undone!" = "Член будет удален из разговора - это действие нельзя отменить!";
-
-/* No comment provided by engineer. */
-"Member will be removed from group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!";
-
-/* alert message */
-"Member will join the group, accept member?" = "Участник хочет присоединиться к группе. Принять?";
-
-/* No comment provided by engineer. */
-"Members can add message reactions." = "Члены могут добавлять реакции на сообщения.";
-
-/* No comment provided by engineer. */
-"Members can irreversibly delete sent messages. (24 hours)" = "Члены могут необратимо удалять отправленные сообщения. (24 часа)";
-
-/* No comment provided by engineer. */
-"Members can report messsages to moderators." = "Члены группы могут пожаловаться модераторам.";
-
-/* No comment provided by engineer. */
-"Members can send direct messages." = "Члены могут посылать прямые сообщения.";
-
-/* No comment provided by engineer. */
-"Members can send disappearing messages." = "Члены могут посылать исчезающие сообщения.";
-
-/* No comment provided by engineer. */
-"Members can send files and media." = "Члены могут слать файлы и медиа.";
-
-/* No comment provided by engineer. */
-"Members can send SimpleX links." = "Члены могут отправлять ссылки SimpleX.";
-
-/* No comment provided by engineer. */
-"Members can send voice messages." = "Члены могут отправлять голосовые сообщения.";
-
-/* No comment provided by engineer. */
-"Mention members 👋" = "Упоминайте участников 👋";
-
/* No comment provided by engineer. */
"Menus" = "Меню";
@@ -3270,9 +3114,6 @@ snd error text */
/* item status text */
"Message forwarded" = "Сообщение переслано";
-/* item status description */
-"Message may be delivered later if member becomes active." = "Сообщение может быть доставлено позже, если член группы станет активным.";
-
/* No comment provided by engineer. */
"Message queue info" = "Информация об очереди сообщений";
@@ -3489,12 +3330,6 @@ snd error text */
/* No comment provided by engineer. */
"New media options" = "Новые медиа-опции";
-/* No comment provided by engineer. */
-"New member role" = "Роль члена группы";
-
-/* rcv group event chat item */
-"New member wants to join the group." = "Новый участник хочет присоединиться к группе.";
-
/* notification */
"new message" = "новое сообщение";
@@ -3534,9 +3369,6 @@ snd error text */
/* No comment provided by engineer. */
"No chats in list %@" = "Нет чатов в списке %@";
-/* No comment provided by engineer. */
-"No chats with members" = "Нет чатов с членами группы";
-
/* No comment provided by engineer. */
"No contacts selected" = "Контакты не выбраны";
@@ -3621,9 +3453,6 @@ snd error text */
/* No comment provided by engineer. */
"Not compatible!" = "Несовместимая версия!";
-/* No comment provided by engineer. */
-"not synchronized" = "не синхронизирован";
-
/* No comment provided by engineer. */
"Notes" = "Заметки";
@@ -3648,15 +3477,11 @@ snd error text */
/* alert title */
"Notifications status" = "Статус уведомлений";
-/* No comment provided by engineer. */
-"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Теперь админы могут:\n- удалять сообщения членов.\n- приостанавливать членов (роль наблюдатель)";
-
/* member role */
"observer" = "читатель";
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "нет";
@@ -3669,8 +3494,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "предложил(a) %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Ок";
/* No comment provided by engineer. */
@@ -3766,9 +3590,6 @@ alert button */
/* No comment provided by engineer. */
"Open group" = "Открыть группу";
-/* alert title */
-"Open link?" = "Открыть ссылку?";
-
/* authentication reason */
"Open migration to another device" = "Открытие миграции на другое устройство";
@@ -3844,9 +3665,6 @@ alert button */
/* No comment provided by engineer. */
"Password to show" = "Пароль чтобы раскрыть";
-/* past/unknown group member */
-"Past member %@" = "Бывший член %@";
-
/* No comment provided by engineer. */
"Paste desktop address" = "Вставить адрес компьютера";
@@ -3871,9 +3689,6 @@ alert button */
/* No comment provided by engineer. */
"pending approval" = "ожидает утверждения";
-/* No comment provided by engineer. */
-"pending review" = "ожидает одобрения";
-
/* No comment provided by engineer. */
"Periodic" = "Периодически";
@@ -3943,9 +3758,6 @@ alert button */
/* token info */
"Please try to disable and re-enable notfications." = "Попробуйте выключить и снова включить уведомления.";
-/* snd group event chat item */
-"Please wait for group moderators to review your request to join the group." = "Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление.";
-
/* token info */
"Please wait for token activation to complete." = "Пожалуйста, дождитесь завершения активации токена.";
@@ -4045,9 +3857,6 @@ alert button */
/* No comment provided by engineer. */
"Prohibit reporting messages to moderators." = "Запретить жаловаться модераторам группы.";
-/* No comment provided by engineer. */
-"Prohibit sending direct messages to members." = "Запретить посылать прямые сообщения членам группы.";
-
/* No comment provided by engineer. */
"Prohibit sending disappearing messages." = "Запретить посылать исчезающие сообщения.";
@@ -4226,9 +4035,6 @@ swipe action */
/* No comment provided by engineer. */
"Reject contact request" = "Отклонить запрос";
-/* alert title */
-"Reject member?" = "Отклонить участника?";
-
/* No comment provided by engineer. */
"rejected" = "отклонён";
@@ -4250,12 +4056,6 @@ swipe action */
/* No comment provided by engineer. */
"Remove image" = "Удалить изображение";
-/* No comment provided by engineer. */
-"Remove member" = "Удалить члена группы";
-
-/* No comment provided by engineer. */
-"Remove member?" = "Удалить члена группы?";
-
/* No comment provided by engineer. */
"Remove passphrase from keychain?" = "Удалить пароль из Keychain?";
@@ -4268,9 +4068,6 @@ swipe action */
/* profile update event chat item */
"removed contact address" = "удалён адрес контакта";
-/* No comment provided by engineer. */
-"removed from group" = "удален из группы";
-
/* profile update event chat item */
"removed profile picture" = "удалена картинка профиля";
@@ -4319,9 +4116,6 @@ swipe action */
/* No comment provided by engineer. */
"Report reason?" = "Причина сообщения?";
-/* alert title */
-"Report sent to moderators" = "Жалоба отправлена модераторам";
-
/* report reason */
"Report spam: only group moderators will see it." = "Пожаловаться на спам: увидят только модераторы группы.";
@@ -4337,9 +4131,6 @@ swipe action */
/* No comment provided by engineer. */
"Reports" = "Сообщения о нарушениях";
-/* No comment provided by engineer. */
-"request to join rejected" = "запрос на вступление отклонён";
-
/* chat list item title */
"requested to connect" = "запрошено соединение";
@@ -4394,21 +4185,9 @@ swipe action */
/* chat item action */
"Reveal" = "Показать";
-/* No comment provided by engineer. */
-"review" = "рассмотрение";
-
/* No comment provided by engineer. */
"Review conditions" = "Посмотреть условия";
-/* admission stage */
-"Review members" = "Одобрять членов";
-
-/* admission stage description */
-"Review members before admitting (\"knocking\")." = "Одобрять членов для вступления в группу.";
-
-/* No comment provided by engineer. */
-"reviewed by admins" = "одобрен админами";
-
/* No comment provided by engineer. */
"Revoke" = "Отозвать";
@@ -4437,15 +4216,9 @@ chat item action */
/* alert button */
"Save (and notify contacts)" = "Сохранить (и уведомить контакты)";
-/* alert title */
-"Save admission settings?" = "Сохранить настройки вступления?";
-
/* alert button */
"Save and notify contact" = "Сохранить и уведомить контакт";
-/* No comment provided by engineer. */
-"Save and notify group members" = "Сохранить и уведомить членов группы";
-
/* No comment provided by engineer. */
"Save and reconnect" = "Сохранить и переподключиться";
@@ -4638,9 +4411,6 @@ chat item action */
/* No comment provided by engineer. */
"Send them from gallery or custom keyboards." = "Отправьте из галереи или из дополнительных клавиатур.";
-/* No comment provided by engineer. */
-"Send up to 100 last messages to new members." = "Отправить до 100 последних сообщений новым членам.";
-
/* alert message */
"Sender cancelled file transfer." = "Отправитель отменил передачу файла.";
@@ -4776,9 +4546,6 @@ chat item action */
/* No comment provided by engineer. */
"Set it instead of system authentication." = "Установите код вместо системной аутентификации.";
-/* No comment provided by engineer. */
-"Set member admission" = "Приём членов в группу";
-
/* No comment provided by engineer. */
"Set message expiration in chats." = "Установите срок хранения сообщений в чатах.";
@@ -4797,9 +4564,6 @@ chat item action */
/* No comment provided by engineer. */
"Set passphrase to export" = "Установите пароль";
-/* No comment provided by engineer. */
-"Set the message shown to new members!" = "Установить сообщение для новых членов группы!";
-
/* No comment provided by engineer. */
"Set timeouts for proxy/VPN" = "Установить таймауты для прокси/VPN";
@@ -4852,9 +4616,6 @@ chat item action */
/* No comment provided by engineer. */
"Share with contacts" = "Поделиться с контактами";
-/* No comment provided by engineer. */
-"Short link" = "Короткая ссылка";
-
/* No comment provided by engineer. */
"Show → on messages sent via private routing." = "Показать → на сообщениях доставленных конфиденциально.";
@@ -4897,9 +4658,6 @@ chat item action */
/* No comment provided by engineer. */
"SimpleX address or 1-time link?" = "Адрес SimpleX или одноразовая ссылка?";
-/* simplex link type */
-"SimpleX channel link" = "SimpleX ссылка канала";
-
/* No comment provided by engineer. */
"SimpleX Chat and Flux made an agreement to include Flux-operated servers into the app." = "SimpleX Chat и Flux заключили соглашение добавить серверы под управлением Flux в приложение.";
@@ -5195,21 +4953,12 @@ report reason */
/* No comment provided by engineer. */
"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "Неправильный ID предыдущего сообщения (меньше или равен предыдущему).\nЭто может произойти из-за ошибки программы, или когда соединение компроментировано.";
-/* No comment provided by engineer. */
-"The message will be deleted for all members." = "Сообщение будет удалено для всех членов группы.";
-
-/* No comment provided by engineer. */
-"The message will be marked as moderated for all members." = "Сообщение будет помечено как удаленное для всех членов группы.";
-
-/* No comment provided by engineer. */
-"The messages will be deleted for all members." = "Сообщения будут удалены для всех членов группы.";
-
-/* No comment provided by engineer. */
-"The messages will be marked as moderated for all members." = "Сообщения будут помечены как удаленные для всех членов группы.";
-
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Предыдущая версия данных чата не удалена при перемещении, её можно удалить.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Те же самые условия будут приняты для оператора **%@**.";
@@ -5273,9 +5022,6 @@ report reason */
/* No comment provided by engineer. */
"This display name is invalid. Please choose another name." = "Ошибка имени профиля. Пожалуйста, выберите другое имя.";
-/* No comment provided by engineer. */
-"This group has over %lld members, delivery receipts are not sent." = "В этой группе более %lld членов, отчёты о доставке не отправляются.";
-
/* No comment provided by engineer. */
"This group no longer exists." = "Эта группа больше не существует.";
@@ -5285,9 +5031,6 @@ report reason */
/* No comment provided by engineer. */
"This is your own SimpleX address!" = "Это ваш собственный адрес SimpleX!";
-/* No comment provided by engineer. */
-"This link requires a newer app version. Please upgrade the app or ask your contact to send a compatible link." = "Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку.";
-
/* No comment provided by engineer. */
"This link was used with another mobile device, please create a new link on the desktop." = "Эта ссылка была использована на другом мобильном, пожалуйста, создайте новую ссылку на компьютере.";
@@ -5399,15 +5142,6 @@ report reason */
/* No comment provided by engineer. */
"Unblock for all" = "Разблокировать для всех";
-/* No comment provided by engineer. */
-"Unblock member" = "Разблокировать члена группы";
-
-/* No comment provided by engineer. */
-"Unblock member for all?" = "Разблокировать члена для всех?";
-
-/* No comment provided by engineer. */
-"Unblock member?" = "Разблокировать члена группы?";
-
/* rcv group event chat item */
"unblocked %@" = "%@ разблокирован";
@@ -5480,12 +5214,6 @@ report reason */
/* swipe action */
"Unread" = "Не прочитано";
-/* No comment provided by engineer. */
-"Unsupported connection link" = "Ссылка не поддерживается";
-
-/* No comment provided by engineer. */
-"Up to 100 last messages are sent to new members." = "До 100 последних сообщений отправляются новым членам.";
-
/* No comment provided by engineer. */
"Update" = "Обновить";
@@ -5576,9 +5304,6 @@ report reason */
/* No comment provided by engineer. */
"Use servers" = "Использовать серверы";
-/* No comment provided by engineer. */
-"Use short links (BETA)" = "Короткие ссылки (БЕТА)";
-
/* No comment provided by engineer. */
"Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?";
@@ -5588,9 +5313,6 @@ report reason */
/* No comment provided by engineer. */
"Use TCP port %@ when no port is specified." = "Использовать TCP-порт %@, когда порт не указан.";
-/* No comment provided by engineer. */
-"Use TCP port 443 for preset servers only." = "Использовать TCP-порт 443 только для серверов по умолчанию.";
-
/* No comment provided by engineer. */
"Use the app while in the call." = "Используйте приложение во время звонка.";
@@ -5819,9 +5541,6 @@ report reason */
/* No comment provided by engineer. */
"You accepted connection" = "Вы приняли приглашение соединиться";
-/* snd group event chat item */
-"you accepted this member" = "Вы приняли этого члена";
-
/* No comment provided by engineer. */
"You allow" = "Вы разрешаете";
@@ -5912,9 +5631,6 @@ report reason */
/* No comment provided by engineer. */
"You can set lock screen notification preview via settings." = "Вы можете установить просмотр уведомлений на экране блокировки в настройках.";
-/* No comment provided by engineer. */
-"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Вы можете поделиться ссылкой или QR кодом - через них можно присоединиться к группе. Вы сможете удалить ссылку, сохранив членов группы, которые через нее соединились.";
-
/* No comment provided by engineer. */
"You can share this address with your contacts to let them connect with **%@**." = "Вы можете поделиться этим адресом с Вашими контактами, чтобы они могли соединиться с **%@**.";
@@ -5933,9 +5649,6 @@ report reason */
/* alert message */
"You can view invitation link again in connection details." = "Вы можете увидеть ссылку-приглашение снова открыв соединение.";
-/* alert message */
-"You can view your reports in Chat with admins." = "Вы можете найти Ваши жалобы в Чате с админами.";
-
/* No comment provided by engineer. */
"You can't send messages!" = "Вы не можете отправлять сообщения!";
@@ -5972,9 +5685,6 @@ report reason */
/* No comment provided by engineer. */
"You joined this group" = "Вы вступили в эту группу";
-/* No comment provided by engineer. */
-"You joined this group. Connecting to inviting group member." = "Вы вступили в эту группу. Устанавливается соединение с пригласившим членом группы.";
-
/* snd group event chat item */
"you left" = "Вы покинули группу";
@@ -6029,9 +5739,6 @@ report reason */
/* No comment provided by engineer. */
"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Вы будете аутентифицированы при запуске и возобновлении приложения, которое было 30 секунд в фоновом режиме.";
-/* No comment provided by engineer. */
-"You will connect to all group members." = "Вы соединитесь со всеми членами группы.";
-
/* No comment provided by engineer. */
"You will still receive calls and notifications from muted profiles when they are active." = "Вы все равно получите звонки и уведомления в профилях без звука, когда они активные.";
@@ -6104,15 +5811,15 @@ report reason */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Будет отправлен Ваш профиль **%@**.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Ваш профиль храниться на Вашем устройстве и отправляется только контактам.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профиль хранится на Вашем устройстве и отправляется только Вашим контактам. SimpleX серверы не могут получить доступ к Вашему профилю.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профиль был изменен. Если вы сохраните его, обновленный профиль будет отправлен всем вашим контактам.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Ваш профиль, контакты и доставленные сообщения хранятся на Вашем устройстве.";
+
/* No comment provided by engineer. */
"Your random profile" = "Случайный профиль";
@@ -6127,3 +5834,4 @@ report reason */
/* No comment provided by engineer. */
"Your SimpleX address" = "Ваш адрес SimpleX";
+
diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings
index a6ef88d0d4..57c0466eb9 100644
--- a/apps/ios/th.lproj/Localizable.strings
+++ b/apps/ios/th.lproj/Localizable.strings
@@ -1028,7 +1028,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "อย่าเปิดใช้งาน";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "ไม่ต้องแสดงอีก";
/* No comment provided by engineer. */
@@ -1256,7 +1256,7 @@ swipe action */
/* alert title */
"Error receiving file" = "เกิดข้อผิดพลาดในการรับไฟล์";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "เกิดข้อผิดพลาดในการลบสมาชิก";
/* No comment provided by engineer. */
@@ -2016,7 +2016,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "ปิด";
@@ -2029,8 +2028,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "เสนอแล้ว %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "ตกลง";
/* No comment provided by engineer. */
@@ -2831,6 +2829,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "ฐานข้อมูลเก่าไม่ได้ถูกลบในระหว่างการย้ายข้อมูล แต่สามารถลบได้";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "ขีดที่สองที่เราพลาด! ✅";
@@ -3291,10 +3292,10 @@ chat item action */
"Your privacy" = "ความเป็นส่วนตัวของคุณ";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "โปรไฟล์นี้แชร์กับผู้ติดต่อของคุณเท่านั้น";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "โปรไฟล์ของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณและแชร์กับผู้ติดต่อของคุณเท่านั้น เซิร์ฟเวอร์ SimpleX ไม่สามารถดูโปรไฟล์ของคุณได้";
+"Your profile, contacts and delivered messages are stored on your device." = "โปรไฟล์ รายชื่อผู้ติดต่อ และข้อความที่ส่งของคุณจะถูกจัดเก็บไว้ในอุปกรณ์ของคุณ";
/* No comment provided by engineer. */
"Your random profile" = "โปรไฟล์แบบสุ่มของคุณ";
diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings
index 3d44c895ec..e3bb11d1cc 100644
--- a/apps/ios/tr.lproj/Localizable.strings
+++ b/apps/ios/tr.lproj/Localizable.strings
@@ -1740,7 +1740,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Etkinleştirme";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Yeniden gösterme";
/* No comment provided by engineer. */
@@ -2083,7 +2083,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Hata sunuculara yeniden bağlanılıyor";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Kişiyi silerken sorun oluştu";
/* No comment provided by engineer. */
@@ -3272,7 +3272,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "kapalı";
@@ -3285,8 +3284,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "%1$@: %2$@ teklif etti";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Tamam";
/* No comment provided by engineer. */
@@ -4603,6 +4601,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Eski veritabanı geçiş sırasında kaldırılmadı, silinebilir.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak.";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "Özlediğimiz ikinci tik! ✅";
@@ -5398,15 +5399,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Profiliniz **%@** paylaşılacaktır.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Profil sadece kişilerinle paylaşılacak.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Profiliniz cihazınızda saklanır ve sadece kişilerinizle paylaşılır. SimpleX sunucuları profilinizi göremez.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Profiliniz değiştirildi. Kaydederseniz, güncellenmiş profil tüm kişilerinize gönderilecektir.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Profiliniz, kişileriniz ve gönderilmiş mesajlar cihazınızda saklanır.";
+
/* No comment provided by engineer. */
"Your random profile" = "Rasgele profiliniz";
diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings
index 932c29d368..734b8dda82 100644
--- a/apps/ios/uk.lproj/Localizable.strings
+++ b/apps/ios/uk.lproj/Localizable.strings
@@ -1746,7 +1746,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't enable" = "Не вмикати";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "Більше не показувати";
/* No comment provided by engineer. */
@@ -2089,7 +2089,7 @@ chat item action */
/* No comment provided by engineer. */
"Error reconnecting servers" = "Помилка перепідключення серверів";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "Помилка видалення учасника";
/* No comment provided by engineer. */
@@ -3317,7 +3317,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "вимкнено";
@@ -3330,8 +3329,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "запропонував %1$@: %2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "Гаразд";
/* No comment provided by engineer. */
@@ -4723,6 +4721,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "Стара база даних не була видалена під час міграції, її можна видалити.";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам.";
+
/* No comment provided by engineer. */
"The same conditions will apply to operator **%@**." = "Такі ж умови діятимуть і для оператора **%@**.";
@@ -5578,15 +5579,15 @@ chat item action */
/* No comment provided by engineer. */
"Your profile **%@** will be shared." = "Ваш профіль **%@** буде опублікований.";
-/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "Профіль доступний лише вашим контактам.";
-
/* No comment provided by engineer. */
"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "Ваш профіль зберігається на вашому пристрої і доступний лише вашим контактам. Сервери SimpleX не бачать ваш профіль.";
/* alert message */
"Your profile was changed. If you save it, the updated profile will be sent to all your contacts." = "Ваш профіль було змінено. Якщо ви збережете його, оновлений профіль буде надіслано всім вашим контактам.";
+/* No comment provided by engineer. */
+"Your profile, contacts and delivered messages are stored on your device." = "Ваш профіль, контакти та доставлені повідомлення зберігаються на вашому пристрої.";
+
/* No comment provided by engineer. */
"Your random profile" = "Ваш випадковий профіль";
diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings
index 19d7c268d4..e3f9669d9f 100644
--- a/apps/ios/zh-Hans.lproj/Localizable.strings
+++ b/apps/ios/zh-Hans.lproj/Localizable.strings
@@ -1866,7 +1866,7 @@ swipe action */
/* No comment provided by engineer. */
"Don't miss important messages." = "不错过重要消息。";
-/* alert action */
+/* No comment provided by engineer. */
"Don't show again" = "不再显示";
/* No comment provided by engineer. */
@@ -2227,7 +2227,7 @@ chat item action */
/* alert title */
"Error registering for notifications" = "注册消息推送出错";
-/* alert title */
+/* No comment provided by engineer. */
"Error removing member" = "删除成员错误";
/* alert title */
@@ -3575,7 +3575,6 @@ snd error text */
/* enabled status
group pref value
-member criteria value
time to disappear */
"off" = "关闭";
@@ -3588,8 +3587,7 @@ time to disappear */
/* feature offered item */
"offered %@: %@" = "已提供 %1$@:%2$@";
-/* alert action
-alert button */
+/* alert button */
"Ok" = "好的";
/* No comment provided by engineer. */
@@ -4924,6 +4922,9 @@ chat item action */
/* No comment provided by engineer. */
"The old database was not removed during the migration, it can be deleted." = "旧数据库在迁移过程中没有被移除,可以删除。";
+/* No comment provided by engineer. */
+"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。";
+
/* No comment provided by engineer. */
"The second tick we missed! ✅" = "我们错过的第二个\"√\"!✅";
@@ -5696,10 +5697,10 @@ chat item action */
"Your profile **%@** will be shared." = "您的个人资料 **%@** 将被共享。";
/* No comment provided by engineer. */
-"Your profile is stored on your device and only shared with your contacts." = "该资料仅与您的联系人共享。";
+"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
/* No comment provided by engineer. */
-"Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile." = "您的资料存储在您的设备上并仅与您的联系人共享。 SimpleX 服务器无法看到您的资料。";
+"Your profile, contacts and delivered messages are stored on your device." = "您的资料、联系人和发送的消息存储在您的设备上。";
/* No comment provided by engineer. */
"Your random profile" = "您的随机资料";
diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts
index 345a75b1e7..e2927e4aaf 100644
--- a/apps/multiplatform/common/build.gradle.kts
+++ b/apps/multiplatform/common/build.gradle.kts
@@ -155,6 +155,7 @@ buildConfig {
buildConfigField("String", "DESKTOP_VERSION_NAME", "\"${extra["desktop.version_name"]}\"")
buildConfigField("int", "DESKTOP_VERSION_CODE", "${extra["desktop.version_code"]}")
buildConfigField("String", "DATABASE_BACKEND", "\"${extra["database.backend"]}\"")
+ buildConfigField("Boolean", "ANDROID_BUNDLE", "${extra["android.bundle"]}")
}
}
diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt
index a09ca2792b..54e3061d25 100644
--- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt
+++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.android.kt
@@ -91,7 +91,7 @@ fun UserPickerUserBox(
ProfileImageForActiveCall(size = USER_PICKER_IMAGE_SIZE, image = userInfo.user.profile.image, color = MaterialTheme.colors.secondaryVariant)
if (userInfo.unreadCount > 0 && !userInfo.user.activeUser) {
- userUnreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false)
+ unreadBadge(userInfo.unreadCount, userInfo.user.showNtfs, false)
}
}
val user = userInfo.user
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
index 270b3a73b2..61c20587bf 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt
@@ -295,13 +295,14 @@ object ChatModel {
}
}
- class ChatsContext(val secondaryContextFilter: SecondaryContextFilter?) {
+ class ChatsContext(val contentTag: MsgContentTag?) {
val chats = mutableStateOf(SnapshotStateList())
/** if you modify the items by adding/removing them, use helpers methods like [addToChatItems], [removeLastChatItems], [removeAllAndNotify], [clearAndNotify] and so on.
* If some helper is missing, create it. Notify is needed to track state of items that we added manually (not via api call). See [apiLoadMessages].
* If you use api call to get the items, use just [add] instead of [addToChatItems].
* Never modify underlying list directly because it produces unexpected results in ChatView's LazyColumn (setting by index is ok) */
val chatItems = mutableStateOf(SnapshotStateList())
+ val chatItemStatuses = mutableMapOf()
// set listener here that will be notified on every add/delete of a chat item
val chatState = ActiveChatState()
@@ -309,26 +310,6 @@ object ChatModel {
fun getChat(id: String): Chat? = chats.value.firstOrNull { it.id == id }
private fun getChatIndex(rhId: Long?, id: String): Int = chats.value.indexOfFirst { it.id == id && it.remoteHostId == rhId }
- val contentTag: MsgContentTag? =
- when (secondaryContextFilter) {
- null -> null
- is SecondaryContextFilter.GroupChatScopeContext -> null
- is SecondaryContextFilter.MsgContentTagContext -> secondaryContextFilter.contentTag
- }
-
- val groupScopeInfo: GroupChatScopeInfo? =
- when (secondaryContextFilter) {
- null -> null
- is SecondaryContextFilter.GroupChatScopeContext -> secondaryContextFilter.groupScopeInfo
- is SecondaryContextFilter.MsgContentTagContext -> null
- }
-
- val isUserSupportChat: Boolean =
- when (groupScopeInfo) {
- null -> false
- is GroupChatScopeInfo.MemberSupport -> groupScopeInfo.groupMember_ == null
- }
-
suspend fun addChat(chat: Chat) {
chats.add(index = 0, chat)
popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = 0)
@@ -361,8 +342,6 @@ object ChatModel {
)
)
}
- } else if (currentCInfo is ChatInfo.Group && newCInfo is ChatInfo.Group && newCInfo.groupChatScope != null) {
- newCInfo = newCInfo.copy(groupInfo = newCInfo.groupInfo, groupChatScope = null)
}
chats[i] = chats[i].copy(chatInfo = newCInfo)
}
@@ -385,7 +364,7 @@ object ChatModel {
updateContact(rhId, updatedContact)
}
- suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo, groupChatScope = null))
+ suspend fun updateGroup(rhId: Long?, groupInfo: GroupInfo) = updateChat(rhId, ChatInfo.Group(groupInfo))
private suspend fun updateChat(rhId: Long?, cInfo: ChatInfo, addMissing: Boolean = true) {
if (hasChat(rhId, cInfo.id)) {
@@ -437,64 +416,55 @@ object ChatModel {
}
suspend fun addChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
- // updates membersRequireAttention
- updateChatInfo(rhId, cInfo)
// mark chat non deleted
if (cInfo is ChatInfo.Direct && cInfo.chatDeleted) {
val updatedContact = cInfo.contact.copy(chatDeleted = false)
updateContact(rhId, updatedContact)
}
- // update chat list
+ // update previews
val i = getChatIndex(rhId, cInfo.id)
val chat: Chat
if (i >= 0) {
- chat = chatsContext.chats[i]
- // update preview (for chat from main scope to show new items for invitee in pending status)
- if (cInfo.groupChatScope() == null || cInfo.groupInfo_?.membership?.memberPending == true) {
- val newPreviewItem = when (cInfo) {
- is ChatInfo.Group -> {
- val currentPreviewItem = chat.chatItems.firstOrNull()
- if (currentPreviewItem != null) {
- if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
- cItem
- } else {
- currentPreviewItem
- }
- } else {
+ chat = chats[i]
+ val newPreviewItem = when (cInfo) {
+ is ChatInfo.Group -> {
+ val currentPreviewItem = chat.chatItems.firstOrNull()
+ if (currentPreviewItem != null) {
+ if (cItem.meta.itemTs >= currentPreviewItem.meta.itemTs) {
cItem
+ } else {
+ currentPreviewItem
}
+ } else {
+ cItem
}
-
- else -> cItem
}
- val wasUnread = chat.unreadTag
- chatsContext.chats[i] = chat.copy(
- chatItems = arrayListOf(newPreviewItem),
- chatStats =
- if (cItem.meta.itemStatus is CIStatus.RcvNew) {
- increaseUnreadCounter(rhId, currentUser.value!!)
- chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions)
- } else
- chat.chatStats
- )
- updateChatTagReadInPrimaryContext(chatsContext.chats[i], wasUnread)
+ else -> cItem
}
- // pop chat
+ val wasUnread = chat.unreadTag
+ chats[i] = chat.copy(
+ chatItems = arrayListOf(newPreviewItem),
+ chatStats =
+ if (cItem.meta.itemStatus is CIStatus.RcvNew) {
+ increaseUnreadCounter(rhId, currentUser.value!!)
+ chat.chatStats.copy(unreadCount = chat.chatStats.unreadCount + 1, unreadMentions = if (cItem.meta.userMention) chat.chatStats.unreadMentions + 1 else chat.chatStats.unreadMentions)
+ }
+ else
+ chat.chatStats
+ )
+ updateChatTagReadNoContentTag(chats[i], wasUnread)
+
if (appPlatform.isDesktop && cItem.chatDir.sent) {
- reorderChat(chatsContext.chats[i], 0)
+ reorderChat(chats[i], 0)
} else {
popChatCollector.throttlePopChat(chat.remoteHostId, chat.id, currentPosition = i)
}
} else {
- if (cInfo.groupChatScope() == null) {
- addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
- } else {
- addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = emptyList()))
- }
+ addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
}
- // add to current scope
withContext(Dispatchers.Main) {
- if (chatItemBelongsToScope(cInfo, cItem)) {
+ // add to current chat
+ if (chatId.value == cInfo.id) {
// Prevent situation when chat item already in the list received from backend
if (chatItems.value.none { it.id == cItem.id }) {
if (chatItems.value.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
@@ -506,101 +476,84 @@ object ChatModel {
}
}
}
-
- private fun chatItemBelongsToScope(cInfo: ChatInfo, cItem: ChatItem): Boolean =
- when (secondaryContextFilter) {
- null ->
- chatId.value == cInfo.id && cInfo.groupChatScope() == null
- is SecondaryContextFilter.GroupChatScopeContext -> {
- val cInfoScope = cInfo.groupChatScope()
- if (cInfoScope != null) {
- chatId.value == cInfo.id && sameChatScope(cInfoScope, secondaryContextFilter.groupScopeInfo.toChatScope())
- } else {
- false
- }
- }
- is SecondaryContextFilter.MsgContentTagContext ->
- chatId.value == cInfo.id && cItem.isReport
- }
suspend fun upsertChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem): Boolean {
- var itemAdded = false
- // update chat list
- if (cInfo.groupChatScope() == null) {
- val i = getChatIndex(rhId, cInfo.id)
- val chat: Chat
- if (i >= 0) {
- chat = chats[i]
- val pItem = chat.chatItems.lastOrNull()
- if (pItem?.id == cItem.id) {
- chats[i] = chat.copy(chatItems = arrayListOf(cItem))
- if (pItem.isRcvNew && !cItem.isRcvNew) {
- // status changed from New to Read, update counter
- decreaseCounterInPrimaryContext(rhId, cInfo.id)
- }
+ // update previews
+ val i = getChatIndex(rhId, cInfo.id)
+ val chat: Chat
+ val res: Boolean
+ if (i >= 0) {
+ chat = chats[i]
+ val pItem = chat.chatItems.lastOrNull()
+ if (pItem?.id == cItem.id) {
+ chats[i] = chat.copy(chatItems = arrayListOf(cItem))
+ if (pItem.isRcvNew && !cItem.isRcvNew) {
+ // status changed from New to Read, update counter
+ decreaseCounterInChatNoContentTag(rhId, cInfo.id)
}
- } else {
- addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
- itemAdded = true
}
+ res = false
+ } else {
+ addChat(Chat(remoteHostId = rhId, chatInfo = cInfo, chatItems = arrayListOf(cItem)))
+ res = true
}
- // update current scope
- withContext(Dispatchers.Main) {
- if (chatItemBelongsToScope(cInfo, cItem)) {
+ return withContext(Dispatchers.Main) {
+ // update current chat
+ if (chatId.value == cInfo.id) {
if (cItem.isDeletedContent || cItem.meta.itemDeleted != null) {
AudioPlayer.stop(cItem)
}
val items = chatItems.value
val itemIndex = items.indexOfFirst { it.id == cItem.id }
if (itemIndex >= 0) {
- val oldStatus = items[itemIndex].meta.itemStatus
- val newStatus = cItem.meta.itemStatus
- val ci = if (shouldKeepOldSndCIStatus(oldStatus, newStatus)) {
- cItem.copy(meta = cItem.meta.copy(itemStatus = oldStatus))
+ items[itemIndex] = cItem
+ false
+ } else {
+ val status = chatItemStatuses.remove(cItem.id)
+ val ci = if (status != null && cItem.meta.itemStatus is CIStatus.SndNew) {
+ cItem.copy(meta = cItem.meta.copy(itemStatus = status))
} else {
cItem
}
- items[itemIndex] = ci
- } else {
- addToChatItems(cItem)
- itemAdded = true
+ addToChatItems(ci)
+ true
}
+ } else {
+ res
}
}
- return itemAdded
}
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem, status: CIStatus? = null, atIndex: Int? = null) {
withContext(Dispatchers.Main) {
- if (chatItemBelongsToScope(cInfo, cItem)) {
+ if (chatId.value == cInfo.id) {
val items = chatItems.value
val itemIndex = atIndex ?: items.indexOfFirst { it.id == cItem.id }
if (itemIndex >= 0) {
items[itemIndex] = cItem
}
+ } else if (status != null) {
+ chatItemStatuses[cItem.id] = status
}
}
}
fun removeChatItem(rhId: Long?, cInfo: ChatInfo, cItem: ChatItem) {
- // update chat list
- if (cInfo.groupChatScope() == null) {
- if (cItem.isRcvNew) {
- decreaseCounterInPrimaryContext(rhId, cInfo.id)
- }
- // update preview
- val i = getChatIndex(rhId, cInfo.id)
- val chat: Chat
- if (i >= 0) {
- chat = chats[i]
- val pItem = chat.chatItems.lastOrNull()
- if (pItem?.id == cItem.id) {
- chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
- }
+ if (cItem.isRcvNew) {
+ decreaseCounterInChatNoContentTag(rhId, cInfo.id)
+ }
+ // update previews
+ val i = getChatIndex(rhId, cInfo.id)
+ val chat: Chat
+ if (i >= 0) {
+ chat = chats[i]
+ val pItem = chat.chatItems.lastOrNull()
+ if (pItem?.id == cItem.id) {
+ chats[i] = chat.copy(chatItems = arrayListOf(ChatItem.deletedItemDummy))
}
}
- // remove from current scope
- if (chatItemBelongsToScope(cInfo, cItem)) {
+ // remove from current chat
+ if (chatId.value == cInfo.id) {
chatItems.removeAllAndNotify {
// We delete taking into account meta.createdAt to make sure we will not be in situation when two items with the same id will be deleted
// (it can happen if already deleted chat item in backend still in the list and new one came with the same (re-used) chat item id)
@@ -633,7 +586,7 @@ object ChatModel {
Log.d(TAG, "exiting removeMemberItems")
return
}
- val cInfo = ChatInfo.Group(groupInfo, groupChatScope = null) // TODO [knocking] review
+ val cInfo = ChatInfo.Group(groupInfo)
if (chatId.value == groupInfo.id) {
for (i in 0 until chatItems.value.size) {
val updatedItem = removedUpdatedItem(chatItems.value[i])
@@ -664,6 +617,7 @@ object ChatModel {
}
// clear current chat
if (chatId.value == cInfo.id) {
+ chatItemStatuses.clear()
chatItems.clearAndNotify()
}
}
@@ -734,7 +688,7 @@ object ChatModel {
chats[chatIdx] = chat.copy(
chatStats = chat.chatStats.copy(unreadCount = unreadCount, unreadMentions = unreadMentions)
)
- updateChatTagReadInPrimaryContext(chats[chatIdx], wasUnread)
+ updateChatTagReadNoContentTag(chats[chatIdx], wasUnread)
}
}
}
@@ -775,9 +729,9 @@ object ChatModel {
return markedRead to mentionsMarkedRead
}
- private fun decreaseCounterInPrimaryContext(rhId: Long?, chatId: ChatId) {
+ private fun decreaseCounterInChatNoContentTag(rhId: Long?, chatId: ChatId) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
- if (secondaryContextFilter != null) return
+ if (contentTag != null) return
val chatIndex = getChatIndex(rhId, chatId)
if (chatIndex == -1) return
@@ -791,7 +745,7 @@ object ChatModel {
unreadCount = unreadCount,
)
)
- updateChatTagReadInPrimaryContext(chats[chatIndex], wasUnread)
+ updateChatTagReadNoContentTag(chats[chatIndex], wasUnread)
}
fun removeChat(rhId: Long?, id: String) {
@@ -860,16 +814,16 @@ object ChatModel {
}
fun increaseUnreadCounter(rhId: Long?, user: UserLike) {
- changeUnreadCounterInPrimaryContext(rhId, user, 1)
+ changeUnreadCounterNoContentTag(rhId, user, 1)
}
fun decreaseUnreadCounter(rhId: Long?, user: UserLike, by: Int = 1) {
- changeUnreadCounterInPrimaryContext(rhId, user, -by)
+ changeUnreadCounterNoContentTag(rhId, user, -by)
}
- private fun changeUnreadCounterInPrimaryContext(rhId: Long?, user: UserLike, by: Int) {
+ private fun changeUnreadCounterNoContentTag(rhId: Long?, user: UserLike, by: Int) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
- if (secondaryContextFilter != null) return
+ if (contentTag != null) return
val i = users.indexOfFirst { it.user.userId == user.userId && it.user.remoteHostId == rhId }
if (i != -1) {
@@ -877,9 +831,9 @@ object ChatModel {
}
}
- fun updateChatTagReadInPrimaryContext(chat: Chat, wasUnread: Boolean) {
+ fun updateChatTagReadNoContentTag(chat: Chat, wasUnread: Boolean) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
- if (secondaryContextFilter != null) return
+ if (contentTag != null) return
val tags = chat.chatInfo.chatTags ?: return
val nowUnread = chat.unreadTag
@@ -889,21 +843,21 @@ object ChatModel {
unreadTags[tag] = (unreadTags[tag] ?: 0) + 1
}
} else if (!nowUnread && wasUnread) {
- markChatTagReadInPrimaryContext_(chat, tags)
+ markChatTagReadNoContentTag_(chat, tags)
}
}
fun markChatTagRead(chat: Chat) {
if (chat.unreadTag) {
chat.chatInfo.chatTags?.let { tags ->
- markChatTagReadInPrimaryContext_(chat, tags)
+ markChatTagReadNoContentTag_(chat, tags)
}
}
}
- private fun markChatTagReadInPrimaryContext_(chat: Chat, tags: List) {
+ private fun markChatTagReadNoContentTag_(chat: Chat, tags: List) {
// updates anything only in main ChatView, not GroupReportsView or anything else from the future
- if (secondaryContextFilter != null) return
+ if (contentTag != null) return
for (tag in tags) {
val count = unreadTags[tag]
@@ -935,12 +889,12 @@ object ChatModel {
val wasReportsCount = chat.chatStats.reportsCount
val nowReportsCount = chats[i].chatStats.reportsCount
val by = if (wasReportsCount == 0 && nowReportsCount > 0) 1 else if (wasReportsCount > 0 && nowReportsCount == 0) -1 else 0
- changeGroupReportsTagInPrimaryContext(by)
+ changeGroupReportsTagNoContentTag(by)
}
}
- private fun changeGroupReportsTagInPrimaryContext(by: Int = 0) {
- if (by == 0 || secondaryContextFilter != null) return
+ private fun changeGroupReportsTagNoContentTag(by: Int = 0) {
+ if (by == 0 || contentTag != null) return
presetTags[PresetTagKind.GROUP_REPORTS] = kotlin.math.max(0, (presetTags[PresetTagKind.GROUP_REPORTS] ?: 0) + by)
clearActiveChatFilterIfNeeded()
}
@@ -1145,28 +1099,6 @@ enum class ChatType(val type: String) {
ContactConnection(":");
}
-sealed class GroupChatScope {
- class MemberSupport(val groupMemberId_: Long?): GroupChatScope()
-}
-
-fun sameChatScope(scope1: GroupChatScope, scope2: GroupChatScope) =
- scope1 is GroupChatScope.MemberSupport
- && scope2 is GroupChatScope.MemberSupport
- && scope1.groupMemberId_ == scope2.groupMemberId_
-
-@Serializable
-sealed class GroupChatScopeInfo {
- @Serializable @SerialName("memberSupport") data class MemberSupport(val groupMember_: GroupMember?) : GroupChatScopeInfo()
-
- fun toChatScope(): GroupChatScope =
- when (this) {
- is MemberSupport -> when (groupMember_) {
- null -> GroupChatScope.MemberSupport(groupMemberId_ = null)
- else -> GroupChatScope.MemberSupport(groupMemberId_ = groupMember_.groupMemberId)
- }
- }
-}
-
@Serializable
data class User(
val remoteHostId: Long?,
@@ -1272,6 +1204,8 @@ interface SomeChat {
val apiId: Long
val ready: Boolean
val chatDeleted: Boolean
+ val userCantSendReason: Pair?
+ val sendMsgEnabled: Boolean
val incognito: Boolean
fun featureEnabled(feature: ChatFeature): Boolean
val timedMessagesTTL: Int?
@@ -1303,16 +1237,6 @@ data class Chat(
val id: String get() = chatInfo.id
- val supportUnreadCount: Int get() = when (chatInfo) {
- is ChatInfo.Group ->
- if (chatInfo.groupInfo.canModerate) {
- chatInfo.groupInfo.membersRequireAttention
- } else {
- chatInfo.groupInfo.membership.supportChat?.unread ?: 0
- }
- else -> 0
- }
-
fun groupFeatureEnabled(feature: GroupFeature): Boolean =
if (chatInfo is ChatInfo.Group) {
chatInfo.groupInfo.groupFeatureEnabled(feature)
@@ -1351,6 +1275,8 @@ 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)
override val timedMessagesTTL: Int? get() = contact.timedMessagesTTL
@@ -1368,13 +1294,15 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
@Serializable @SerialName("group")
- data class Group(val groupInfo: GroupInfo, val groupChatScope: GroupChatScopeInfo?): ChatInfo() {
+ data class Group(val groupInfo: GroupInfo): ChatInfo() {
override val chatType get() = ChatType.Group
override val localDisplayName get() = groupInfo.localDisplayName
override val id get() = groupInfo.id
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)
override val timedMessagesTTL: Int? get() = groupInfo.timedMessagesTTL
@@ -1386,7 +1314,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
override val localAlias get() = groupInfo.localAlias
companion object {
- val sampleData = Group(GroupInfo.sampleData, groupChatScope = null)
+ val sampleData = Group(GroupInfo.sampleData)
}
}
@@ -1398,6 +1326,8 @@ 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)
override val timedMessagesTTL: Int? get() = noteFolder.timedMessagesTTL
@@ -1421,6 +1351,8 @@ 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)
override val timedMessagesTTL: Int? get() = contactRequest.timedMessagesTTL
@@ -1444,6 +1376,8 @@ 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)
override val timedMessagesTTL: Int? get() = contactConnection.timedMessagesTTL
@@ -1472,6 +1406,8 @@ 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
override val timedMessagesTTL: Int? get() = null
@@ -1486,71 +1422,6 @@ sealed class ChatInfo: SomeChat, NamedChat {
}
}
- val userCantSendReason: Pair?
- 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) {
- when (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
- }
- is GroupChatScopeInfo.MemberSupport ->
- if (groupChatScope.groupMember_ != null) {
- if (
- groupChatScope.groupMember_.versionRange.maxVersion < GROUP_KNOCKING_VERSION
- && !groupChatScope.groupMember_.memberPending
- ) {
- return generalGetString(MR.strings.cant_send_message_member_has_old_version) to null
- }
- return null
- } else {
- 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
- }
-
fun ntfsEnabled(ci: ChatItem): Boolean =
ntfsEnabled(ci.meta.userMention)
@@ -1594,13 +1465,7 @@ sealed class ChatInfo: SomeChat, NamedChat {
is Direct -> contact.activeConn == null && contact.profile.contactLink != null && contact.active
else -> false
}
-
- val groupInfo_: GroupInfo?
- get() = when (this) {
- is Group -> groupInfo
- else -> null
- }
-}
+ }
@Serializable
sealed class NetworkStatus {
@@ -1654,6 +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 userCantSendReason: Pair?
+ 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) {
@@ -1878,7 +1754,6 @@ data class GroupInfo (
override val updatedAt: Instant,
val chatTs: Instant?,
val uiThemes: ThemeModeOverrides? = null,
- val membersRequireAttention: Int,
val chatTags: List,
val chatItemTTL: Long?,
override val localAlias: String,
@@ -1888,6 +1763,23 @@ data class GroupInfo (
override val apiId get() = groupId
override val ready get() = membership.memberActive
override val chatDeleted get() = false
+ override val userCantSendReason: Pair? 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
@@ -1905,7 +1797,7 @@ data class GroupInfo (
get() = membership.memberRole == GroupMemberRole.Owner && membership.memberCurrent
val canDelete: Boolean
- get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrentOrPending
+ get() = membership.memberRole == GroupMemberRole.Owner || !membership.memberCurrent
val canAddMembers: Boolean
get() = membership.memberRole >= GroupMemberRole.Admin && membership.memberActive
@@ -1940,7 +1832,6 @@ data class GroupInfo (
updatedAt = Clock.System.now(),
chatTs = Clock.System.now(),
uiThemes = null,
- membersRequireAttention = 0,
chatTags = emptyList(),
localAlias = "",
chatItemTTL = null
@@ -1958,8 +1849,7 @@ data class GroupProfile (
val description: String? = null,
override val image: String? = null,
override val localAlias: String = "",
- val groupPreferences: GroupPreferences? = null,
- val memberAdmission: GroupMemberAdmission? = null
+ val groupPreferences: GroupPreferences? = null
): NamedChat {
companion object {
val sampleData = GroupProfile(
@@ -1969,27 +1859,6 @@ data class GroupProfile (
}
}
-@Serializable
-data class GroupMemberAdmission(
- val review: MemberCriteria? = null,
-) {
- companion object {
- val sampleData = GroupMemberAdmission(
- review = null,
- )
- }
-}
-
-@Serializable
-enum class MemberCriteria {
- @SerialName("all") All;
-
- val text: String
- get() = when(this) {
- MemberCriteria.All -> generalGetString(MR.strings.member_criteria_all)
- }
-}
-
@Serializable
data class BusinessChatInfo (
val chatType: BusinessChatType,
@@ -2018,9 +1887,7 @@ data class GroupMember (
val memberProfile: LocalProfile,
val memberContactId: Long? = null,
val memberContactProfileId: Long,
- var activeConn: Connection? = null,
- val supportChat: GroupSupportChat? = null,
- val memberChatVRange: VersionRange
+ var activeConn: Connection? = null
): NamedChat {
val id: String get() = "#$groupId @$groupMemberId"
val ready get() = activeConn?.connStatus == ConnStatus.Ready
@@ -2079,7 +1946,6 @@ data class GroupMember (
GroupMemberStatus.MemUnknown -> false
GroupMemberStatus.MemInvited -> false
GroupMemberStatus.MemPendingApproval -> true
- GroupMemberStatus.MemPendingReview -> true
GroupMemberStatus.MemIntroduced -> false
GroupMemberStatus.MemIntroInvited -> false
GroupMemberStatus.MemAccepted -> false
@@ -2097,7 +1963,6 @@ data class GroupMember (
GroupMemberStatus.MemUnknown -> false
GroupMemberStatus.MemInvited -> false
GroupMemberStatus.MemPendingApproval -> false
- GroupMemberStatus.MemPendingReview -> false
GroupMemberStatus.MemIntroduced -> true
GroupMemberStatus.MemIntroInvited -> true
GroupMemberStatus.MemAccepted -> true
@@ -2107,15 +1972,6 @@ data class GroupMember (
GroupMemberStatus.MemCreator -> true
}
- val memberPending: Boolean get() = when (this.memberStatus) {
- GroupMemberStatus.MemPendingApproval -> true
- GroupMemberStatus.MemPendingReview -> true
- else -> false
- }
-
- val memberCurrentOrPending: Boolean get() =
- memberCurrent || memberPending
-
fun canBeRemoved(groupInfo: GroupInfo): Boolean {
val userRole = groupInfo.membership.memberRole
return memberStatus != GroupMemberStatus.MemRemoved && memberStatus != GroupMemberStatus.MemLeft
@@ -2134,8 +1990,6 @@ data class GroupMember (
&& userRole >= GroupMemberRole.Moderator && userRole >= memberRole && groupInfo.membership.memberActive
}
- val versionRange: VersionRange = activeConn?.peerChatVRange ?: memberChatVRange
-
val memberIncognito = memberProfile.profileId != memberContactProfileId
companion object {
@@ -2153,20 +2007,11 @@ data class GroupMember (
memberProfile = LocalProfile.sampleData,
memberContactId = 1,
memberContactProfileId = 1L,
- activeConn = Connection.sampleData,
- memberChatVRange = VersionRange(minVersion = 1, maxVersion = 15)
+ activeConn = Connection.sampleData
)
}
}
-@Serializable
-class GroupSupportChat (
- val chatTs: Instant,
- val unread: Int,
- val memberAttention: Int,
- val mentions: Int
-)
-
@Serializable
data class GroupMemberSettings(val showMessages: Boolean) {}
@@ -2223,7 +2068,6 @@ enum class GroupMemberStatus {
@SerialName("unknown") MemUnknown,
@SerialName("invited") MemInvited,
@SerialName("pending_approval") MemPendingApproval,
- @SerialName("pending_review") MemPendingReview,
@SerialName("introduced") MemIntroduced,
@SerialName("intro-inv") MemIntroInvited,
@SerialName("accepted") MemAccepted,
@@ -2240,7 +2084,6 @@ enum class GroupMemberStatus {
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown)
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval)
- MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review)
MemIntroduced -> generalGetString(MR.strings.group_member_status_introduced)
MemIntroInvited -> generalGetString(MR.strings.group_member_status_intro_invitation)
MemAccepted -> generalGetString(MR.strings.group_member_status_accepted)
@@ -2258,7 +2101,6 @@ enum class GroupMemberStatus {
MemUnknown -> generalGetString(MR.strings.group_member_status_unknown_short)
MemInvited -> generalGetString(MR.strings.group_member_status_invited)
MemPendingApproval -> generalGetString(MR.strings.group_member_status_pending_approval_short)
- MemPendingReview -> generalGetString(MR.strings.group_member_status_pending_review_short)
MemIntroduced -> generalGetString(MR.strings.group_member_status_connecting)
MemIntroInvited -> generalGetString(MR.strings.group_member_status_connecting)
MemAccepted -> generalGetString(MR.strings.group_member_status_connecting)
@@ -2313,6 +2155,8 @@ class NoteFolder(
override val apiId get() = noteFolderId
override val chatDeleted get() = false
override val ready get() = true
+ override val userCantSendReason: Pair? = null
+ override val sendMsgEnabled get() = true
override val incognito get() = false
override fun featureEnabled(feature: ChatFeature) = feature == ChatFeature.Voice
override val timedMessagesTTL: Int? get() = null
@@ -2348,6 +2192,8 @@ 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
override val timedMessagesTTL: Int? get() = null
@@ -2386,6 +2232,8 @@ 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
override val timedMessagesTTL: Int? get() = null
@@ -2603,16 +2451,11 @@ data class ChatItem (
is CIContent.RcvGroupFeature,
is CIContent.SndGroupFeature -> CIMergeCategory.ChatFeature
is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) {
- is RcvGroupEvent.UserRole,
- is RcvGroupEvent.UserDeleted,
- is RcvGroupEvent.GroupDeleted,
- is RcvGroupEvent.MemberCreatedContact,
- is RcvGroupEvent.NewMemberPendingReview ->
- null
+ is RcvGroupEvent.UserRole, is RcvGroupEvent.UserDeleted, is RcvGroupEvent.GroupDeleted, is RcvGroupEvent.MemberCreatedContact -> null
else -> CIMergeCategory.RcvGroupEvent
}
is CIContent.SndGroupEventContent -> when (content.sndGroupEvent) {
- is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft, is SndGroupEvent.MemberAccepted, is SndGroupEvent.UserPendingReview -> null
+ is SndGroupEvent.UserRole, is SndGroupEvent.UserLeft -> null
else -> CIMergeCategory.SndGroupEvent
}
else -> {
@@ -2683,8 +2526,6 @@ data class ChatItem (
is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) {
is RcvGroupEvent.MemberAdded -> false
is RcvGroupEvent.MemberConnected -> false
- is RcvGroupEvent.MemberAccepted -> false
- is RcvGroupEvent.UserAccepted -> false
is RcvGroupEvent.MemberLeft -> false
is RcvGroupEvent.MemberRole -> false
is RcvGroupEvent.MemberBlocked -> false
@@ -2696,7 +2537,6 @@ data class ChatItem (
is RcvGroupEvent.InvitedViaGroupLink -> false
is RcvGroupEvent.MemberCreatedContact -> false
is RcvGroupEvent.MemberProfileUpdated -> false
- is RcvGroupEvent.NewMemberPendingReview -> true
}
is CIContent.SndGroupEventContent -> false
is CIContent.RcvConnEventContent -> false
@@ -2878,11 +2718,6 @@ data class ChatItem (
}
}
-sealed class SecondaryContextFilter {
- class GroupChatScopeContext(val groupScopeInfo: GroupChatScopeInfo): SecondaryContextFilter()
- class MsgContentTagContext(val contentTag: MsgContentTag): SecondaryContextFilter()
-}
-
fun MutableState>.add(index: Int, elem: Chat) {
value = SnapshotStateList().apply { addAll(value); add(index, elem) }
}
@@ -3141,19 +2976,6 @@ sealed class CIStatus {
@Serializable @SerialName("rcvRead") class RcvRead: CIStatus()
@Serializable @SerialName("invalid") class Invalid(val text: String): CIStatus()
- // as in corresponds to SENT response from agent
- fun isSent(): Boolean = when(this) {
- is SndNew -> false
- is SndSent -> true
- is SndRcvd -> false
- is SndErrorAuth -> true
- is CISSndError -> true
- is SndWarning -> true
- is RcvNew -> false
- is RcvRead -> false
- is Invalid -> false
- }
-
fun statusIcon(
primaryColor: Color,
metaColor: Color = CurrentColors.value.colors.secondary,
@@ -3193,13 +3015,6 @@ sealed class CIStatus {
}
}
-fun shouldKeepOldSndCIStatus(oldStatus: CIStatus, newStatus: CIStatus): Boolean =
- when {
- oldStatus is CIStatus.SndRcvd && newStatus !is CIStatus.SndRcvd -> true
- oldStatus.isSent() && newStatus is CIStatus.SndNew -> true
- else -> false
- }
-
@Serializable
sealed class SndError {
@Serializable @SerialName("auth") class Auth: SndError()
@@ -4338,8 +4153,6 @@ sealed class RcvDirectEvent() {
sealed class RcvGroupEvent() {
@Serializable @SerialName("memberAdded") class MemberAdded(val groupMemberId: Long, val profile: Profile): RcvGroupEvent()
@Serializable @SerialName("memberConnected") class MemberConnected(): RcvGroupEvent()
- @Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): RcvGroupEvent()
- @Serializable @SerialName("userAccepted") class UserAccepted(): RcvGroupEvent()
@Serializable @SerialName("memberLeft") class MemberLeft(): RcvGroupEvent()
@Serializable @SerialName("memberRole") class MemberRole(val groupMemberId: Long, val profile: Profile, val role: GroupMemberRole): RcvGroupEvent()
@Serializable @SerialName("memberBlocked") class MemberBlocked(val groupMemberId: Long, val profile: Profile, val blocked: Boolean): RcvGroupEvent()
@@ -4351,13 +4164,10 @@ sealed class RcvGroupEvent() {
@Serializable @SerialName("invitedViaGroupLink") class InvitedViaGroupLink(): RcvGroupEvent()
@Serializable @SerialName("memberCreatedContact") class MemberCreatedContact(): RcvGroupEvent()
@Serializable @SerialName("memberProfileUpdated") class MemberProfileUpdated(val fromProfile: Profile, val toProfile: Profile): RcvGroupEvent()
- @Serializable @SerialName("newMemberPendingReview") class NewMemberPendingReview(): RcvGroupEvent()
val text: String get() = when (this) {
is MemberAdded -> String.format(generalGetString(MR.strings.rcv_group_event_member_added), profile.profileViewName)
is MemberConnected -> generalGetString(MR.strings.rcv_group_event_member_connected)
- is MemberAccepted -> String.format(generalGetString(MR.strings.rcv_group_event_member_accepted), profile.profileViewName)
- is UserAccepted -> generalGetString(MR.strings.rcv_group_event_user_accepted)
is MemberLeft -> generalGetString(MR.strings.rcv_group_event_member_left)
is MemberRole -> String.format(generalGetString(MR.strings.rcv_group_event_changed_member_role), profile.profileViewName, role.text)
is MemberBlocked -> if (blocked) {
@@ -4373,7 +4183,6 @@ sealed class RcvGroupEvent() {
is InvitedViaGroupLink -> generalGetString(MR.strings.rcv_group_event_invited_via_your_group_link)
is MemberCreatedContact -> generalGetString(MR.strings.rcv_group_event_member_created_contact)
is MemberProfileUpdated -> profileUpdatedText(fromProfile, toProfile)
- is NewMemberPendingReview -> generalGetString(MR.strings.rcv_group_event_new_member_pending_review)
}
private fun profileUpdatedText(from: Profile, to: Profile): String =
@@ -4398,8 +4207,6 @@ sealed class SndGroupEvent() {
@Serializable @SerialName("memberDeleted") class MemberDeleted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
@Serializable @SerialName("userLeft") class UserLeft(): SndGroupEvent()
@Serializable @SerialName("groupUpdated") class GroupUpdated(val groupProfile: GroupProfile): SndGroupEvent()
- @Serializable @SerialName("memberAccepted") class MemberAccepted(val groupMemberId: Long, val profile: Profile): SndGroupEvent()
- @Serializable @SerialName("userPendingReview") class UserPendingReview(): SndGroupEvent()
val text: String get() = when (this) {
is MemberRole -> String.format(generalGetString(MR.strings.snd_group_event_changed_member_role), profile.profileViewName, role.text)
@@ -4412,8 +4219,6 @@ sealed class SndGroupEvent() {
is MemberDeleted -> String.format(generalGetString(MR.strings.snd_group_event_member_deleted), profile.profileViewName)
is UserLeft -> generalGetString(MR.strings.snd_group_event_user_left)
is GroupUpdated -> generalGetString(MR.strings.snd_group_event_group_profile_updated)
- is MemberAccepted -> generalGetString(MR.strings.snd_group_event_member_accepted)
- is UserPendingReview -> generalGetString(MR.strings.snd_group_event_user_pending_review)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
index 7cb2d9fe5e..979d79c839 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt
@@ -58,9 +58,6 @@ typealias ChatCtrl = Long
// version range that supports establishing direct connection with a group member (xGrpDirectInvVRange in core)
val CREATE_MEMBER_CONTACT_VERSION = 2
-// support group knocking (MsgScope)
-val GROUP_KNOCKING_VERSION = 15
-
enum class CallOnLockScreen {
DISABLE,
SHOW,
@@ -166,7 +163,6 @@ class AppPreferences {
val oneHandUICardShown = mkBoolPreference(SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN, false)
val addressCreationCardShown = mkBoolPreference(SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN, false)
val showMuteProfileAlert = mkBoolPreference(SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT, true)
- val showReportsInSupportChatAlert = mkBoolPreference(SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT, true)
val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null)
val appUpdateChannel = mkEnumPreference(SHARED_PREFS_APP_UPDATE_CHANNEL, AppUpdatesChannel.DISABLED) { AppUpdatesChannel.entries.firstOrNull { it.name == this } }
val appSkippedUpdate = mkStrPreference(SHARED_PREFS_APP_SKIPPED_UPDATE, "")
@@ -249,7 +245,6 @@ class AppPreferences {
liveMessageAlertShown to false,
showHiddenProfilesNotice to true,
showMuteProfileAlert to true,
- showReportsInSupportChatAlert to true,
showDeleteConversationNotice to true,
showDeleteContactNotice to true,
)
@@ -420,7 +415,6 @@ class AppPreferences {
private const val SHARED_PREFS_ONE_HAND_UI_CARD_SHOWN = "OneHandUICardShown"
private const val SHARED_PREFS_ADDRESS_CREATION_CARD_SHOWN = "AddressCreationCardShown"
private const val SHARED_PREFS_SHOW_MUTE_PROFILE_ALERT = "ShowMuteProfileAlert"
- private const val SHARED_PREFS_SHOW_REPORTS_IN_SUPPORT_CHAT_ALERT = "ShowReportsInSupportChatAlert"
private const val SHARED_PREFS_STORE_DB_PASSPHRASE = "StoreDBPassphrase"
private const val SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE = "InitialRandomDBPassphrase"
private const val SHARED_PREFS_ENCRYPTED_DB_PASSPHRASE = "EncryptedDBPassphrase"
@@ -888,8 +882,8 @@ object ChatController {
return null
}
- suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair? {
- val r = sendCmd(rh, CC.ApiGetChat(type, id, scope, contentTag, pagination, search))
+ suspend fun apiGetChat(rh: Long?, type: ChatType, id: Long, contentTag: MsgContentTag? = null, pagination: ChatPagination, search: String = ""): Pair? {
+ val r = sendCmd(rh, CC.ApiGetChat(type, id, contentTag, pagination, search))
if (r is API.Result && r.res is CR.ApiChat) return if (rh == null) r.res.chat to r.res.navInfo else r.res.chat.copy(remoteHostId = rh) to r.res.navInfo
Log.e(TAG, "apiGetChat bad response: ${r.responseType} ${r.details}")
val e = (r as? API.Error)?.err
@@ -923,8 +917,8 @@ object ChatController {
suspend fun apiReorderChatTags(rh: Long?, tagIds: List) = sendCommandOkResp(rh, CC.ApiReorderChatTags(tagIds))
- suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? {
- val cmd = CC.ApiSendMessages(type, id, scope, live, ttl, composedMessages)
+ suspend fun apiSendMessages(rh: Long?, type: ChatType, id: Long, live: Boolean = false, ttl: Int? = null, composedMessages: List): List? {
+ val cmd = CC.ApiSendMessages(type, id, live, ttl, composedMessages)
return processSendMessageCmd(rh, cmd)
}
@@ -975,27 +969,27 @@ object ChatController {
return null
}
- suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long): ChatItemInfo? {
- val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, scope, itemId))
+ suspend fun apiGetChatItemInfo(rh: Long?, type: ChatType, id: Long, itemId: Long): ChatItemInfo? {
+ val r = sendCmd(rh, CC.ApiGetChatItemInfo(type, id, itemId))
if (r is API.Result && r.res is CR.ApiChatItemInfo) return r.res.chatItemInfo
apiErrorAlert("apiGetChatItemInfo", generalGetString(MR.strings.error_loading_details), r)
return null
}
- suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, toScope: GroupChatScope?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, itemIds: List, ttl: Int?): List? {
- val cmd = CC.ApiForwardChatItems(toChatType, toChatId, toScope, fromChatType, fromChatId, fromScope, itemIds, ttl)
+ suspend fun apiForwardChatItems(rh: Long?, toChatType: ChatType, toChatId: Long, fromChatType: ChatType, fromChatId: Long, itemIds: List, ttl: Int?): List? {
+ val cmd = CC.ApiForwardChatItems(toChatType, toChatId, fromChatType, fromChatId, itemIds, ttl)
return processSendMessageCmd(rh, cmd)?.map { it.chatItem }
}
- suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, fromScope: GroupChatScope?, chatItemIds: List): CR.ForwardPlan? {
- val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, fromScope, chatItemIds))
+ suspend fun apiPlanForwardChatItems(rh: Long?, fromChatType: ChatType, fromChatId: Long, chatItemIds: List): CR.ForwardPlan? {
+ val r = sendCmd(rh, CC.ApiPlanForwardChatItems(fromChatType, fromChatId, chatItemIds))
if (r is API.Result && r.res is CR.ForwardPlan) return r.res
apiErrorAlert("apiPlanForwardChatItems", generalGetString(MR.strings.error_forwarding_messages), r)
return null
}
- suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? {
- val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, scope, itemId, updatedMessage, live))
+ suspend fun apiUpdateChatItem(rh: Long?, type: ChatType, id: Long, itemId: Long, updatedMessage: UpdatedMessage, live: Boolean = false): AChatItem? {
+ val r = sendCmd(rh, CC.ApiUpdateChatItem(type, id, itemId, updatedMessage, live))
when {
r is API.Result && r.res is CR.ChatItemUpdated -> return r.res.chatItem
r is API.Result && r.res is CR.ChatItemNotChanged -> return r.res.chatItem
@@ -1017,8 +1011,8 @@ object ChatController {
return null
}
- suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? {
- val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, scope, itemId, add, reaction))
+ suspend fun apiChatItemReaction(rh: Long?, type: ChatType, id: Long, itemId: Long, add: Boolean, reaction: MsgReaction): ChatItem? {
+ val r = sendCmd(rh, CC.ApiChatItemReaction(type, id, itemId, add, reaction))
if (r is API.Result && r.res is CR.ChatItemReaction) return r.res.reaction.chatReaction.chatItem
Log.e(TAG, "apiUpdateChatItem bad response: ${r.responseType} ${r.details}")
return null
@@ -1032,8 +1026,8 @@ object ChatController {
return null
}
- suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List, mode: CIDeleteMode): List? {
- val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, scope, itemIds, mode))
+ suspend fun apiDeleteChatItems(rh: Long?, type: ChatType, id: Long, itemIds: List, mode: CIDeleteMode): List? {
+ val r = sendCmd(rh, CC.ApiDeleteChatItem(type, id, itemIds, mode))
if (r is API.Result && r.res is CR.ChatItemsDeleted) return r.res.chatItemDeletions
Log.e(TAG, "apiDeleteChatItem bad response: ${r.responseType} ${r.details}")
return null
@@ -1665,18 +1659,18 @@ object ChatController {
return null
}
- suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?): Boolean {
- val r = sendCmd(rh, CC.ApiChatRead(type, id, scope))
+ suspend fun apiChatRead(rh: Long?, type: ChatType, id: Long): Boolean {
+ val r = sendCmd(rh, CC.ApiChatRead(type, id))
if (r.result is CR.CmdOk) return true
Log.e(TAG, "apiChatRead bad response: ${r.responseType} ${r.details}")
return false
}
- suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, scope: GroupChatScope?, itemIds: List): ChatInfo? {
- val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, scope, itemIds))
- if (r is API.Result && r.res is CR.ItemsReadForChat) return r.res.chatInfo
+ suspend fun apiChatItemsRead(rh: Long?, type: ChatType, id: Long, itemIds: List): Boolean {
+ val r = sendCmd(rh, CC.ApiChatItemsRead(type, id, itemIds))
+ if (r.result is CR.CmdOk) return true
Log.e(TAG, "apiChatItemsRead bad response: ${r.responseType} ${r.details}")
- return null
+ return false
}
suspend fun apiChatUnread(rh: Long?, type: ChatType, id: Long, unreadChat: Boolean): Boolean {
@@ -1905,25 +1899,9 @@ object ChatController {
}
}
- suspend fun apiAcceptMember(rh: Long?, groupId: Long, groupMemberId: Long, memberRole: GroupMemberRole): Pair? {
- val r = sendCmd(rh, CC.ApiAcceptMember(groupId, groupMemberId, memberRole))
- if (r is API.Result && r.res is CR.MemberAccepted) return r.res.groupInfo to r.res.member
- if (!(networkErrorAlert(r))) {
- apiErrorAlert("apiAcceptMember", generalGetString(MR.strings.error_accepting_member), r)
- }
- return null
- }
-
- suspend fun apiDeleteMemberSupportChat(rh: Long?, groupId: Long, groupMemberId: Long): Pair? {
- val r = sendCmd(rh, CC.ApiDeleteMemberSupportChat(groupId, groupMemberId))
- if (r is API.Result && r.res is CR.MemberSupportChatDeleted) return r.res.groupInfo to r.res.member
- apiErrorAlert("apiDeleteMemberSupportChat", generalGetString(MR.strings.error_deleting_member_support_chat), r)
- return null
- }
-
- suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): Pair>? {
+ suspend fun apiRemoveMembers(rh: Long?, groupId: Long, memberIds: List, withMessages: Boolean = false): List? {
val r = sendCmd(rh, CC.ApiRemoveMembers(groupId, memberIds, withMessages))
- if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.groupInfo to r.res.members
+ if (r is API.Result && r.res is CR.UserDeletedMembers) return r.res.members
if (!(networkErrorAlert(r))) {
apiErrorAlert("apiRemoveMembers", generalGetString(MR.strings.error_removing_member), r)
}
@@ -2442,7 +2420,9 @@ object ChatController {
}
}
withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem)
+ if (cItem.isReport) {
+ chatModel.secondaryChatsContext.value?.addChatItem(rhId, cInfo, cItem)
+ }
}
} else if (cItem.isRcvNew && cInfo.ntfsEnabled(cItem)) {
withContext(Dispatchers.Main) {
@@ -2468,10 +2448,12 @@ object ChatController {
val cItem = chatItem.chatItem
if (!cItem.isDeletedContent && active(r.user)) {
withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertChatItem(rhId, cInfo, cItem)
+ chatModel.chatsContext.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
}
withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, cItem)
+ if (cItem.isReport) {
+ chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, cItem, status = cItem.meta.itemStatus)
+ }
}
}
}
@@ -2483,7 +2465,9 @@ object ChatController {
chatModel.chatsContext.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
}
withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
+ if (r.reaction.chatReaction.chatItem.isReport) {
+ chatModel.secondaryChatsContext.value?.updateChatItem(r.reaction.chatInfo, r.reaction.chatReaction.chatItem)
+ }
}
}
}
@@ -2526,18 +2510,15 @@ object ChatController {
}
}
withContext(Dispatchers.Main) {
- if (toChatItem == null) {
- chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem)
- } else {
- chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
+ if (cItem.isReport) {
+ if (toChatItem == null) {
+ chatModel.secondaryChatsContext.value?.removeChatItem(rhId, cInfo, cItem)
+ } else {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(rhId, cInfo, toChatItem.chatItem)
+ }
}
}
}
- r.chatItemDeletions.lastOrNull()?.deletedChatItem?.chatInfo?.let { updatedChatInfo ->
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateChatInfo(rhId, updatedChatInfo)
- }
- }
}
is CR.GroupChatItemsDeleted -> {
groupChatItemsDeleted(rhId, r)
@@ -2594,13 +2575,6 @@ object ChatController {
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
}
- is CR.MemberAcceptedByOther ->
- if (active(r.user)) {
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
- chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
- }
- }
is CR.DeletedMemberUser -> // TODO update user member
if (active(r.user)) {
withContext(Dispatchers.Main) {
@@ -2618,7 +2592,6 @@ object ChatController {
is CR.DeletedMember ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.deletedMember)
if (r.withMessages) {
chatModel.chatsContext.removeMemberItems(rhId, r.deletedMember, byMember = r.byMember, r.groupInfo)
@@ -2634,7 +2607,6 @@ object ChatController {
is CR.LeftMember ->
if (active(r.user)) {
withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
chatModel.chatsContext.upsertGroupMember(rhId, r.groupInfo, r.member)
}
withContext(Dispatchers.Main) {
@@ -2683,16 +2655,6 @@ object ChatController {
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId, r.groupInfo)
}
- if (
- chatModel.chatId.value == r.groupInfo.id
- && ModalManager.end.hasModalOpen(ModalViewId.SECONDARY_CHAT)
- && chatModel.secondaryChatsContext.value?.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
- ) {
- withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value = null
- }
- ModalManager.end.closeModals()
- }
}
is CR.JoinedGroupMember ->
if (active(r.user)) {
@@ -3054,11 +3016,11 @@ object ChatController {
if (activeUser(rh, user)) {
val cInfo = aChatItem.chatInfo
val cItem = aChatItem.chatItem
+ withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem)
- }
- withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
+ if (cItem.isReport) {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
+ }
}
}
}
@@ -3070,7 +3032,7 @@ object ChatController {
chatModel.users.addAll(users)
return
}
- val cInfo = ChatInfo.Group(r.groupInfo, groupChatScope = null) // TODO [knocking] get scope from items?
+ val cInfo = ChatInfo.Group(r.groupInfo)
withContext(Dispatchers.Main) {
val chatsCtx = chatModel.chatsContext
r.chatItemIDs.forEach { itemId ->
@@ -3125,11 +3087,11 @@ object ChatController {
if (!activeUser(rh, user)) {
notify()
} else {
- val createdChat = withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem)
- }
+ val createdChat = withContext(Dispatchers.Main) { chatModel.chatsContext.upsertChatItem(rh, cInfo, cItem) }
withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
+ if (cItem.content.msgContent is MsgContent.MCReport) {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(rh, cInfo, cItem)
+ }
}
if (createdChat) {
notify()
@@ -3334,9 +3296,9 @@ sealed class CC {
class ApiGetSettings(val settings: AppSettings): CC()
class ApiGetChatTags(val userId: Long): CC()
class ApiGetChats(val userId: Long): CC()
- class ApiGetChat(val type: ChatType, val id: Long, val scope: GroupChatScope?, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC()
- class ApiGetChatItemInfo(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long): CC()
- class ApiSendMessages(val type: ChatType, val id: Long, val scope: GroupChatScope?, val live: Boolean, val ttl: Int?, val composedMessages: List): CC()
+ class ApiGetChat(val type: ChatType, val id: Long, val contentTag: MsgContentTag?, val pagination: ChatPagination, val search: String = ""): CC()
+ class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC()
+ class ApiSendMessages(val type: ChatType, val id: Long, val live: Boolean, val ttl: Int?, val composedMessages: List): CC()
class ApiCreateChatTag(val tag: ChatTagData): CC()
class ApiSetChatTags(val type: ChatType, val id: Long, val tagIds: List): CC()
class ApiDeleteChatTag(val tagId: Long): CC()
@@ -3344,20 +3306,18 @@ sealed class CC {
class ApiReorderChatTags(val tagIds: List): CC()
class ApiCreateChatItems(val noteFolderId: Long, val composedMessages: List): CC()
class ApiReportMessage(val groupId: Long, val chatItemId: Long, val reportReason: ReportReason, val reportText: String): CC()
- class ApiUpdateChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC()
- class ApiDeleteChatItem(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List, val mode: CIDeleteMode): CC()
+ class ApiUpdateChatItem(val type: ChatType, val id: Long, val itemId: Long, val updatedMessage: UpdatedMessage, val live: Boolean): CC()
+ class ApiDeleteChatItem(val type: ChatType, val id: Long, val itemIds: List, val mode: CIDeleteMode): CC()
class ApiDeleteMemberChatItem(val groupId: Long, val itemIds: List): CC()
class ApiArchiveReceivedReports(val groupId: Long): CC()
class ApiDeleteReceivedReports(val groupId: Long, val itemIds: List, val mode: CIDeleteMode): CC()
- class ApiChatItemReaction(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
+ class ApiChatItemReaction(val type: ChatType, val id: Long, val itemId: Long, val add: Boolean, val reaction: MsgReaction): CC()
class ApiGetReactionMembers(val userId: Long, val groupId: Long, val itemId: Long, val reaction: MsgReaction): CC()
- class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val chatItemIds: List): CC()
- class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val toScope: GroupChatScope?, val fromChatType: ChatType, val fromChatId: Long, val fromScope: GroupChatScope?, val itemIds: List, val ttl: Int?): CC()
+ class ApiPlanForwardChatItems(val fromChatType: ChatType, val fromChatId: Long, val chatItemIds: List): CC()
+ class ApiForwardChatItems(val toChatType: ChatType, val toChatId: Long, val fromChatType: ChatType, val fromChatId: Long, val itemIds: List, val ttl: Int?): CC()
class ApiNewGroup(val userId: Long, val incognito: Boolean, val groupProfile: GroupProfile): CC()
class ApiAddMember(val groupId: Long, val contactId: Long, val memberRole: GroupMemberRole): CC()
class ApiJoinGroup(val groupId: Long): CC()
- class ApiAcceptMember(val groupId: Long, val groupMemberId: Long, val memberRole: GroupMemberRole): CC()
- class ApiDeleteMemberSupportChat(val groupId: Long, val groupMemberId: Long): CC()
class ApiMembersRole(val groupId: Long, val memberIds: List, val memberRole: GroupMemberRole): CC()
class ApiBlockMembersForAll(val groupId: Long, val memberIds: List, val blocked: Boolean): CC()
class ApiRemoveMembers(val groupId: Long, val memberIds: List, val withMessages: Boolean): CC()
@@ -3435,8 +3395,8 @@ sealed class CC {
class ApiGetNetworkStatuses(): CC()
class ApiAcceptContact(val incognito: Boolean, val contactReqId: Long): CC()
class ApiRejectContact(val contactReqId: Long): CC()
- class ApiChatRead(val type: ChatType, val id: Long, val scope: GroupChatScope?): CC()
- class ApiChatItemsRead(val type: ChatType, val id: Long, val scope: GroupChatScope?, val itemIds: List): CC()
+ class ApiChatRead(val type: ChatType, val id: Long): CC()
+ class ApiChatItemsRead(val type: ChatType, val id: Long, val itemIds: List): CC()
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val userApprovedRelays: Boolean, val encrypt: Boolean, val inline: Boolean?): CC()
class CancelFile(val fileId: Long): CC()
@@ -3508,16 +3468,16 @@ sealed class CC {
} else {
" content=${contentTag.name.lowercase()}"
}
- "/_get chat ${chatRef(type, id, scope)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search")
+ "/_get chat ${chatRef(type, id)}$tag ${pagination.cmdString}" + (if (search == "") "" else " search=$search")
}
- is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id, scope)} $itemId"
+ is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId"
is ApiSendMessages -> {
val msgs = json.encodeToString(composedMessages)
val ttlStr = if (ttl != null) "$ttl" else "default"
- "/_send ${chatRef(type, id, scope)} live=${onOff(live)} ttl=${ttlStr} json $msgs"
+ "/_send ${chatRef(type, id)} live=${onOff(live)} ttl=${ttlStr} json $msgs"
}
is ApiCreateChatTag -> "/_create tag ${json.encodeToString(tag)}"
- is ApiSetChatTags -> "/_tags ${chatRef(type, id, scope = null)} ${tagIds.joinToString(",")}"
+ is ApiSetChatTags -> "/_tags ${chatRef(type, id)} ${tagIds.joinToString(",")}"
is ApiDeleteChatTag -> "/_delete tag $tagId"
is ApiUpdateChatTag -> "/_update tag $tagId ${json.encodeToString(tagData)}"
is ApiReorderChatTags -> "/_reorder tags ${tagIds.joinToString(",")}"
@@ -3526,25 +3486,23 @@ sealed class CC {
"/_create *$noteFolderId json $msgs"
}
is ApiReportMessage -> "/_report #$groupId $chatItemId reason=${json.encodeToString(reportReason).trim('"')} $reportText"
- is ApiUpdateChatItem -> "/_update item ${chatRef(type, id, scope)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}"
- is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id, scope)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
+ is ApiUpdateChatItem -> "/_update item ${chatRef(type, id)} $itemId live=${onOff(live)} ${updatedMessage.cmdString}"
+ is ApiDeleteChatItem -> "/_delete item ${chatRef(type, id)} ${itemIds.joinToString(",")} ${mode.deleteMode}"
is ApiDeleteMemberChatItem -> "/_delete member item #$groupId ${itemIds.joinToString(",")}"
is ApiArchiveReceivedReports -> "/_archive reports #$groupId"
is ApiDeleteReceivedReports -> "/_delete reports #$groupId ${itemIds.joinToString(",")} ${mode.deleteMode}"
- is ApiChatItemReaction -> "/_reaction ${chatRef(type, id, scope)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
+ is ApiChatItemReaction -> "/_reaction ${chatRef(type, id)} $itemId ${onOff(add)} ${json.encodeToString(reaction)}"
is ApiGetReactionMembers -> "/_reaction members $userId #$groupId $itemId ${json.encodeToString(reaction)}"
is ApiForwardChatItems -> {
val ttlStr = if (ttl != null) "$ttl" else "default"
- "/_forward ${chatRef(toChatType, toChatId, toScope)} ${chatRef(fromChatType, fromChatId, fromScope)} ${itemIds.joinToString(",")} ttl=${ttlStr}"
+ "/_forward ${chatRef(toChatType, toChatId)} ${chatRef(fromChatType, fromChatId)} ${itemIds.joinToString(",")} ttl=${ttlStr}"
}
is ApiPlanForwardChatItems -> {
- "/_forward plan ${chatRef(fromChatType, fromChatId, fromScope)} ${chatItemIds.joinToString(",")}"
+ "/_forward plan ${chatRef(fromChatType, fromChatId)} ${chatItemIds.joinToString(",")}"
}
is ApiNewGroup -> "/_group $userId incognito=${onOff(incognito)} ${json.encodeToString(groupProfile)}"
is ApiAddMember -> "/_add #$groupId $contactId ${memberRole.memberRole}"
is ApiJoinGroup -> "/_join #$groupId"
- is ApiAcceptMember -> "/_accept member #$groupId $groupMemberId ${memberRole.memberRole}"
- is ApiDeleteMemberSupportChat -> "/_delete member chat #$groupId $groupMemberId"
is ApiMembersRole -> "/_member role #$groupId ${memberIds.joinToString(",")} ${memberRole.memberRole}"
is ApiBlockMembersForAll -> "/_block #$groupId ${memberIds.joinToString(",")} blocked=${onOff(blocked)}"
is ApiRemoveMembers -> "/_remove #$groupId ${memberIds.joinToString(",")} messages=${onOff(withMessages)}"
@@ -3568,13 +3526,13 @@ sealed class CC {
is ApiAcceptConditions -> "/_accept_conditions ${conditionsId} ${operatorIds.joinToString(",")}"
is APISetChatItemTTL -> "/_ttl $userId ${chatItemTTLStr(seconds)}"
is APIGetChatItemTTL -> "/_ttl $userId"
- is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id, scope = null)} ${chatItemTTLStr(seconds)}"
+ is APISetChatTTL -> "/_ttl $userId ${chatRef(chatType, id)} ${chatItemTTLStr(seconds)}"
is APISetNetworkConfig -> "/_network ${json.encodeToString(networkConfig)}"
is APIGetNetworkConfig -> "/network"
is APISetNetworkInfo -> "/_network info ${json.encodeToString(networkInfo)}"
is ReconnectServer -> "/reconnect $userId $server"
is ReconnectAllServers -> "/reconnect"
- is APISetChatSettings -> "/_settings ${chatRef(type, id, scope = null)} ${json.encodeToString(chatSettings)}"
+ is APISetChatSettings -> "/_settings ${chatRef(type, id)} ${json.encodeToString(chatSettings)}"
is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}"
is APIContactInfo -> "/_info @$contactId"
is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId"
@@ -3596,8 +3554,8 @@ sealed class CC {
is APIConnectPlan -> "/_connect plan $userId $connLink"
is APIConnect -> "/_connect $userId incognito=${onOff(incognito)} ${connLink.connFullLink} ${connLink.connShortLink ?: ""}"
is ApiConnectContactViaAddress -> "/_connect contact $userId incognito=${onOff(incognito)} $contactId"
- is ApiDeleteChat -> "/_delete ${chatRef(type, id, scope = null)} ${chatDeleteMode.cmdString}"
- is ApiClearChat -> "/_clear chat ${chatRef(type, id, scope = null)}"
+ is ApiDeleteChat -> "/_delete ${chatRef(type, id)} ${chatDeleteMode.cmdString}"
+ is ApiClearChat -> "/_clear chat ${chatRef(type, id)}"
is ApiListContacts -> "/_contacts $userId"
is ApiUpdateProfile -> "/_profile $userId ${json.encodeToString(profile)}"
is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}"
@@ -3622,9 +3580,9 @@ sealed class CC {
is ApiEndCall -> "/_call end @${contact.apiId}"
is ApiCallStatus -> "/_call status @${contact.apiId} ${callStatus.value}"
is ApiGetNetworkStatuses -> "/_network_statuses"
- is ApiChatRead -> "/_read chat ${chatRef(type, id, scope)}"
- is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id, scope)} ${itemIds.joinToString(",")}"
- is ApiChatUnread -> "/_unread chat ${chatRef(type, id, scope = null)} ${onOff(unreadChat)}"
+ is ApiChatRead -> "/_read chat ${chatRef(type, id)}"
+ is ApiChatItemsRead -> "/_read chat items ${chatRef(type, id)} ${itemIds.joinToString(",")}"
+ is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ReceiveFile ->
"/freceive $fileId" +
" approved_relays=${onOff(userApprovedRelays)}" +
@@ -3708,8 +3666,6 @@ sealed class CC {
is ApiNewGroup -> "apiNewGroup"
is ApiAddMember -> "apiAddMember"
is ApiJoinGroup -> "apiJoinGroup"
- is ApiAcceptMember -> "apiAcceptMember"
- is ApiDeleteMemberSupportChat -> "apiDeleteMemberSupportChat"
is ApiMembersRole -> "apiMembersRole"
is ApiBlockMembersForAll -> "apiBlockMembersForAll"
is ApiRemoveMembers -> "apiRemoveMembers"
@@ -3846,13 +3802,7 @@ sealed class CC {
private fun maybePwd(pwd: String?): String = if (pwd == "" || pwd == null) "" else " " + json.encodeToString(pwd)
companion object {
- fun chatRef(chatType: ChatType, id: Long, scope: GroupChatScope?) = when (scope) {
- null -> "${chatType.type}${id}"
- is GroupChatScope.MemberSupport -> when (scope.groupMemberId_) {
- null -> "${chatType.type}${id}(_support)"
- else -> "${chatType.type}${id}(_support:${scope.groupMemberId_})"
- }
- }
+ fun chatRef(chatType: ChatType, id: Long) = "${chatType.type}${id}"
}
}
@@ -5814,7 +5764,6 @@ sealed class CR {
@Serializable @SerialName("contactAlreadyExists") class ContactAlreadyExists(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeleted") class ContactDeleted(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("contactDeletedByContact") class ContactDeletedByContact(val user: UserRef, val contact: Contact): CR()
- @Serializable @SerialName("itemsReadForChat") class ItemsReadForChat(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("chatCleared") class ChatCleared(val user: UserRef, val chatInfo: ChatInfo): CR()
@Serializable @SerialName("userProfileNoChange") class UserProfileNoChange(val user: User): CR()
@Serializable @SerialName("userProfileUpdated") class UserProfileUpdated(val user: User, val fromProfile: Profile, val toProfile: Profile, val updateSummary: UserProfileUpdateSummary): CR()
@@ -5863,9 +5812,6 @@ sealed class CR {
@Serializable @SerialName("receivedGroupInvitation") class ReceivedGroupInvitation(val user: UserRef, val groupInfo: GroupInfo, val contact: Contact, val memberRole: GroupMemberRole): CR()
@Serializable @SerialName("groupDeletedUser") class GroupDeletedUser(val user: UserRef, val groupInfo: GroupInfo): CR()
@Serializable @SerialName("joinedGroupMemberConnecting") class JoinedGroupMemberConnecting(val user: UserRef, val groupInfo: GroupInfo, val hostMember: GroupMember, val member: GroupMember): CR()
- @Serializable @SerialName("memberAccepted") class MemberAccepted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
- @Serializable @SerialName("memberSupportChatDeleted") class MemberSupportChatDeleted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember): CR()
- @Serializable @SerialName("memberAcceptedByOther") class MemberAcceptedByOther(val user: UserRef, val groupInfo: GroupInfo, val acceptingMember: GroupMember, val member: GroupMember): CR()
@Serializable @SerialName("memberRole") class MemberRole(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val fromRole: GroupMemberRole, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("membersRoleUser") class MembersRoleUser(val user: UserRef, val groupInfo: GroupInfo, val members: List, val toRole: GroupMemberRole): CR()
@Serializable @SerialName("memberBlockedForAll") class MemberBlockedForAll(val user: UserRef, val groupInfo: GroupInfo, val byMember: GroupMember, val member: GroupMember, val blocked: Boolean): CR()
@@ -5996,7 +5942,6 @@ sealed class CR {
is ContactAlreadyExists -> "contactAlreadyExists"
is ContactDeleted -> "contactDeleted"
is ContactDeletedByContact -> "contactDeletedByContact"
- is ItemsReadForChat -> "itemsReadForChat"
is ChatCleared -> "chatCleared"
is UserProfileNoChange -> "userProfileNoChange"
is UserProfileUpdated -> "userProfileUpdated"
@@ -6042,9 +5987,6 @@ sealed class CR {
is ReceivedGroupInvitation -> "receivedGroupInvitation"
is GroupDeletedUser -> "groupDeletedUser"
is JoinedGroupMemberConnecting -> "joinedGroupMemberConnecting"
- is MemberAccepted -> "memberAccepted"
- is MemberSupportChatDeleted -> "memberSupportChatDeleted"
- is MemberAcceptedByOther -> "memberAcceptedByOther"
is MemberRole -> "memberRole"
is MembersRoleUser -> "membersRoleUser"
is MemberBlockedForAll -> "memberBlockedForAll"
@@ -6168,7 +6110,6 @@ sealed class CR {
is ContactAlreadyExists -> withUser(user, json.encodeToString(contact))
is ContactDeleted -> withUser(user, json.encodeToString(contact))
is ContactDeletedByContact -> withUser(user, json.encodeToString(contact))
- is ItemsReadForChat -> withUser(user, json.encodeToString(chatInfo))
is ChatCleared -> withUser(user, json.encodeToString(chatInfo))
is UserProfileNoChange -> withUser(user, noDetails())
is UserProfileUpdated -> withUser(user, json.encodeToString(toProfile))
@@ -6214,9 +6155,6 @@ sealed class CR {
is ReceivedGroupInvitation -> withUser(user, "groupInfo: $groupInfo\ncontact: $contact\nmemberRole: $memberRole")
is GroupDeletedUser -> withUser(user, json.encodeToString(groupInfo))
is JoinedGroupMemberConnecting -> withUser(user, "groupInfo: $groupInfo\nhostMember: $hostMember\nmember: $member")
- is MemberAccepted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
- is MemberSupportChatDeleted -> withUser(user, "groupInfo: $groupInfo\nmember: $member")
- is MemberAcceptedByOther -> withUser(user, "groupInfo: $groupInfo\nacceptingMember: $acceptingMember\nmember: $member")
is MemberRole -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nfromRole: $fromRole\ntoRole: $toRole")
is MembersRoleUser -> withUser(user, "groupInfo: $groupInfo\nmembers: $members\ntoRole: $toRole")
is MemberBlockedForAll -> withUser(user, "groupInfo: $groupInfo\nbyMember: $byMember\nmember: $member\nblocked: $blocked")
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
index a8b77e8fdd..2a77d0a6dc 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt
@@ -40,8 +40,9 @@ import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.platform.*
-import chat.simplex.common.views.chat.group.ChatTTLOption
+import chat.simplex.common.views.chat.group.ChatTTLSection
import chat.simplex.common.views.chatlist.updateChatSettings
+import chat.simplex.common.views.database.*
import chat.simplex.common.views.newchat.*
import chat.simplex.res.MR
import kotlinx.coroutines.*
@@ -616,10 +617,7 @@ fun ChatInfoLayout(
}
SectionDividerSpaced(maxBottomPadding = false)
- SectionView {
- ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
- SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
- }
+ ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
val conn = contact.activeConn
@@ -1386,7 +1384,7 @@ private fun setChatTTL(
private suspend fun afterSetChatTTL(chatsCtx: ChatModel.ChatsContext, rhId: Long?, chatInfo: ChatInfo, progressIndicator: MutableState) {
try {
val pagination = ChatPagination.Initial(ChatPagination.INITIAL_COUNT)
- val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, scope = null, contentTag = null, pagination) ?: return
+ val (chat, navInfo) = controller.apiGetChat(rhId, chatInfo.chatType, chatInfo.apiId, null, pagination) ?: return
if (chat.chatItems.isEmpty()) {
// replacing old chat with the same old chat but without items. Less intrusive way of clearing a preview
withContext(Dispatchers.Main) {
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
index 07cdc065a7..eabe9cb60a 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemsLoader.kt
@@ -17,7 +17,7 @@ suspend fun apiLoadSingleMessage(
apiId: Long,
itemId: Long
): ChatItem? = coroutineScope {
- val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null
+ val (chat, _) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, ChatPagination.Around(itemId, 0), "") ?: return@coroutineScope null
chat.chatItems.firstOrNull()
}
@@ -31,7 +31,7 @@ suspend fun apiLoadMessages(
openAroundItemId: Long? = null,
visibleItemIndexesNonReversed: () -> IntRange = { 0 .. 0 }
) = coroutineScope {
- val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.groupScopeInfo?.toChatScope(), chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
+ val (chat, navInfo) = chatModel.controller.apiGetChat(rhId, chatType, apiId, chatsCtx.contentTag, pagination, search) ?: return@coroutineScope
// For .initial allow the chatItems to be empty as well as chatModel.chatId to not match this chat because these values become set after .initial finishes
/** When [openAroundItemId] is provided, chatId can be different too */
if (((chatModel.chatId.value != chat.id || chat.chatItems.isEmpty()) && pagination !is ChatPagination.Initial && pagination !is ChatPagination.Last && openAroundItemId == null)
@@ -54,7 +54,7 @@ suspend fun processLoadedChat(
when (pagination) {
is ChatPagination.Initial -> {
val newSplits = if (chat.chatItems.isNotEmpty() && navInfo.afterTotal > 0) listOf(chat.chatItems.last().id) else emptyList()
- if (chatsCtx.secondaryContextFilter == null) {
+ if (chatsCtx.contentTag == null) {
// update main chats, not content tagged
withContext(Dispatchers.Main) {
val oldChat = chatModel.chatsContext.getChat(chat.id)
@@ -68,6 +68,7 @@ suspend fun processLoadedChat(
}
}
withContext(Dispatchers.Main) {
+ chatsCtx.chatItemStatuses.clear()
chatsCtx.chatItems.replaceAll(chat.chatItems)
chatModel.chatId.value = chat.id
splits.value = newSplits
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
index 2ca0dcc35d..6d7cdcdebe 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt
@@ -63,23 +63,12 @@ fun ChatView(
onComposed: suspend (chatId: String) -> Unit
) {
val showSearch = rememberSaveable { mutableStateOf(false) }
- val chat = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }
// They have their own iterator inside for a reason to prevent crash "Reading a state that was created after the snapshot..."
val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } }
- val activeChatInfo = remember { derivedStateOf {
- var chatInfo = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo
- if (
- chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
- && chatInfo is ChatInfo.Group
- ) {
- val scopeInfo = chatsCtx.secondaryContextFilter.groupScopeInfo
- chatInfo = chatInfo.copy(groupChatScope = scopeInfo)
- }
- chatInfo
- } }
+ val activeChatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo } }
val user = chatModel.currentUser.value
val chatInfo = activeChatInfo.value
- if (chat == null || chatInfo == null || user == null) {
+ if (chatInfo == null || user == null) {
LaunchedEffect(Unit) {
chatModel.chatId.value = null
ModalManager.end.closeModals()
@@ -110,7 +99,7 @@ fun ChatView(
.distinctUntilChanged()
.filterNotNull()
.collect { chatId ->
- if (chatsCtx.secondaryContextFilter == null) {
+ if (chatsCtx.contentTag == null) {
markUnreadChatAsRead(chatId)
}
showSearch.value = false
@@ -119,18 +108,6 @@ fun ChatView(
}
}
}
- if (chatsCtx.secondaryContextFilter == null && chatInfo is ChatInfo.Group && chatInfo.groupInfo.membership.memberPending) {
- LaunchedEffect(Unit) {
- val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null)
- val supportChatInfo = ChatInfo.Group(chatInfo.groupInfo, groupChatScope = scopeInfo)
- showMemberSupportChatView(
- chatModel.chatId,
- scrollToItemId,
- supportChatInfo,
- scopeInfo
- )
- }
- }
val view = LocalMultiplatformView()
val chatRh = remoteHostId.value
// We need to have real unreadCount value for displaying it inside top right button
@@ -140,7 +117,6 @@ fun ChatView(
chatsCtx.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatStats?.unreadCount ?: 0
}
}
- val reportsCount = reportsCount(chatInfo.id)
val clipboard = LocalClipboardManager.current
CompositionLocalProvider(
LocalAppBarHandler provides rememberAppBarHandler(chatInfo.id, keyboardCoversBar = false),
@@ -157,7 +133,7 @@ fun ChatView(
val sameText = searchText.value == value
// showSearch can be false with empty text when it was closed manually after clicking on message from search to load .around it
// (required on Android to have this check to prevent call to search with old text)
- val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.secondaryContextFilter == null
+ val emptyAndClosedSearch = searchText.value.isEmpty() && !showSearch.value && chatsCtx.contentTag == null
val c = chatModel.getChat(chatInfo.id)
if (sameText || emptyAndClosedSearch || c == null || chatModel.chatId.value != chatInfo.id) return@onSearchValueChanged
withBGApi {
@@ -191,7 +167,7 @@ fun ChatView(
)
}
ComposeView(
- rhId = remoteHostId.value, chatModel, chatsCtx, Chat(remoteHostId = chatRh, chatInfo = chatInfo, chatItems = emptyList()), composeState, attachmentOption,
+ chatModel, Chat(remoteHostId = chatRh, chatInfo = chatInfo, chatItems = emptyList()), composeState, attachmentOption,
showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } },
focusRequester = focusRequester
)
@@ -244,7 +220,6 @@ fun ChatView(
rh = chatRh,
fromChatType = chatInfo.chatType,
fromChatId = chatInfo.apiId,
- fromScope = chatInfo.groupChatScope(),
chatItemIds = chatItemIds
)
@@ -342,7 +317,7 @@ fun ChatView(
}
}
},
- showReports = {
+ showGroupReports = {
val info = activeChatInfo.value ?: return@ChatLayout
if (ModalManager.end.hasModalsOpen()) {
ModalManager.end.closeModals()
@@ -353,38 +328,6 @@ fun ChatView(
showGroupReportsView(staleChatId, scrollToItemId, info)
}
},
- showSupportChats = {
- val info = activeChatInfo.value ?: return@ChatLayout
- if (ModalManager.end.hasModalsOpen()) {
- ModalManager.end.closeModals()
- return@ChatLayout
- }
- hideKeyboard(view)
- scope.launch {
- if (info is ChatInfo.Group && info.groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
- ModalManager.end.showCustomModal { close ->
- MemberSupportView(
- chatRh,
- chat,
- info.groupInfo,
- scrollToItemId,
- close
- )
- }
- } else if (info is ChatInfo.Group) {
- val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null)
- val supportChatInfo = ChatInfo.Group(info.groupInfo, groupChatScope = scopeInfo)
- scope.launch {
- showMemberSupportChatView(
- chatModel.chatId,
- scrollToItemId = scrollToItemId,
- supportChatInfo,
- scopeInfo
- )
- }
- }
- }
- },
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
hideKeyboard(view)
groupMembersJob.cancel()
@@ -400,12 +343,12 @@ fun ChatView(
setGroupMembers(chatRh, groupInfo, chatModel)
if (!isActive) return@launch
- if (chatsCtx.secondaryContextFilter == null) {
+ if (chatsCtx.contentTag == null) {
ModalManager.end.closeModals()
}
ModalManager.end.showModalCloseable(true) { close ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
- GroupMemberInfoView(chatRh, groupInfo, mem, scrollToItemId, stats, code, chatModel, close, close)
+ GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close)
}
}
}
@@ -436,7 +379,6 @@ fun ChatView(
chatRh,
type = chatInfo.chatType,
id = chatInfo.apiId,
- scope = chatInfo.groupChatScope(),
itemIds = listOf(itemId),
mode = mode
)
@@ -455,13 +397,14 @@ fun ChatView(
if (deletedItem.isActiveReport) {
chatModel.chatsContext.decreaseGroupReportsCounter(chatRh, chatInfo.id)
}
- chatModel.chatsContext.updateChatInfo(chatRh, deleted.deletedChatItem.chatInfo)
}
withContext(Dispatchers.Main) {
- if (toChatItem != null) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
- } else {
- chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, deletedChatItem)
+ if (deletedChatItem.isReport) {
+ if (toChatItem != null) {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
+ } else {
+ chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, deletedChatItem)
+ }
}
}
}
@@ -569,7 +512,6 @@ fun ChatView(
rh = chatRh,
type = cInfo.chatType,
id = cInfo.apiId,
- scope = cInfo.groupChatScope(),
itemId = cItem.id,
add = add,
reaction = reaction
@@ -579,14 +521,16 @@ fun ChatView(
chatModel.chatsContext.updateChatItem(cInfo, updatedCI)
}
withContext(Dispatchers.Main) {
- chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, updatedCI)
+ if (cItem.isReport) {
+ chatModel.secondaryChatsContext.value?.updateChatItem(cInfo, updatedCI)
+ }
}
}
}
},
showItemDetails = { cInfo, cItem ->
suspend fun loadChatItemInfo(): ChatItemInfo? = coroutineScope {
- val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cInfo.groupChatScope(), cItem.id)
+ val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
if (ciInfo != null) {
if (chatInfo is ChatInfo.Group) {
setGroupMembers(chatRh, chatInfo.groupInfo, chatModel)
@@ -635,16 +579,12 @@ fun ChatView(
withContext(Dispatchers.Main) {
chatModel.chatsContext.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
ntfManager.cancelNotificationsForChat(chatInfo.id)
- val updatedChatInfo = chatModel.controller.apiChatItemsRead(
+ chatModel.controller.apiChatItemsRead(
chatRh,
chatInfo.chatType,
chatInfo.apiId,
- chatInfo.groupChatScope(),
itemsIds
)
- if (updatedChatInfo != null) {
- chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo)
- }
}
withContext(Dispatchers.Main) {
chatModel.secondaryChatsContext.value?.markChatItemsRead(chatRh, chatInfo.id, itemsIds)
@@ -659,8 +599,7 @@ fun ChatView(
chatModel.controller.apiChatRead(
chatRh,
chatInfo.chatType,
- chatInfo.apiId,
- chatInfo.groupChatScope()
+ chatInfo.apiId
)
}
withContext(Dispatchers.Main) {
@@ -743,8 +682,7 @@ fun ChatLayout(
selectedChatItems: MutableState?>,
back: () -> Unit,
info: () -> Unit,
- showReports: () -> Unit,
- showSupportChats: () -> Unit,
+ showGroupReports: () -> Unit,
showMemberInfo: (GroupInfo, GroupMember) -> Unit,
loadMessages: suspend (ChatId, ChatPagination, visibleItemIndexesNonReversed: () -> IntRange) -> Unit,
deleteMessage: (Long, CIDeleteMode) -> Unit,
@@ -807,7 +745,7 @@ fun ChatLayout(
sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
) {
val composeViewHeight = remember { mutableStateOf(0.dp) }
- Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.secondaryContextFilter == null)) {
+ Box(Modifier.fillMaxSize().chatViewBackgroundModifier(MaterialTheme.colors, MaterialTheme.wallpaper, LocalAppBarHandler.current?.backgroundGraphicsLayerSize, LocalAppBarHandler.current?.backgroundGraphicsLayer, drawWallpaper = chatsCtx.contentTag == null)) {
val remoteHostId = remember { remoteHostId }.value
val chatInfo = remember { chatInfo }.value
val oneHandUI = remember { appPrefs.oneHandUI.state }
@@ -835,7 +773,6 @@ fun ChatLayout(
.padding(bottom = composeViewHeight.value)
) {
GroupMentions(
- chatsCtx = chatsCtx,
rhId = remoteHostId,
composeState = composeState,
composeViewFocusRequester = composeViewFocusRequester,
@@ -895,76 +832,41 @@ fun ChatLayout(
}
}
val reportsCount = reportsCount(chatInfo?.id)
- val supportUnreadCount = supportUnreadCount(chatInfo?.id)
if (oneHandUI.value && chatBottomBar.value) {
- if (
- chatInfo is ChatInfo.Group
- && chatsCtx.secondaryContextFilter == null
- && (reportsCount > 0 || supportUnreadCount > 0)
- ) {
- SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = true, showReports, showSupportChats)
+ if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0) {
+ ReportedCountToolbar(reportsCount, withStatusBar = true, showGroupReports)
} else {
StatusBarBackground()
}
} else {
NavigationBarBackground(true, oneHandUI.value, noAlpha = true)
}
- when (chatsCtx.secondaryContextFilter) {
- is SecondaryContextFilter.GroupChatScopeContext -> {
- when (chatsCtx.secondaryContextFilter.groupScopeInfo) {
- is GroupChatScopeInfo.MemberSupport -> {
- if (oneHandUI.value) {
- StatusBarBackground()
- }
- Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
- Box {
- if (selectedChatItems.value == null) {
- MemberSupportChatAppBar(chatsCtx, chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_, { ModalManager.end.closeModal() }, onSearchValueChanged)
- } else {
- SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value)
- }
- }
- }
+ if (chatsCtx.contentTag == MsgContentTag.Report) {
+ if (oneHandUI.value) {
+ StatusBarBackground()
+ }
+ Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
+ Box {
+ if (selectedChatItems.value == null) {
+ GroupReportsAppBar(chatsCtx, { ModalManager.end.closeModal() }, onSearchValueChanged)
+ } else {
+ SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value)
}
}
}
- is SecondaryContextFilter.MsgContentTagContext -> {
- when (chatsCtx.secondaryContextFilter.contentTag) {
- MsgContentTag.Report -> {
- if (oneHandUI.value) {
- StatusBarBackground()
- }
- Column(if (oneHandUI.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
- Box {
- if (selectedChatItems.value == null) {
- GroupReportsAppBar(chatsCtx, { ModalManager.end.closeModal() }, onSearchValueChanged)
- } else {
- SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value)
- }
- }
+ } else {
+ Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
+ Box {
+ if (selectedChatItems.value == null) {
+ if (chatInfo != null) {
+ ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
}
+ } else {
+ SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
}
- else -> TODO()
}
- }
- null -> {
- Column(if (oneHandUI.value && chatBottomBar.value) Modifier.align(Alignment.BottomStart).imePadding() else Modifier) {
- Box {
- if (selectedChatItems.value == null) {
- if (chatInfo != null) {
- ChatInfoToolbar(chatsCtx, chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch)
- }
- } else {
- SelectedItemsCounterToolbar(selectedChatItems, !oneHandUI.value || !chatBottomBar.value)
- }
- }
- if (
- chatInfo is ChatInfo.Group
- && (reportsCount > 0 || supportUnreadCount > 0)
- && (!oneHandUI.value || !chatBottomBar.value)
- ) {
- SupportChatsCountToolbar(chatInfo, reportsCount, supportUnreadCount, withStatusBar = false, showReports, showSupportChats)
- }
+ if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate && chatsCtx.contentTag == null && reportsCount > 0 && (!oneHandUI.value || !chatBottomBar.value)) {
+ ReportedCountToolbar(reportsCount, withStatusBar = false, showGroupReports)
}
}
}
@@ -998,7 +900,7 @@ fun BoxScope.ChatInfoToolbar(
showSearch.value = false
}
}
- if (appPlatform.isAndroid && chatsCtx.secondaryContextFilter == null) {
+ if (appPlatform.isAndroid && chatsCtx.contentTag == null) {
BackHandler(onBack = onBackClicked)
}
val barButtons = arrayListOf<@Composable RowScope.() -> Unit>()
@@ -1195,79 +1097,33 @@ fun ChatInfoToolbarTitle(cInfo: ChatInfo, imageSize: Dp = 40.dp, iconColor: Colo
}
@Composable
-private fun SupportChatsCountToolbar(
- chatInfo: ChatInfo,
+private fun ReportedCountToolbar(
reportsCount: Int,
- supportUnreadCount: Int,
withStatusBar: Boolean,
- showReports: () -> Unit,
- showSupportChats: () -> Unit
+ showGroupReports: () -> Unit
) {
Box {
val statusBarPadding = if (withStatusBar) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() else 0.dp
Row(
Modifier
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceEvenly,
+ .fillMaxWidth()
+ .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding)
+ .background(MaterialTheme.colors.background)
+ .clickable(onClick = showGroupReports)
+ .padding(top = statusBarPadding),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
) {
- if (
- chatInfo is ChatInfo.Group
- && chatInfo.groupInfo.canModerate
- && reportsCount > 0
- ) {
- Row(
- Modifier
- .fillMaxWidth()
- .weight(1F)
- .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding)
- .background(MaterialTheme.colors.background)
- .clickable(onClick = showReports)
- .padding(top = statusBarPadding),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
- Spacer(Modifier.width(4.dp))
- Text(
- if (reportsCount == 1) {
- stringResource(MR.strings.group_reports_active_one)
- } else {
- stringResource(MR.strings.group_reports_active).format(reportsCount)
- },
- style = MaterialTheme.typography.button
- )
- }
- }
-
- if (supportUnreadCount > 0) {
- Row(
- Modifier
- .fillMaxWidth()
- .weight(1F)
- .height(AppBarHeight * fontSizeSqrtMultiplier + statusBarPadding)
- .background(MaterialTheme.colors.background)
- .clickable(onClick = showSupportChats)
- .padding(top = statusBarPadding),
- verticalAlignment = Alignment.CenterVertically,
- horizontalArrangement = Arrangement.Center
- ) {
- Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.primary)
- Spacer(Modifier.width(4.dp))
- Text(
- if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) {
- if (appPlatform.isAndroid)
- stringResource(MR.strings.group_new_support_chats_short).format(supportUnreadCount)
- else if (supportUnreadCount == 1)
- stringResource(MR.strings.group_new_support_chat_one)
- else
- stringResource(MR.strings.group_new_support_chats).format(supportUnreadCount)
- } else {
- stringResource(MR.strings.group_new_support_messages).format(supportUnreadCount)
- },
- style = MaterialTheme.typography.button
- )
- }
- }
+ Icon(painterResource(MR.images.ic_flag), null, Modifier.size(22.dp), tint = MaterialTheme.colors.error)
+ Spacer(Modifier.width(4.dp))
+ Text(
+ if (reportsCount == 1) {
+ stringResource(MR.strings.group_reports_active_one)
+ } else {
+ stringResource(MR.strings.group_reports_active).format(reportsCount)
+ },
+ style = MaterialTheme.typography.button
+ )
}
Divider(Modifier.align(Alignment.BottomStart))
}
@@ -1342,281 +1198,298 @@ fun BoxScope.ChatItemsList(
val searchValueIsNotBlank = remember { derivedStateOf { searchValue.value.isNotBlank() } }
val revealedItems = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf(setOf()) }
// not using reversedChatItems inside to prevent possible derivedState bug in Compose when one derived state access can cause crash asking another derived state
- val mergedItems = remember {
- derivedStateOf {
- MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState)
- }
- }
- val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } }
- val reportsCount = reportsCount(chatInfo.id)
- val supportUnreadCount = supportUnreadCount(chatInfo.id)
- val topPaddingToContent = topPaddingToContent(
- chatView = chatsCtx.secondaryContextFilter == null,
- additionalTopBar = chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)
- )
- val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
- val numberOfBottomAppBars = numberOfBottomAppBars()
-
- /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of
- * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears
- * */
- val maxHeightForList = rememberUpdatedState(
- with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() }
- )
- val resetListState = remember { mutableStateOf(false) }
- remember(chatModel.openAroundItemId.value) {
- if (chatModel.openAroundItemId.value != null) {
- closeSearch()
- resetListState.value = !resetListState.value
- }
- }
- val highlightedItems = remember { mutableStateOf(setOf()) }
- val hoveredItemId = remember { mutableStateOf(null as Long?) }
- val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) {
- val openAroundItemId = chatModel.openAroundItemId.value
- val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() }
- val reportsState = reportsListState
- if (openAroundItemId != null) {
- highlightedItems.value += openAroundItemId
- chatModel.openAroundItemId.value = null
- }
- hoveredItemId.value = null
- if (reportsState != null) {
- reportsListState = null
- reportsState
- } else if (index <= 0 || !searchValueIsEmpty.value) {
- LazyListState(0, 0)
- } else {
- LazyListState(index + 1, -maxHeightForList.value)
- }
- })
- SaveReportsStateOnDispose(chatsCtx, listState)
- val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
- val loadingMoreItems = remember { mutableStateOf(false) }
- val animatedScrollingInProgress = remember { mutableStateOf(false) }
- val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() }
- LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) {
- if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT)
- ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect)
- }
- PreloadItems(chatsCtx, chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination ->
- if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false
- loadingMoreItems.value = true
- withContext(NonCancellable) {
- try {
- loadMessages(chatId, pagination) {
- visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value)
- }
- } finally {
- loadingMoreItems.value = false
+ if (chatsCtx != null) {
+ val mergedItems = remember {
+ derivedStateOf {
+ MergedItems.create(chatsCtx.chatItems.value.asReversed(), unreadCount, revealedItems.value, chatsCtx.chatState)
}
}
- true
- }
- val remoteHostIdUpdated = rememberUpdatedState(remoteHostId)
- val chatInfoUpdated = rememberUpdatedState(chatInfo)
- val scope = rememberCoroutineScope()
- val scrollToItem: (Long) -> Unit = remember {
- scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages)
- }
- val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem, scrollToItemId) }
- if (chatsCtx.secondaryContextFilter == null) {
- LaunchedEffect(Unit) {
- snapshotFlow { scrollToItemId.value }.filterNotNull().collect {
- if (appPlatform.isAndroid) {
- ModalManager.end.closeModals()
- }
- scrollToItem(it)
- scrollToItemId.value = null
+ val reversedChatItems = remember { derivedStateOf { chatsCtx.chatItems.value.asReversed() } }
+ val reportsCount = reportsCount(chatInfo.id)
+ val topPaddingToContent = topPaddingToContent(
+ chatView = chatsCtx.contentTag == null,
+ additionalTopBar = chatsCtx.contentTag == null && reportsCount > 0
+ )
+ val topPaddingToContentPx = rememberUpdatedState(with(LocalDensity.current) { topPaddingToContent.roundToPx() })
+ val numberOfBottomAppBars = numberOfBottomAppBars()
+
+ /** determines height based on window info and static height of two AppBars. It's needed because in the first graphic frame height of
+ * [composeViewHeight] is unknown, but we need to set scroll position for unread messages already so it will be correct before the first frame appears
+ * */
+ val maxHeightForList = rememberUpdatedState(
+ with(LocalDensity.current) { LocalWindowHeight().roundToPx() - topPaddingToContentPx.value - (AppBarHeight * fontSizeSqrtMultiplier * numberOfBottomAppBars).roundToPx() }
+ )
+ val resetListState = remember { mutableStateOf(false) }
+ remember(chatModel.openAroundItemId.value) {
+ if (chatModel.openAroundItemId.value != null) {
+ closeSearch()
+ resetListState.value = !resetListState.value
}
}
- }
- SmallScrollOnNewMessage(listState, reversedChatItems)
- val finishedInitialComposition = remember { mutableStateOf(false) }
- NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed)
-
- DisposableEffectOnGone(
- whenGone = {
- VideoPlayerHolder.releaseAll()
+ val highlightedItems = remember { mutableStateOf(setOf()) }
+ val hoveredItemId = remember { mutableStateOf(null as Long?) }
+ val listState = rememberUpdatedState(rememberSaveable(chatInfo.id, searchValueIsEmpty.value, resetListState.value, saver = LazyListState.Saver) {
+ val openAroundItemId = chatModel.openAroundItemId.value
+ val index = mergedItems.value.indexInParentItems[openAroundItemId] ?: mergedItems.value.items.indexOfLast { it.hasUnread() }
+ val reportsState = reportsListState
+ if (openAroundItemId != null) {
+ highlightedItems.value += openAroundItemId
+ chatModel.openAroundItemId.value = null
+ }
+ hoveredItemId.value = null
+ if (reportsState != null) {
+ reportsListState = null
+ reportsState
+ } else if (index <= 0 || !searchValueIsEmpty.value) {
+ LazyListState(0, 0)
+ } else {
+ LazyListState(index + 1, -maxHeightForList.value)
+ }
+ })
+ SaveReportsStateOnDispose(chatsCtx, listState)
+ val maxHeight = remember { derivedStateOf { listState.value.layoutInfo.viewportEndOffset - topPaddingToContentPx.value } }
+ val loadingMoreItems = remember { mutableStateOf(false) }
+ val animatedScrollingInProgress = remember { mutableStateOf(false) }
+ val ignoreLoadingRequests = remember(remoteHostId) { mutableSetOf() }
+ LaunchedEffect(chatInfo.id, searchValueIsEmpty.value) {
+ if (searchValueIsEmpty.value && reversedChatItems.value.size < ChatPagination.INITIAL_COUNT)
+ ignoreLoadingRequests.add(reversedChatItems.value.lastOrNull()?.id ?: return@LaunchedEffect)
}
- )
+ PreloadItems(chatsCtx, chatInfo.id, if (searchValueIsEmpty.value) ignoreLoadingRequests else mutableSetOf(), loadingMoreItems, resetListState, mergedItems, listState, ChatPagination.UNTIL_PRELOAD_COUNT) { chatId, pagination ->
+ if (loadingMoreItems.value || chatId != chatModel.chatId.value) return@PreloadItems false
+ loadingMoreItems.value = true
+ withContext(NonCancellable) {
+ try {
+ loadMessages(chatId, pagination) {
+ visibleItemIndexesNonReversed(mergedItems, reversedChatItems.value.size, listState.value)
+ }
+ } finally {
+ loadingMoreItems.value = false
+ }
+ }
+ true
+ }
+ val remoteHostIdUpdated = rememberUpdatedState(remoteHostId)
+ val chatInfoUpdated = rememberUpdatedState(chatInfo)
+ val scope = rememberCoroutineScope()
+ val scrollToItem: (Long) -> Unit = remember {
+ // In group reports just set the itemId to scroll to so the main ChatView will handle scrolling
+ if (chatsCtx.contentTag == MsgContentTag.Report) return@remember { scrollToItemId.value = it }
+ scrollToItem(searchValue, loadingMoreItems, animatedScrollingInProgress, highlightedItems, chatInfoUpdated, maxHeight, scope, reversedChatItems, mergedItems, listState, loadMessages)
+ }
+ val scrollToQuotedItemFromItem: (Long) -> Unit = remember { findQuotedItemFromItem(chatsCtx, remoteHostIdUpdated, chatInfoUpdated, scope, scrollToItem) }
+ if (chatsCtx.contentTag == null) {
+ LaunchedEffect(Unit) {
+ snapshotFlow { scrollToItemId.value }.filterNotNull().collect {
+ if (appPlatform.isAndroid) {
+ ModalManager.end.closeModals()
+ }
+ scrollToItem(it)
+ scrollToItemId.value = null
+ }
+ }
+ }
+ SmallScrollOnNewMessage(listState, reversedChatItems)
+ val finishedInitialComposition = remember { mutableStateOf(false) }
+ NotifyChatListOnFinishingComposition(finishedInitialComposition, chatInfo, revealedItems, listState, onComposed)
- @Composable
- fun ChatViewListItem(
- itemAtZeroIndexInWholeList: Boolean,
- range: State,
- showAvatar: Boolean,
- cItem: ChatItem,
- itemSeparation: ItemSeparation,
- previousItemSeparationLargeGap: Boolean,
- revealed: State,
- reveal: (Boolean) -> Unit
- ) {
- val itemScope = rememberCoroutineScope()
- CompositionLocalProvider(
- // Makes horizontal and vertical scrolling to coexist nicely.
- // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view
- LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop()
+ DisposableEffectOnGone(
+ whenGone = {
+ VideoPlayerHolder.releaseAll()
+ }
+ )
+
+ @Composable
+ fun ChatViewListItem(
+ itemAtZeroIndexInWholeList: Boolean,
+ range: State,
+ showAvatar: Boolean,
+ cItem: ChatItem,
+ itemSeparation: ItemSeparation,
+ previousItemSeparationLargeGap: Boolean,
+ revealed: State,
+ reveal: (Boolean) -> Unit
) {
- val provider = {
- providerForGallery(reversedChatItems.value.asReversed(), cItem.id) { indexInReversed ->
- itemScope.launch {
- listState.value.scrollToItem(
- min(reversedChatItems.value.lastIndex, indexInReversed + 1),
- -maxHeight.value
- )
- }
- }
- }
-
- @Composable
- fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) {
- tryOrShowError("${cItem.id}ChatItem", error = {
- CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
- }) {
- val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } }
- LaunchedEffect(Unit) {
- snapshotFlow { highlighted.value }
- .distinctUntilChanged()
- .filter { it }
- .collect {
- delay(500)
- highlightedItems.value = setOf()
- }
- }
- ChatItemView(chatsCtx, remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToItemId = scrollToItemId, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
- }
- }
-
- @Composable
- fun ChatItemView(cItem: ChatItem, range: State, itemSeparation: ItemSeparation, previousItemSeparationLargeGap: Boolean) {
- val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
- if (it == DismissValue.DismissedToStart) {
+ val itemScope = rememberCoroutineScope()
+ CompositionLocalProvider(
+ // Makes horizontal and vertical scrolling to coexist nicely.
+ // With default touchSlop when you scroll LazyColumn, you can unintentionally open reply view
+ LocalViewConfiguration provides LocalViewConfiguration.current.bigTouchSlop()
+ ) {
+ val provider = {
+ providerForGallery(reversedChatItems.value.asReversed(), cItem.id) { indexInReversed ->
itemScope.launch {
- if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local && !cItem.isReport) {
- if (composeState.value.editing) {
- composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
- } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
- composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
+ listState.value.scrollToItem(
+ min(reversedChatItems.value.lastIndex, indexInReversed + 1),
+ -maxHeight.value
+ )
+ }
+ }
+ }
+
+ @Composable
+ fun ChatItemViewShortHand(cItem: ChatItem, itemSeparation: ItemSeparation, range: State, fillMaxWidth: Boolean = true) {
+ tryOrShowError("${cItem.id}ChatItem", error = {
+ CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
+ }) {
+ val highlighted = remember { derivedStateOf { highlightedItems.value.contains(cItem.id) } }
+ LaunchedEffect(Unit) {
+ snapshotFlow { highlighted.value }
+ .distinctUntilChanged()
+ .filter { it }
+ .collect {
+ delay(500)
+ highlightedItems.value = setOf()
+ }
+ }
+ ChatItemView(chatsCtx, remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, highlighted = highlighted, hoveredItemId = hoveredItemId, range = range, searchIsNotBlank = searchValueIsNotBlank, fillMaxWidth = fillMaxWidth, selectedChatItems = selectedChatItems, selectChatItem = { selectUnselectChatItem(true, cItem, revealed, selectedChatItems, reversedChatItems) }, deleteMessage = deleteMessage, deleteMessages = deleteMessages, archiveReports = archiveReports, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, scrollToQuotedItemFromItem = scrollToQuotedItemFromItem, setReaction = setReaction, showItemDetails = showItemDetails, reveal = reveal, showMemberInfo = showMemberInfo, showChatInfo = showChatInfo, developerTools = developerTools, showViaProxy = showViaProxy, itemSeparation = itemSeparation, showTimestamp = itemSeparation.timestamp)
+ }
+ }
+
+ @Composable
+ fun ChatItemView(cItem: ChatItem, range: State, itemSeparation: ItemSeparation, previousItemSeparationLargeGap: Boolean) {
+ val dismissState = rememberDismissState(initialValue = DismissValue.Default) {
+ if (it == DismissValue.DismissedToStart) {
+ itemScope.launch {
+ if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local && !cItem.isReport) {
+ if (composeState.value.editing) {
+ composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews)
+ } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
+ composeState.value = composeState.value.copy(contextItem = ComposeContextItem.QuotedItem(cItem))
+ }
}
}
}
+ false
}
- false
- }
- val swipeableModifier = SwipeToDismissModifier(
- state = dismissState,
- directions = setOf(DismissDirection.EndToStart),
- swipeDistance = with(LocalDensity.current) { 30.dp.toPx() },
- )
- val sent = cItem.chatDir.sent
+ val swipeableModifier = SwipeToDismissModifier(
+ state = dismissState,
+ directions = setOf(DismissDirection.EndToStart),
+ swipeDistance = with(LocalDensity.current) { 30.dp.toPx() },
+ )
+ val sent = cItem.chatDir.sent
- @Composable
- fun ChatItemBox(modifier: Modifier = Modifier, content: @Composable () -> Unit = { }) {
- Box(
- modifier = modifier.padding(
- bottom = if (itemSeparation.largeGap) {
- if (itemAtZeroIndexInWholeList) {
- 8.dp
- } else {
- 4.dp
- }
- } else 1.dp, top = if (previousItemSeparationLargeGap) 4.dp else 1.dp
- ),
- contentAlignment = Alignment.CenterStart
- ) {
- content()
+ @Composable
+ fun ChatItemBox(modifier: Modifier = Modifier, content: @Composable () -> Unit = { }) {
+ Box(
+ modifier = modifier.padding(
+ bottom = if (itemSeparation.largeGap) {
+ if (itemAtZeroIndexInWholeList) {
+ 8.dp
+ } else {
+ 4.dp
+ }
+ } else 1.dp, top = if (previousItemSeparationLargeGap) 4.dp else 1.dp
+ ),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ content()
+ }
}
- }
- @Composable
- fun adjustTailPaddingOffset(originalPadding: Dp, start: Boolean): Dp {
- val chatItemTail = remember { appPreferences.chatItemTail.state }
- val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true)
- val tailRendered = style is ShapeStyle.Bubble && style.tailVisible
+ @Composable
+ fun adjustTailPaddingOffset(originalPadding: Dp, start: Boolean): Dp {
+ val chatItemTail = remember { appPreferences.chatItemTail.state }
+ val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true)
+ val tailRendered = style is ShapeStyle.Bubble && style.tailVisible
- return originalPadding + (if (tailRendered) 0.dp else if (start) msgTailWidthDp * 2 else msgTailWidthDp)
- }
-
- Box {
- val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null
- val selectionVisible = selectedChatItems.value != null && cItem.canBeDeletedForSelf
- val selectionOffset by animateDpAsState(if (selectionVisible && !sent) 4.dp + 22.dp * fontSizeMultiplier else 0.dp)
- val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() }
- if (chatInfo is ChatInfo.Group) {
- if (cItem.chatDir is CIDirection.GroupRcv) {
- if (showAvatar) {
- Column(
- Modifier
- .padding(top = 8.dp)
- .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false))
- .fillMaxWidth()
- .then(swipeableModifier),
- verticalArrangement = Arrangement.spacedBy(4.dp),
- horizontalAlignment = Alignment.Start
- ) {
- @Composable
- fun MemberNameAndRole(range: State) {
- Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) {
- val member = cItem.chatDir.groupMember
- val rangeValue = range.value
- val (prevMember, memCount) =
- if (rangeValue != null) {
- chatModel.getPrevHiddenMember(member, rangeValue, reversedChatItems.value)
- } else {
- null to 1
- }
- Text(
- memberNames(member, prevMember, memCount),
- Modifier
- .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF)
- .weight(1f, false),
- fontSize = 13.5.sp,
- color = MaterialTheme.colors.secondary,
- overflow = TextOverflow.Ellipsis,
- maxLines = 1
- )
- if (memCount == 1 && member.memberRole > GroupMemberRole.Member) {
- val chatItemTail = remember { appPreferences.chatItemTail.state }
- val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true)
- val tailRendered = style is ShapeStyle.Bubble && style.tailVisible
+ return originalPadding + (if (tailRendered) 0.dp else if (start) msgTailWidthDp * 2 else msgTailWidthDp)
+ }
+ Box {
+ val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null
+ val selectionVisible = selectedChatItems.value != null && cItem.canBeDeletedForSelf
+ val selectionOffset by animateDpAsState(if (selectionVisible && !sent) 4.dp + 22.dp * fontSizeMultiplier else 0.dp)
+ val swipeableOrSelectionModifier = (if (selectionVisible) Modifier else swipeableModifier).graphicsLayer { translationX = selectionOffset.toPx() }
+ if (chatInfo is ChatInfo.Group) {
+ if (cItem.chatDir is CIDirection.GroupRcv) {
+ if (showAvatar) {
+ Column(
+ Modifier
+ .padding(top = 8.dp)
+ .padding(start = 8.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false))
+ .fillMaxWidth()
+ .then(swipeableModifier),
+ verticalArrangement = Arrangement.spacedBy(4.dp),
+ horizontalAlignment = Alignment.Start
+ ) {
+ @Composable
+ fun MemberNameAndRole(range: State) {
+ Row(Modifier.padding(bottom = 2.dp).graphicsLayer { translationX = selectionOffset.toPx() }, horizontalArrangement = Arrangement.SpaceBetween) {
+ val member = cItem.chatDir.groupMember
+ val rangeValue = range.value
+ val (prevMember, memCount) =
+ if (rangeValue != null) {
+ chatModel.getPrevHiddenMember(member, rangeValue, reversedChatItems.value)
+ } else {
+ null to 1
+ }
Text(
- member.memberRole.text,
- Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp),
+ memberNames(member, prevMember, memCount),
+ Modifier
+ .padding(start = (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + DEFAULT_PADDING_HALF)
+ .weight(1f, false),
fontSize = 13.5.sp,
- fontWeight = FontWeight.Medium,
color = MaterialTheme.colors.secondary,
+ overflow = TextOverflow.Ellipsis,
maxLines = 1
)
- }
- }
- }
+ if (memCount == 1 && member.memberRole > GroupMemberRole.Member) {
+ val chatItemTail = remember { appPreferences.chatItemTail.state }
+ val style = shapeStyle(cItem, chatItemTail.value, itemSeparation.largeGap, true)
+ val tailRendered = style is ShapeStyle.Bubble && style.tailVisible
- @Composable
- fun Item() {
- ChatItemBox(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID)) {
- androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
- SelectedListItem(Modifier, cItem.id, selectedChatItems)
- }
- Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) {
- val member = cItem.chatDir.groupMember
- Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) {
- MemberImage(member)
- }
- Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) {
- ChatItemViewShortHand(cItem, itemSeparation, range, false)
+ Text(
+ member.memberRole.text,
+ Modifier.padding(start = DEFAULT_PADDING_HALF * 1.5f, end = DEFAULT_PADDING_HALF + if (tailRendered) msgTailWidthDp else 0.dp),
+ fontSize = 13.5.sp,
+ fontWeight = FontWeight.Medium,
+ color = MaterialTheme.colors.secondary,
+ maxLines = 1
+ )
}
}
}
- }
- if (cItem.content.showMemberName) {
- DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) {
- MemberNameAndRole(range)
+
+ @Composable
+ fun Item() {
+ ChatItemBox(Modifier.layoutId(CHAT_BUBBLE_LAYOUT_ID)) {
+ androidx.compose.animation.AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
+ SelectedListItem(Modifier, cItem.id, selectedChatItems)
+ }
+ Row(Modifier.graphicsLayer { translationX = selectionOffset.toPx() }) {
+ val member = cItem.chatDir.groupMember
+ Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) {
+ MemberImage(member)
+ }
+ Box(modifier = Modifier.padding(top = 2.dp, start = 4.dp).chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)) {
+ ChatItemViewShortHand(cItem, itemSeparation, range, false)
+ }
+ }
+ }
+ }
+ if (cItem.content.showMemberName) {
+ DependentLayout(Modifier, CHAT_BUBBLE_LAYOUT_ID) {
+ MemberNameAndRole(range)
+ Item()
+ }
+ } else {
Item()
}
- } else {
- Item()
+ }
+ } else {
+ ChatItemBox {
+ AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
+ SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems)
+ }
+ Row(
+ Modifier
+ .padding(start = 8.dp + (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + 4.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false))
+ .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)
+ .then(swipeableOrSelectionModifier)
+ ) {
+ ChatItemViewShortHand(cItem, itemSeparation, range)
+ }
}
}
} else {
@@ -1624,152 +1497,138 @@ fun BoxScope.ChatItemsList(
AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems)
}
- Row(
+ Box(
Modifier
- .padding(start = 8.dp + (MEMBER_IMAGE_SIZE * fontSizeSqrtMultiplier) + 4.dp, end = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(66.dp, start = false))
+ .padding(start = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(104.dp, start = true), end = 12.dp)
.chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)
- .then(swipeableOrSelectionModifier)
+ .then(if (selectionVisible) Modifier else swipeableModifier)
) {
ChatItemViewShortHand(cItem, itemSeparation, range)
}
}
}
- } else {
+ } else { // direct message
ChatItemBox {
AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems)
}
+
Box(
- Modifier
- .padding(start = if (voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(104.dp, start = true), end = 12.dp)
+ Modifier.padding(
+ start = if (sent && !voiceWithTransparentBack) adjustTailPaddingOffset(76.dp, start = true) else 12.dp,
+ end = if (sent || voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(76.dp, start = false),
+ )
.chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)
- .then(if (selectionVisible) Modifier else swipeableModifier)
+ .then(if (!selectionVisible || !sent) swipeableOrSelectionModifier else Modifier)
) {
ChatItemViewShortHand(cItem, itemSeparation, range)
}
}
}
- } else { // direct message
- ChatItemBox {
- AnimatedVisibility(selectionVisible, enter = fadeIn(), exit = fadeOut()) {
- SelectedListItem(Modifier.padding(start = 8.dp), cItem.id, selectedChatItems)
- }
-
- Box(
- Modifier.padding(
- start = if (sent && !voiceWithTransparentBack) adjustTailPaddingOffset(76.dp, start = true) else 12.dp,
- end = if (sent || voiceWithTransparentBack) 12.dp else adjustTailPaddingOffset(76.dp, start = false),
- )
- .chatItemOffset(cItem, itemSeparation.largeGap, revealed = revealed.value)
- .then(if (!selectionVisible || !sent) swipeableOrSelectionModifier else Modifier)
- ) {
- ChatItemViewShortHand(cItem, itemSeparation, range)
- }
+ if (selectionVisible) {
+ Box(Modifier.matchParentSize().clickable {
+ val checked = selectedChatItems.value?.contains(cItem.id) == true
+ selectUnselectChatItem(select = !checked, cItem, revealed, selectedChatItems, reversedChatItems)
+ })
}
}
- if (selectionVisible) {
- Box(Modifier.matchParentSize().clickable {
- val checked = selectedChatItems.value?.contains(cItem.id) == true
- selectUnselectChatItem(select = !checked, cItem, revealed, selectedChatItems, reversedChatItems)
- })
+ }
+ if (itemSeparation.date != null) {
+ DateSeparator(itemSeparation.date)
+ }
+ ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap)
+ }
+ }
+ LazyColumnWithScrollBar(
+ Modifier.align(Alignment.BottomCenter),
+ state = listState.value,
+ contentPadding = PaddingValues(
+ top = topPaddingToContent,
+ bottom = composeViewHeight.value
+ ),
+ reverseLayout = true,
+ additionalBarOffset = composeViewHeight,
+ additionalTopBar = rememberUpdatedState(chatsCtx.contentTag == null && reportsCount > 0),
+ chatBottomBar = remember { appPrefs.chatBottomBar.state }
+ ) {
+ val mergedItemsValue = mergedItems.value
+ itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged ->
+ val isLastItem = index == mergedItemsValue.items.lastIndex
+ val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null
+ val listItem = merged.newest()
+ val item = listItem.item
+ val range = if (merged is MergedItem.Grouped) {
+ merged.rangeInReversed.value
+ } else {
+ null
+ }
+ val showAvatar = shouldShowAvatar(item, listItem.nextItem)
+ val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } }
+ val itemSeparation: ItemSeparation
+ val prevItemSeparationLargeGap: Boolean
+ if (merged is MergedItem.Single || isRevealed.value) {
+ val prev = listItem.prevItem
+ itemSeparation = getItemSeparation(item, prev)
+ val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem
+ prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item)
+ } else {
+ itemSeparation = getItemSeparation(item, null)
+ prevItemSeparationLargeGap = false
+ }
+ ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) {
+ if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems)
+ }
+
+ if (last != null) {
+ // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items
+ DateSeparator(last.meta.itemTs)
+ }
+ if (item.isRcvNew) {
+ val itemIds = when (merged) {
+ is MergedItem.Single -> listOf(merged.item.item.id)
+ is MergedItem.Grouped -> merged.items.map { it.item.id }
+ }
+ MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead)
+ }
+ }
+ }
+ FloatingButtons(
+ chatsCtx,
+ reversedChatItems,
+ chatInfoUpdated,
+ topPaddingToContent,
+ topPaddingToContentPx,
+ loadingMoreItems,
+ loadingTopItems,
+ loadingBottomItems,
+ animatedScrollingInProgress,
+ mergedItems,
+ unreadCount,
+ maxHeight,
+ composeViewHeight,
+ searchValue,
+ markChatRead,
+ listState,
+ loadMessages
+ )
+ FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState)
+
+ LaunchedEffect(Unit) {
+ snapshotFlow { listState.value.isScrollInProgress }
+ .collect {
+ chatViewScrollState.value = it
+ }
+ }
+ LaunchedEffect(Unit) {
+ snapshotFlow { listState.value.isScrollInProgress }
+ .filter { !it }
+ .collect {
+ if (animatedScrollingInProgress.value) {
+ animatedScrollingInProgress.value = false
}
}
- }
- if (itemSeparation.date != null) {
- DateSeparator(itemSeparation.date)
- }
- ChatItemView(cItem, range, itemSeparation, previousItemSeparationLargeGap)
}
}
- LazyColumnWithScrollBar(
- Modifier.align(Alignment.BottomCenter),
- state = listState.value,
- contentPadding = PaddingValues(
- top = topPaddingToContent,
- bottom = composeViewHeight.value
- ),
- reverseLayout = true,
- additionalBarOffset = composeViewHeight,
- additionalTopBar = rememberUpdatedState(chatsCtx.secondaryContextFilter == null && (reportsCount > 0 || supportUnreadCount > 0)),
- chatBottomBar = remember { appPrefs.chatBottomBar.state }
- ) {
- val mergedItemsValue = mergedItems.value
- itemsIndexed(mergedItemsValue.items, key = { _, merged -> keyForItem(merged.newest().item) }) { index, merged ->
- val isLastItem = index == mergedItemsValue.items.lastIndex
- val last = if (isLastItem) reversedChatItems.value.lastOrNull() else null
- val listItem = merged.newest()
- val item = listItem.item
- val range = if (merged is MergedItem.Grouped) {
- merged.rangeInReversed.value
- } else {
- null
- }
- val showAvatar = shouldShowAvatar(item, listItem.nextItem)
- val isRevealed = remember { derivedStateOf { revealedItems.value.contains(item.id) } }
- val itemSeparation: ItemSeparation
- val prevItemSeparationLargeGap: Boolean
- if (merged is MergedItem.Single || isRevealed.value) {
- val prev = listItem.prevItem
- itemSeparation = getItemSeparation(item, prev)
- val nextForGap = if ((item.mergeCategory != null && item.mergeCategory == prev?.mergeCategory) || isLastItem) null else listItem.nextItem
- prevItemSeparationLargeGap = if (nextForGap == null) false else getItemSeparationLargeGap(nextForGap, item)
- } else {
- itemSeparation = getItemSeparation(item, null)
- prevItemSeparationLargeGap = false
- }
- ChatViewListItem(index == 0, rememberUpdatedState(range), showAvatar, item, itemSeparation, prevItemSeparationLargeGap, isRevealed) {
- if (merged is MergedItem.Grouped) merged.reveal(it, revealedItems)
- }
-
- if (last != null) {
- // no using separate item(){} block in order to have total number of items in LazyColumn match number of merged items
- DateSeparator(last.meta.itemTs)
- }
- if (item.isRcvNew) {
- val itemIds = when (merged) {
- is MergedItem.Single -> listOf(merged.item.item.id)
- is MergedItem.Grouped -> merged.items.map { it.item.id }
- }
- MarkItemsReadAfterDelay(keyForItem(item), itemIds, finishedInitialComposition, chatInfo.id, listState, markItemsRead)
- }
- }
- }
- FloatingButtons(
- chatsCtx,
- reversedChatItems,
- chatInfoUpdated,
- topPaddingToContent,
- topPaddingToContentPx,
- loadingMoreItems,
- loadingTopItems,
- loadingBottomItems,
- animatedScrollingInProgress,
- mergedItems,
- unreadCount,
- maxHeight,
- composeViewHeight,
- searchValue,
- markChatRead,
- listState,
- loadMessages
- )
- FloatingDate(Modifier.padding(top = 10.dp + topPaddingToContent).align(Alignment.TopCenter), topPaddingToContentPx, mergedItems, listState)
-
- LaunchedEffect(Unit) {
- snapshotFlow { listState.value.isScrollInProgress }
- .collect {
- chatViewScrollState.value = it
- }
- }
- LaunchedEffect(Unit) {
- snapshotFlow { listState.value.isScrollInProgress }
- .filter { !it }
- .collect {
- if (animatedScrollingInProgress.value) {
- animatedScrollingInProgress.value = false
- }
- }
- }
}
private suspend fun loadLastItems(chatsCtx: ChatModel.ChatsContext, chatId: State, listState: State, loadItems: State Boolean>) {
@@ -2375,15 +2234,6 @@ fun reportsCount(staleChatId: String?): Int {
}
}
-@Composable
-fun supportUnreadCount(staleChatId: String?): Int {
- return if (staleChatId?.startsWith("#") != true) {
- 0
- } else {
- remember(staleChatId) { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId } } }.value?.supportUnreadCount ?: 0
- }
-}
-
private fun reversedChatItemsStatic(chatsCtx: ChatModel.ChatsContext): List =
chatsCtx.chatItems.value.asReversed()
@@ -2465,8 +2315,7 @@ private fun findQuotedItemFromItem(
rhId: State,
chatInfo: State,
scope: CoroutineScope,
- scrollToItem: (Long) -> Unit,
- scrollToItemId: MutableState
+ scrollToItem: (Long) -> Unit
): (Long) -> Unit = { itemId: Long ->
scope.launch(Dispatchers.Default) {
val item = apiLoadSingleMessage(chatsCtx, rhId.value, chatInfo.value.chatType, chatInfo.value.apiId, itemId)
@@ -2478,11 +2327,7 @@ private fun findQuotedItemFromItem(
chatModel.secondaryChatsContext.value?.updateChatItem(chatInfo.value, item)
}
if (item.quotedItem?.itemId != null) {
- if (item.isReport && chatsCtx.secondaryContextFilter != null) {
- scrollToItemId.value = item.quotedItem.itemId
- } else {
- scrollToItem(item.quotedItem.itemId)
- }
+ scrollToItem(item.quotedItem.itemId)
} else {
showQuotedItemDoesNotExistAlert()
}
@@ -2654,7 +2499,6 @@ private fun deleteMessages(chatRh: Long?, chatInfo: ChatInfo, itemIds: List
- chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo)
- }
}
withContext(Dispatchers.Main) {
for (di in deleted) {
- val toChatItem = di.toChatItem?.chatItem
- if (toChatItem != null) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
- } else {
- chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
+ if (di.deletedChatItem.chatItem.isReport) {
+ val toChatItem = di.toChatItem?.chatItem
+ if (toChatItem != null) {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
+ } else {
+ chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
+ }
}
}
}
@@ -2716,17 +2559,16 @@ private fun archiveReports(chatRh: Long?, chatInfo: ChatInfo, itemIds: List
- chatModel.chatsContext.updateChatInfo(chatRh, updatedChatInfo)
- }
}
withContext(Dispatchers.Main) {
for (di in deleted) {
- val toChatItem = di.toChatItem?.chatItem
- if (toChatItem != null) {
- chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
- } else {
- chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
+ if (di.deletedChatItem.chatItem.isReport) {
+ val toChatItem = di.toChatItem?.chatItem
+ if (toChatItem != null) {
+ chatModel.secondaryChatsContext.value?.upsertChatItem(chatRh, chatInfo, toChatItem)
+ } else {
+ chatModel.secondaryChatsContext.value?.removeChatItem(chatRh, chatInfo, di.deletedChatItem.chatItem)
+ }
}
}
}
@@ -3078,7 +2920,7 @@ fun PreviewChatLayout() {
val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) }
val searchValue = remember { mutableStateOf("") }
ChatLayout(
- chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null),
+ chatsCtx = ChatModel.ChatsContext(contentTag = null),
remoteHostId = remember { mutableStateOf(null) },
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
unreadCount = unreadCount,
@@ -3093,8 +2935,7 @@ fun PreviewChatLayout() {
selectedChatItems = remember { mutableStateOf(setOf()) },
back = {},
info = {},
- showReports = {},
- showSupportChats = {},
+ showGroupReports = {},
showMemberInfo = { _, _ -> },
loadMessages = { _, _, _ -> },
deleteMessage = { _, _ -> },
@@ -3157,7 +2998,7 @@ fun PreviewGroupChatLayout() {
val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) }
val searchValue = remember { mutableStateOf("") }
ChatLayout(
- chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null),
+ chatsCtx = ChatModel.ChatsContext(contentTag = null),
remoteHostId = remember { mutableStateOf(null) },
chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) },
unreadCount = unreadCount,
@@ -3172,8 +3013,7 @@ fun PreviewGroupChatLayout() {
selectedChatItems = remember { mutableStateOf(setOf()) },
back = {},
info = {},
- showReports = {},
- showSupportChats = {},
+ showGroupReports = {},
showMemberInfo = { _, _ -> },
loadMessages = { _, _, _ -> },
deleteMessage = { _, _ -> },
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt
deleted file mode 100644
index 3c3f99ad94..0000000000
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextPendingMemberActionsView.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-package chat.simplex.common.views.chat
-
-import SectionItemView
-import androidx.compose.foundation.background
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.unit.dp
-import chat.simplex.common.model.*
-import chat.simplex.common.platform.chatModel
-import chat.simplex.common.views.chat.group.removeMember
-import chat.simplex.common.views.chat.group.removeMemberDialog
-import chat.simplex.common.views.helpers.*
-import chat.simplex.res.MR
-import dev.icerock.moko.resources.compose.stringResource
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-@Composable
-fun ComposeContextPendingMemberActionsView(
- rhId: Long?,
- groupInfo: GroupInfo,
- member: GroupMember
-) {
- Column(
- Modifier
- .height(60.dp)
- .background(MaterialTheme.colors.surface)
- ) {
- Divider()
-
- Row(
- Modifier
- .fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceEvenly,
- ) {
- Column(
- Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .weight(1F)
- .clickable {
- rejectMemberDialog(rhId, member, chatModel, close = { ModalManager.end.closeModal() })
- },
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text(stringResource(MR.strings.reject_pending_member_button), color = Color.Red)
- }
-
- Column(
- Modifier
- .fillMaxWidth()
- .fillMaxHeight()
- .weight(1F)
- .clickable {
- acceptMemberDialog(rhId, groupInfo, member, close = { ModalManager.end.closeModal() })
- },
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Text(stringResource(MR.strings.accept_pending_member_button), color = MaterialTheme.colors.primary)
- }
- }
- }
-}
-
-fun rejectMemberDialog(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
- AlertManager.shared.showAlertDialog(
- title = generalGetString(MR.strings.reject_pending_member_alert_title),
- confirmText = generalGetString(MR.strings.reject_pending_member_button),
- onConfirm = {
- removeMember(rhId, member, chatModel, close)
- },
- destructive = true,
- )
-}
-
-fun acceptMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, close: (() -> Unit)? = null) {
- AlertManager.shared.showAlertDialogButtonsColumn(
- title = generalGetString(MR.strings.accept_pending_member_alert_title),
- text = generalGetString(MR.strings.accept_pending_member_alert_question),
- buttons = {
- Column {
- // Accept as member
- SectionItemView({
- AlertManager.shared.hideAlert()
- acceptMember(rhId, groupInfo, member, GroupMemberRole.Member, close)
- }) {
- Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_member), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- // Accept as observer
- SectionItemView({
- AlertManager.shared.hideAlert()
- acceptMember(rhId, groupInfo, member, GroupMemberRole.Observer, close)
- }) {
- Text(generalGetString(MR.strings.accept_pending_member_alert_confirmation_as_observer), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- // Cancel
- SectionItemView({
- AlertManager.shared.hideAlert()
- }) {
- Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
- }
- }
- }
- )
-}
-
-private fun acceptMember(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, role: GroupMemberRole, close: (() -> Unit)?) {
- withBGApi {
- val r = chatModel.controller.apiAcceptMember(rhId, groupInfo.groupId, member.groupMemberId, role)
- if (r != null) {
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second)
- chatModel.chatsContext.updateGroup(rhId, r.first)
- }
- }
- close?.invoke()
- }
-}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
index ca6279fd88..894bcf3b37 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt
@@ -333,9 +333,7 @@ suspend fun MutableState.processPickedMedia(uris: List, text:
@Composable
fun ComposeView(
- rhId: Long?,
chatModel: ChatModel,
- chatsCtx: ChatModel.ChatsContext,
chat: Chat,
composeState: MutableState,
attachmentOption: MutableState,
@@ -468,7 +466,6 @@ fun ComposeView(
rh = chat.remoteHostId,
type = cInfo.chatType,
id = cInfo.apiId,
- scope = cInfo.groupChatScope(),
live = live,
ttl = ttl,
composedMessages = listOf(ComposedMessage(file, quoted, mc, mentions))
@@ -476,7 +473,7 @@ fun ComposeView(
if (!chatItems.isNullOrEmpty()) {
chatItems.forEach { aChatItem ->
withContext(Dispatchers.Main) {
- chatsCtx.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
+ chatModel.chatsContext.addChatItem(chat.remoteHostId, cInfo, aChatItem.chatItem)
}
}
return chatItems.first().chatItem
@@ -501,17 +498,15 @@ fun ComposeView(
rh = rhId,
toChatType = chat.chatInfo.chatType,
toChatId = chat.chatInfo.apiId,
- toScope = chat.chatInfo.groupChatScope(),
fromChatType = fromChatInfo.chatType,
fromChatId = fromChatInfo.apiId,
- fromScope = fromChatInfo.groupChatScope(),
itemIds = forwardedItem.map { it.id },
ttl = ttl
)
withContext(Dispatchers.Main) {
chatItems?.forEach { chatItem ->
- chatsCtx.addChatItem(rhId, chat.chatInfo, chatItem)
+ chatModel.chatsContext.addChatItem(rhId, chat.chatInfo, chatItem)
}
}
@@ -568,21 +563,16 @@ fun ComposeView(
}
}
- fun showReportsInSupportChatAlert() {
- AlertManager.shared.showAlertDialog(
- title = generalGetString(MR.strings.report_sent_alert_title),
- text = generalGetString(MR.strings.report_sent_alert_msg_view_in_support_chat),
- confirmText = generalGetString(MR.strings.ok),
- dismissText = generalGetString(MR.strings.dont_show_again),
- onDismiss = {
- chatModel.controller.appPrefs.showReportsInSupportChatAlert.set(false)
- },
- )
- }
-
suspend fun sendReport(reportReason: ReportReason, chatItemId: Long): List? {
val cItems = chatModel.controller.apiReportMessage(chat.remoteHostId, chat.chatInfo.apiId, chatItemId, reportReason, msgText)
- if (chatModel.controller.appPrefs.showReportsInSupportChatAlert.get()) showReportsInSupportChatAlert()
+ if (cItems != null) {
+ withContext(Dispatchers.Main) {
+ cItems.forEach { chatItem ->
+ chatModel.chatsContext.addChatItem(chat.remoteHostId, chat.chatInfo, chatItem.chatItem)
+ }
+ }
+ }
+
return cItems?.map { it.chatItem }
}
@@ -591,7 +581,7 @@ fun ComposeView(
val contact = chatModel.controller.apiSendMemberContactInvitation(chat.remoteHostId, chat.chatInfo.apiId, mc)
if (contact != null) {
withContext(Dispatchers.Main) {
- chatsCtx.updateContact(chat.remoteHostId, contact)
+ chatModel.chatsContext.updateContact(chat.remoteHostId, contact)
}
}
}
@@ -604,14 +594,13 @@ fun ComposeView(
rh = chat.remoteHostId,
type = cInfo.chatType,
id = cInfo.apiId,
- scope = cInfo.groupChatScope(),
itemId = ei.meta.itemId,
updatedMessage = UpdatedMessage(updateMsgContent(oldMsgContent), cs.memberMentions),
live = live
)
if (updatedItem != null) {
withContext(Dispatchers.Main) {
- chatsCtx.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
+ chatModel.chatsContext.upsertChatItem(chat.remoteHostId, cInfo, updatedItem.chatItem)
}
}
return updatedItem?.chatItem
@@ -902,7 +891,7 @@ fun ComposeView(
fun editPrevMessage() {
if (composeState.value.contextItem != ComposeContextItem.NoContextItem || composeState.value.preview != ComposePreview.NoPreview) return
- val lastEditable = chatsCtx.chatItems.value.findLast { it.meta.editable }
+ val lastEditable = chatModel.chatsContext.chatItems.value.findLast { it.meta.editable }
if (lastEditable != null) {
composeState.value = ComposeState(editingItem = lastEditable, useLinkPreviews = useLinkPreviews)
}
@@ -1015,21 +1004,6 @@ fun ComposeView(
val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv)
Column {
- if (
- chat.chatInfo is ChatInfo.Group
- && chatsCtx.secondaryContextFilter is SecondaryContextFilter.GroupChatScopeContext
- && chatsCtx.secondaryContextFilter.groupScopeInfo is GroupChatScopeInfo.MemberSupport
- && chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_ != null
- && chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_.memberPending
- && composeState.value.contextItem == ComposeContextItem.NoContextItem
- && composeState.value.preview == ComposePreview.NoPreview
- ) {
- ComposeContextPendingMemberActionsView(
- rhId = rhId,
- groupInfo = chat.chatInfo.groupInfo,
- member = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_
- )
- }
if (nextSendGrpInv.value) {
ComposeContextInvitingContactMemberView()
}
@@ -1037,8 +1011,8 @@ fun ComposeView(
if (ctx is ComposeContextItem.ReportedItem) {
ReportReasonView(ctx.reason)
}
- val simplexLinkProhibited = chatsCtx.secondaryContextFilter == null && hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
- val fileProhibited = chatsCtx.secondaryContextFilter == null && composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
+ val simplexLinkProhibited = hasSimplexLink.value && !chat.groupFeatureEnabled(GroupFeature.SimplexLinks)
+ val fileProhibited = composeState.value.attachmentPreview && !chat.groupFeatureEnabled(GroupFeature.Files)
val voiceProhibited = composeState.value.preview is ComposePreview.VoicePreview && !chat.chatInfo.featureEnabled(ChatFeature.Voice)
if (composeState.value.preview !is ComposePreview.VoicePreview || composeState.value.editing) {
if (simplexLinkProhibited) {
@@ -1067,10 +1041,7 @@ fun ComposeView(
Surface(color = MaterialTheme.colors.background, contentColor = MaterialTheme.colors.onBackground) {
Divider()
Row(Modifier.padding(end = 8.dp), verticalAlignment = Alignment.Bottom) {
- val isGroupAndProhibitedFiles =
- chatsCtx.secondaryContextFilter == null
- && chat.chatInfo is ChatInfo.Group
- && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
+ val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership)
val attachmentClicked = if (isGroupAndProhibitedFiles) {
{
AlertManager.shared.showAlertMsg(
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt
index ac722783a3..b9538bc691 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SelectableChatItemToolbars.kt
@@ -77,7 +77,7 @@ fun SelectedItemsButtonsToolbar(
val forwardCountProhibited = remember { mutableStateOf(false) }
Box {
// It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty
- ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
+ ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
Row(
Modifier
.matchParentSize()
@@ -101,21 +101,21 @@ fun SelectedItemsButtonsToolbar(
)
}
- IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
+ IconButton({ moderateItems() }, Modifier.alpha(if (canModerate.value) 1f else 0f), enabled = moderateEnabled.value && !deleteCountProhibited.value) {
Icon(
painterResource(MR.images.ic_flag),
null,
Modifier.size(22.dp),
- tint = if (!moderateEnabled.value || deleteCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.error
+ tint = if (!moderateEnabled.value || deleteCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.error
)
}
- IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value && chatsCtx.secondaryContextFilter == null) {
+ IconButton({ forwardItems() }, enabled = forwardEnabled.value && !forwardCountProhibited.value) {
Icon(
painterResource(MR.images.ic_forward),
null,
Modifier.size(22.dp),
- tint = if (!forwardEnabled.value || forwardCountProhibited.value || chatsCtx.secondaryContextFilter != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
+ tint = if (!forwardEnabled.value || forwardCountProhibited.value) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt
index 827af085ea..10694d13bf 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt
@@ -55,16 +55,6 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
GroupPreferencesView(chatModel, rhId, groupInfo.id, close)
}
},
- openMemberAdmission = {
- ModalManager.end.showCustomModal { close ->
- MemberAdmissionView(
- chat.simplex.common.platform.chatModel,
- rhId,
- groupInfo.id,
- close
- )
- }
- },
inviteMembers = {
allowModifyMembers = false
withLongRunningApi(slow = 120_000) {
@@ -103,9 +93,8 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List {
.asSequence()
.map { it.chatInfo }
.filterIsInstance()
- .filter { it.sendMsgEnabled }
.map { it.contact }
- .filter { c -> !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
+ .filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.anyNameContains(s)
}
.sortedBy { it.displayName.lowercase() }
.toList()
@@ -121,7 +110,6 @@ fun AddGroupMembersLayout(
allowModifyMembers: Boolean,
searchText: MutableState,
openPreferences: () -> Unit,
- openMemberAdmission: () -> Unit,
inviteMembers: () -> Unit,
clearSelection: () -> Unit,
addContact: (Long) -> Unit,
@@ -156,7 +144,7 @@ fun AddGroupMembersLayout(
horizontalArrangement = Arrangement.Center
) {
ChatInfoToolbarTitle(
- ChatInfo.Group(groupInfo, groupChatScope = null),
+ ChatInfo.Group(groupInfo),
imageSize = 60.dp,
iconColor = if (isInDarkTheme()) GroupDark else SettingsSecondaryLight
)
@@ -177,9 +165,6 @@ fun AddGroupMembersLayout(
} else {
SectionView {
if (creatingGroup) {
- SectionItemView(openMemberAdmission) {
- Text(stringResource(MR.strings.set_member_admission))
- }
SectionItemView(openPreferences) {
Text(stringResource(MR.strings.set_group_preferences))
}
@@ -391,7 +376,6 @@ fun PreviewAddGroupMembersLayout() {
allowModifyMembers = true,
searchText = remember { mutableStateOf(TextFieldValue("")) },
openPreferences = {},
- openMemberAdmission = {},
inviteMembers = {},
clearSelection = {},
addContact = {},
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
index db6eff562e..22956738e7 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
@@ -74,9 +74,6 @@ fun ModalData.GroupChatInfoView(
val chatItemTTL = remember(groupInfo.id) { mutableStateOf(if (groupInfo.chatItemTTL != null) ChatItemTTL.fromSeconds(groupInfo.chatItemTTL) else null) }
val deletingItems = rememberSaveable(groupInfo.id) { mutableStateOf(false) }
val scope = rememberCoroutineScope()
- val activeSortedMembers = remember { chatModel.groupMembers }.value
- .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
- .sortedByDescending { it.memberRole }
GroupChatInfoLayout(
chat,
@@ -98,7 +95,9 @@ fun ModalData.GroupChatInfoView(
setChatTTLAlert(chatsCtx, chat.remoteHostId, chat.chatInfo, chatItemTTL, previousChatTTL, deletingItems)
},
- activeSortedMembers = activeSortedMembers,
+ activeSortedMembers = remember { chatModel.groupMembers }.value
+ .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
+ .sortedByDescending { it.memberRole },
developerTools,
onLocalAliasChanged = { setGroupAlias(chat, it, chatModel) },
groupLink,
@@ -127,7 +126,7 @@ fun ModalData.GroupChatInfoView(
}
ModalManager.end.showModalCloseable(true) { closeCurrent ->
remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem ->
- GroupMemberInfoView(rhId, groupInfo, mem, scrollToItemId, stats, code, chatModel, closeCurrent) {
+ GroupMemberInfoView(rhId, groupInfo, mem, stats, code, chatModel, closeCurrent) {
closeCurrent()
close()
}
@@ -141,17 +140,6 @@ fun ModalData.GroupChatInfoView(
addOrEditWelcomeMessage = {
ModalManager.end.showCustomModal { close -> GroupWelcomeView(chatModel, rhId, groupInfo, close) }
},
- openMemberSupport = {
- ModalManager.end.showCustomModal { close ->
- MemberSupportView(
- rhId,
- chat,
- groupInfo,
- scrollToItemId,
- close
- )
- }
- },
openPreferences = {
ModalManager.end.showCustomModal { close ->
GroupPreferencesView(
@@ -329,40 +317,6 @@ fun AddGroupMembersButton(
)
}
-@Composable
-fun UserSupportChatButton(
- chat: Chat,
- groupInfo: GroupInfo,
- scrollToItemId: MutableState
-) {
- val scope = rememberCoroutineScope()
-
- SettingsActionItemWithContent(
- painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
- stringResource(MR.strings.button_support_chat),
- click = {
- val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = null)
- val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo)
- scope.launch {
- showMemberSupportChatView(
- chatModel.chatId,
- scrollToItemId = scrollToItemId,
- supportChatInfo,
- scopeInfo
- )
- }
- },
- iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary),
- ) {
- if (chat.supportUnreadCount > 0) {
- UnreadBadge(
- text = unreadCountStr(chat.supportUnreadCount),
- backgroundColor = MaterialTheme.colors.primary
- )
- }
- }
-}
-
@Composable
fun ModalData.GroupChatInfoLayout(
chat: Chat,
@@ -383,7 +337,6 @@ fun ModalData.GroupChatInfoLayout(
showMemberInfo: (GroupMember) -> Unit,
editGroupProfile: () -> Unit,
addOrEditWelcomeMessage: () -> Unit,
- openMemberSupport: () -> Unit,
openPreferences: () -> Unit,
deleteGroup: () -> Unit,
clearChat: () -> Unit,
@@ -469,40 +422,6 @@ fun ModalData.GroupChatInfoLayout(
SectionSpacer()
- var anyTopSectionRowShow = false
- SectionView {
- if (groupInfo.canAddMembers && groupInfo.businessChat == null) {
- anyTopSectionRowShow = true
- if (groupLink == null) {
- CreateGroupLinkButton(manageGroupLink)
- } else {
- GroupLinkButton(manageGroupLink)
- }
- }
- if (groupInfo.businessChat == null && groupInfo.membership.memberRole >= GroupMemberRole.Moderator) {
- anyTopSectionRowShow = true
- MemberSupportButton(chat, openMemberSupport)
- }
- if (groupInfo.canModerate) {
- anyTopSectionRowShow = true
- GroupReportsButton(chat) {
- scope.launch {
- showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo)
- }
- }
- }
- if (
- groupInfo.membership.memberActive &&
- (groupInfo.membership.memberRole < GroupMemberRole.Moderator || groupInfo.membership.supportChat != null)
- ) {
- anyTopSectionRowShow = true
- UserSupportChatButton(chat, groupInfo, scrollToItemId)
- }
- }
- if (anyTopSectionRowShow) {
- SectionDividerSpaced(maxBottomPadding = false)
- }
-
SectionView {
if (groupInfo.isOwner && groupInfo.businessChat?.chatType == null) {
EditGroupProfileButton(editGroupProfile)
@@ -512,17 +431,19 @@ fun ModalData.GroupChatInfoLayout(
}
val prefsTitleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences
GroupPreferencesButton(prefsTitleId, openPreferences)
- }
- val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs
- SectionTextFooter(stringResource(footerId))
- SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
-
- SectionView {
+ if (groupInfo.canModerate) {
+ GroupReportsButton {
+ scope.launch {
+ showGroupReportsView(chatModel.chatId, scrollToItemId, chat.chatInfo)
+ }
+ }
+ }
if (activeSortedMembers.filter { it.memberCurrent }.size <= SMALL_GROUPS_RCPS_MEM_LIMIT) {
SendReceiptsOption(currentUser, sendReceipts, setSendReceipts)
} else {
SendReceiptsOptionDisabled()
}
+
WallpaperButton {
ModalManager.end.showModal {
val chat = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chat.id } } }
@@ -532,13 +453,23 @@ fun ModalData.GroupChatInfoLayout(
}
}
}
- ChatTTLOption(chatItemTTL, setChatItemTTL, deletingItems)
- SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
}
+ val footerId = if (groupInfo.businessChat == null) MR.strings.only_group_owners_can_change_prefs else MR.strings.only_chat_owners_can_change_prefs
+ SectionTextFooter(stringResource(footerId))
+ SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
+
+ ChatTTLSection(chatItemTTL, setChatItemTTL, deletingItems)
SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = true)
SectionView(title = String.format(generalGetString(MR.strings.group_info_section_title_num_members), activeSortedMembers.count() + 1)) {
if (groupInfo.canAddMembers) {
+ if (groupInfo.businessChat == null) {
+ if (groupLink == null) {
+ CreateGroupLinkButton(manageGroupLink)
+ } else {
+ GroupLinkButton(manageGroupLink)
+ }
+ }
val onAddMembersClick = if (chat.chatInfo.incognito) ::cantInviteIncognitoAlert else addMembers
val tint = if (chat.chatInfo.incognito) MaterialTheme.colors.secondary else MaterialTheme.colors.primary
val addMembersTitleId = when (groupInfo.businessChat?.chatType) {
@@ -550,7 +481,7 @@ fun ModalData.GroupChatInfoLayout(
}
if (activeSortedMembers.size > 8) {
SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) {
- MemberListSearchRowView(searchText)
+ SearchRowView(searchText)
}
}
SectionItemView(minHeight = 54.dp, padding = PaddingValues(horizontal = DEFAULT_PADDING)) {
@@ -596,7 +527,7 @@ fun ModalData.GroupChatInfoLayout(
val titleId = if (groupInfo.businessChat == null) MR.strings.button_delete_group else MR.strings.button_delete_chat
DeleteGroupButton(titleId, deleteGroup)
}
- if (groupInfo.membership.memberCurrentOrPending) {
+ if (groupInfo.membership.memberCurrent) {
val titleId = if (groupInfo.businessChat == null) MR.strings.button_leave_group else MR.strings.button_leave_chat
LeaveGroupButton(titleId, leaveGroup)
}
@@ -700,14 +631,17 @@ private fun SelectedItemsCounterToolbarSetter(
}
@Composable
-fun ChatTTLOption(chatItemTTL: State, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State) {
+fun ChatTTLSection(chatItemTTL: State, setChatItemTTL: (ChatItemTTL?) -> Unit, deletingItems: State) {
Box {
- TtlOptions(
- chatItemTTL,
- enabled = remember { derivedStateOf { !deletingItems.value } },
- onSelected = setChatItemTTL,
- default = chatModel.chatItemTTL
- )
+ SectionView {
+ TtlOptions(
+ chatItemTTL,
+ enabled = remember { derivedStateOf { !deletingItems.value } },
+ onSelected = setChatItemTTL,
+ default = chatModel.chatItemTTL
+ )
+ SectionTextFooter(stringResource(MR.strings.chat_ttl_options_footer))
+ }
if (deletingItems.value) {
Box(Modifier.matchParentSize()) {
ProgressIndicator()
@@ -749,23 +683,6 @@ private fun GroupChatInfoHeader(cInfo: ChatInfo, groupInfo: GroupInfo) {
}
}
-@Composable
-private fun MemberSupportButton(chat: Chat, onClick: () -> Unit) {
- SettingsActionItemWithContent(
- painterResource(if (chat.supportUnreadCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
- stringResource(MR.strings.member_support),
- click = onClick,
- iconColor = (if (chat.supportUnreadCount > 0) MaterialTheme.colors.primary else MaterialTheme.colors.secondary)
- ) {
- if (chat.supportUnreadCount > 0) {
- UnreadBadge(
- text = unreadCountStr(chat.supportUnreadCount),
- backgroundColor = MaterialTheme.colors.primary
- )
- }
- }
-}
-
@Composable
private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit) {
SettingsActionItem(
@@ -776,20 +693,12 @@ private fun GroupPreferencesButton(titleId: StringResource, onClick: () -> Unit)
}
@Composable
-private fun GroupReportsButton(chat: Chat, onClick: () -> Unit) {
- SettingsActionItemWithContent(
- painterResource(if (chat.chatStats.reportsCount > 0) MR.images.ic_flag_filled else MR.images.ic_flag),
+private fun GroupReportsButton(onClick: () -> Unit) {
+ SettingsActionItem(
+ painterResource(MR.images.ic_flag),
stringResource(MR.strings.group_reports_member_reports),
- click = onClick,
- iconColor = (if (chat.chatStats.reportsCount > 0) Color.Red else MaterialTheme.colors.secondary)
- ) {
- if (chat.chatStats.reportsCount > 0) {
- UnreadBadge(
- text = unreadCountStr(chat.chatStats.reportsCount),
- backgroundColor = Color.Red
- )
- }
- }
+ click = onClick
+ )
}
@Composable
@@ -911,7 +820,7 @@ fun MemberRow(member: GroupMember, user: Boolean = false, infoPage: Boolean = tr
}
@Composable
-fun MemberVerifiedShield() {
+private fun MemberVerifiedShield() {
Icon(painterResource(MR.images.ic_verified_user), null, Modifier.padding(end = 3.dp).size(16.dp), tint = MaterialTheme.colors.secondary)
}
@@ -1032,7 +941,7 @@ private fun DeleteGroupButton(titleId: StringResource, onClick: () -> Unit) {
}
@Composable
-fun MemberListSearchRowView(
+private fun SearchRowView(
searchText: MutableState = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) }
) {
Box(Modifier.width(36.dp), contentAlignment = Alignment.Center) {
@@ -1055,18 +964,16 @@ private fun setGroupAlias(chat: Chat, localAlias: String, chatModel: ChatModel)
fun removeMembers(rhId: Long?, groupInfo: GroupInfo, memberIds: List, onSuccess: () -> Unit = {}) {
withBGApi {
- val r = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds)
- if (r != null) {
- val (updatedGroupInfo, updatedMembers) = r
+ val updatedMembers = chatModel.controller.apiRemoveMembers(rhId, groupInfo.groupId, memberIds)
+ if (updatedMembers != null) {
withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo)
updatedMembers.forEach { updatedMember ->
- chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, updatedMember)
+ chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, updatedMember)
}
}
withContext(Dispatchers.Main) {
updatedMembers.forEach { updatedMember ->
- chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, updatedGroupInfo, updatedMember)
+ chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, updatedMember)
}
}
onSuccess()
@@ -1109,18 +1016,7 @@ fun PreviewGroupChatInfoLayout() {
selectedItems = remember { mutableStateOf(null) },
appBar = remember { mutableStateOf(null) },
scrollToItemId = remember { mutableStateOf(null) },
- addMembers = {},
- showMemberInfo = {},
- editGroupProfile = {},
- addOrEditWelcomeMessage = {},
- openMemberSupport = {},
- openPreferences = {},
- deleteGroup = {},
- clearChat = {},
- leaveGroup = {},
- manageGroupLink = {},
- onSearchClicked = {},
- deletingItems = remember { mutableStateOf(true) }
+ addMembers = {}, showMemberInfo = {}, editGroupProfile = {}, addOrEditWelcomeMessage = {}, openPreferences = {}, deleteGroup = {}, clearChat = {}, leaveGroup = {}, manageGroupLink = {}, onSearchClicked = {}, deletingItems = remember { mutableStateOf(true) }
)
}
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
index e56bc36562..285c96165c 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt
@@ -45,7 +45,6 @@ fun GroupMemberInfoView(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
- scrollToItemId: MutableState,
connectionStats: ConnectionStats?,
connectionCode: String?,
chatModel: ChatModel,
@@ -80,7 +79,6 @@ fun GroupMemberInfoView(
rhId = rhId,
groupInfo,
member,
- scrollToItemId,
connStats,
newRole,
developerTools,
@@ -245,34 +243,32 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c
text = generalGetString(messageId),
confirmText = generalGetString(MR.strings.remove_member_confirmation),
onConfirm = {
- removeMember(rhId, member, chatModel, close)
+ withBGApi {
+ val removedMembers = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId))
+ if (removedMembers != null) {
+ withContext(Dispatchers.Main) {
+ removedMembers.forEach { removedMember ->
+ chatModel.chatsContext.upsertGroupMember(rhId, groupInfo, removedMember)
+ }
+ }
+ withContext(Dispatchers.Main) {
+ removedMembers.forEach { removedMember ->
+ chatModel.secondaryChatsContext.value?.upsertGroupMember(rhId, groupInfo, removedMember)
+ }
+ }
+ }
+ close?.invoke()
+ }
},
destructive = true,
)
}
-fun removeMember(rhId: Long?, member: GroupMember, chatModel: ChatModel, close: (() -> Unit)? = null) {
- withBGApi {
- val r = chatModel.controller.apiRemoveMembers(rhId, member.groupId, listOf(member.groupMemberId))
- if (r != null) {
- val (updatedGroupInfo, removedMembers) = r
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateGroup(rhId, updatedGroupInfo)
- removedMembers.forEach { removedMember ->
- chatModel.chatsContext.upsertGroupMember(rhId, updatedGroupInfo, removedMember)
- }
- }
- }
- close?.invoke()
- }
-}
-
@Composable
fun GroupMemberInfoLayout(
rhId: Long?,
groupInfo: GroupInfo,
member: GroupMember,
- scrollToItemId: MutableState,
connStats: MutableState,
newRole: MutableState,
developerTools: Boolean,
@@ -303,29 +299,6 @@ fun GroupMemberInfoLayout(
}
}
- @Composable
- fun SupportChatButton() {
- val scope = rememberCoroutineScope()
-
- SettingsActionItem(
- painterResource(MR.images.ic_flag),
- stringResource(MR.strings.button_support_chat_member),
- click = {
- val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = member)
- val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo)
- scope.launch {
- showMemberSupportChatView(
- chatModel.chatId,
- scrollToItemId = scrollToItemId,
- supportChatInfo,
- scopeInfo
- )
- }
- },
- iconColor = MaterialTheme.colors.secondary,
- )
- }
-
@Composable
fun ModeratorDestructiveSection() {
val canBlockForAll = member.canBlockForAll(groupInfo)
@@ -440,12 +413,6 @@ fun GroupMemberInfoLayout(
if (member.memberActive) {
SectionView {
- if (
- groupInfo.membership.memberRole >= GroupMemberRole.Moderator &&
- (member.memberRole < GroupMemberRole.Moderator || member.supportChat != null)
- ) {
- SupportChatButton()
- }
if (connectionCode != null) {
VerifyCodeButton(member.verified, verifyClicked)
}
@@ -911,7 +878,6 @@ fun PreviewGroupMemberInfoLayout() {
rhId = null,
groupInfo = GroupInfo.sampleData,
member = GroupMember.sampleData,
- scrollToItemId = remember { mutableStateOf(null) },
connStats = remember { mutableStateOf(null) },
newRole = remember { mutableStateOf(GroupMemberRole.Member) },
developerTools = false,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt
index 62f1a4337c..2c4d4b16a8 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMembersToolbar.kt
@@ -44,7 +44,7 @@ fun SelectedItemsMembersToolbar(
) {
// It's hard to measure exact height of ComposeView with different fontSizes. Better to depend on actual ComposeView, even empty
Box(Modifier.alpha(0f)) {
- ComposeView(rhId = null, chatModel = chatModel, chatModel.chatsContext, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
+ ComposeView(chatModel = chatModel, Chat.sampleData, remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, remember { mutableStateOf(null) }, {}, remember { FocusRequester() })
}
Row(
Modifier
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt
index aa737a02d3..91f4f5173c 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMentions.kt
@@ -35,7 +35,6 @@ private val MAX_PICKER_HEIGHT = (PICKER_ROW_SIZE * 4) + (MEMBER_ROW_AVATAR_SIZE
@Composable
fun GroupMentions(
- chatsCtx: ChatModel.ChatsContext,
rhId: Long?,
composeState: MutableState,
composeViewFocusRequester: FocusRequester?,
@@ -49,31 +48,12 @@ fun GroupMentions(
val mentionName = remember { mutableStateOf("") }
val mentionRange = remember { mutableStateOf(null) }
val mentionMemberId = remember { mutableStateOf(null) }
-
- fun contextMemberFilter(member: GroupMember): Boolean =
- when (chatsCtx.secondaryContextFilter) {
- null -> true
- is SecondaryContextFilter.GroupChatScopeContext ->
- when (chatsCtx.secondaryContextFilter.groupScopeInfo) {
- is GroupChatScopeInfo.MemberSupport -> {
- val scopeMember = chatsCtx.secondaryContextFilter.groupScopeInfo.groupMember_
- if (scopeMember != null) {
- member.memberRole >= GroupMemberRole.Moderator || member.groupMemberId == scopeMember.groupMemberId
- } else {
- member.memberRole >= GroupMemberRole.Moderator
- }
- }
- }
- is SecondaryContextFilter.MsgContentTagContext -> false
- }
-
val filteredMembers = remember {
derivedStateOf {
val members = chatModel.groupMembers.value
.filter {
val status = it.memberStatus
status != GroupMemberStatus.MemLeft && status != GroupMemberStatus.MemRemoved && status != GroupMemberStatus.MemInvited
- && contextMemberFilter(it)
}
.sortedByDescending { it.memberRole }
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt
index b8db5969a1..12c5b65769 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt
@@ -17,9 +17,7 @@ import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon
import chat.simplex.common.model.*
import chat.simplex.common.platform.ColumnWithScrollBar
import chat.simplex.common.platform.chatModel
-import chat.simplex.common.views.usersettings.SettingsActionItem
import chat.simplex.res.MR
-import dev.icerock.moko.resources.compose.painterResource
import kotlinx.coroutines.*
private val featureRoles: List> = listOf(
@@ -73,16 +71,6 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () ->
preferences = currentPreferences
},
savePrefs = ::savePrefs,
- openMemberAdmission = {
- ModalManager.end.showCustomModal { close ->
- MemberAdmissionView(
- chatModel,
- rhId,
- chatId,
- close
- )
- }
- }
)
}
}
@@ -95,15 +83,10 @@ private fun GroupPreferencesLayout(
applyPrefs: (FullGroupPreferences) -> Unit,
reset: () -> Unit,
savePrefs: () -> Unit,
- openMemberAdmission: () -> Unit,
) {
ColumnWithScrollBar {
val titleId = if (groupInfo.businessChat == null) MR.strings.group_preferences else MR.strings.chat_preferences
AppBarTitle(stringResource(titleId))
- if (groupInfo.businessChat == null) {
- MemberAdmissionButton(openMemberAdmission)
- SectionDividerSpaced(maxBottomPadding = false)
- }
val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) }
val onTTLUpdated = { ttl: Int? ->
applyPrefs(preferences.copy(timedMessages = preferences.timedMessages.copy(ttl = ttl)))
@@ -173,15 +156,6 @@ private fun GroupPreferencesLayout(
}
}
-@Composable
-private fun MemberAdmissionButton(onClick: () -> Unit) {
- SettingsActionItem(
- painterResource(MR.images.ic_toggle_on),
- stringResource(MR.strings.member_admission),
- click = onClick
- )
-}
-
@Composable
private fun FeatureSection(
feature: GroupFeature,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt
index 2cc2402c0a..1eeeb99c93 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupReportsView.kt
@@ -15,15 +15,7 @@ import dev.icerock.moko.resources.compose.stringResource
import kotlinx.coroutines.flow.*
@Composable
-private fun GroupReportsView(
- reportsChatsCtx: ChatModel.ChatsContext,
- staleChatId: State,
- scrollToItemId: MutableState,
- close: () -> Unit
-) {
- KeyChangeEffect(chatModel.chatId.value) {
- close()
- }
+private fun GroupReportsView(reportsChatsCtx: ChatModel.ChatsContext, staleChatId: State, scrollToItemId: MutableState) {
ChatView(reportsChatsCtx, staleChatId, scrollToItemId, onComposed = {})
}
@@ -61,7 +53,7 @@ fun GroupReportsAppBar(
}
@Composable
-fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
+private fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
LaunchedEffect(Unit) {
snapshotFlow { chatModel.chatId.value }
.distinctUntilChanged()
@@ -77,13 +69,13 @@ fun ItemsReload(chatsCtx: ChatModel.ChatsContext,) {
}
suspend fun showGroupReportsView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo) {
- val reportsChatsCtx = ChatModel.ChatsContext(secondaryContextFilter = SecondaryContextFilter.MsgContentTagContext(MsgContentTag.Report))
+ val reportsChatsCtx = ChatModel.ChatsContext(contentTag = MsgContentTag.Report)
openChat(secondaryChatsCtx = reportsChatsCtx, chatModel.remoteHostId(), chatInfo)
ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close ->
ModalView({}, showAppBar = false) {
val chatInfo = remember { derivedStateOf { chatModel.chats.value.firstOrNull { it.id == chatModel.chatId.value }?.chatInfo } }.value
if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canModerate) {
- GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId, close)
+ GroupReportsView(reportsChatsCtx, staleChatId, scrollToItemId)
} else {
LaunchedEffect(Unit) {
close()
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt
deleted file mode 100644
index 48171bfeb7..0000000000
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberAdmission.kt
+++ /dev/null
@@ -1,152 +0,0 @@
-package chat.simplex.common.views.chat.group
-
-import InfoRow
-import SectionBottomSpacer
-import SectionDividerSpaced
-import SectionItemView
-import SectionTextFooter
-import SectionView
-import androidx.compose.material.MaterialTheme
-import androidx.compose.material.Text
-import androidx.compose.runtime.*
-import androidx.compose.runtime.saveable.rememberSaveable
-import dev.icerock.moko.resources.compose.stringResource
-import chat.simplex.common.views.helpers.*
-import chat.simplex.common.model.*
-import chat.simplex.common.platform.ColumnWithScrollBar
-import chat.simplex.common.platform.chatModel
-import chat.simplex.res.MR
-import dev.icerock.moko.resources.StringResource
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
-
-@Composable
-fun MemberAdmissionView(m: ChatModel, rhId: Long?, chatId: String, close: () -> Unit) {
- val groupInfo = remember { derivedStateOf {
- val ch = m.getChat(chatId)
- val g = (ch?.chatInfo as? ChatInfo.Group)?.groupInfo
- if (g == null || ch.remoteHostId != rhId) null else g
- }}
- val gInfo = groupInfo.value ?: return
- var admission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(gInfo.groupProfile.memberAdmission) }
- var currentAdmission by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(admission) }
-
- fun saveAdmission(afterSave: () -> Unit = {}) {
- withBGApi {
- val gp = gInfo.groupProfile.copy(memberAdmission = admission)
- val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
- if (g != null) {
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateGroup(rhId, g)
- currentAdmission = admission
- }
- }
- afterSave()
- }
- }
- ModalView(
- close = {
- if (admission == currentAdmission) close()
- else showUnsavedChangesAlert({ saveAdmission(close) }, close)
- },
- ) {
- MemberAdmissionLayout(
- admission,
- currentAdmission,
- gInfo,
- applyAdmission = { admsn ->
- admission = admsn
- },
- reset = {
- admission = currentAdmission
- },
- saveAdmission = ::saveAdmission,
- )
- }
-}
-
-@Composable
-private fun MemberAdmissionLayout(
- admission: GroupMemberAdmission?,
- currentAdmission: GroupMemberAdmission?,
- groupInfo: GroupInfo,
- applyAdmission: (GroupMemberAdmission) -> Unit,
- reset: () -> Unit,
- saveAdmission: () -> Unit,
-) {
- ColumnWithScrollBar {
- AppBarTitle(stringResource(MR.strings.member_admission))
- val review = remember(admission) { mutableStateOf(admission?.review) }
- AdmissionSection(MR.strings.admission_stage_review, MR.strings.admission_stage_review_descr, review, groupInfo) { criteria ->
- if (admission != null) {
- applyAdmission(admission.copy(review = criteria))
- } else {
- applyAdmission(GroupMemberAdmission(review = criteria))
- }
- }
- if (groupInfo.isOwner) {
- SectionDividerSpaced(maxTopPadding = true, maxBottomPadding = false)
- ResetSaveButtons(
- reset = reset,
- save = saveAdmission,
- disabled = admission == currentAdmission
- )
- }
- SectionBottomSpacer()
- }
-}
-
-private val memberCriterias: List> = listOf(
- null to generalGetString(MR.strings.member_criteria_off),
- MemberCriteria.All to generalGetString(MR.strings.member_criteria_all)
-)
-
-@Composable
-private fun AdmissionSection(
- admissionStageStrId: StringResource,
- admissionStageDescrStrId: StringResource,
- memberCriteria: State,
- groupInfo: GroupInfo,
- onSelected: (MemberCriteria?) -> Unit
-) {
- SectionView {
- if (groupInfo.isOwner) {
- ExposedDropDownSettingRow(
- generalGetString(admissionStageStrId),
- memberCriterias,
- memberCriteria,
- onSelected = { value ->
- onSelected(value)
- }
- )
- } else {
- InfoRow(
- stringResource(admissionStageStrId),
- memberCriteria.value?.text ?: generalGetString(MR.strings.member_criteria_off)
- )
- }
- }
- SectionTextFooter(stringResource( admissionStageDescrStrId))
-}
-
-@Composable
-private fun ResetSaveButtons(reset: () -> Unit, save: () -> Unit, disabled: Boolean) {
- SectionView {
- SectionItemView(reset, disabled = disabled) {
- Text(stringResource(MR.strings.reset_verb), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
- }
- SectionItemView(save, disabled = disabled) {
- Text(stringResource(MR.strings.save_and_notify_group_members), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary)
- }
- }
-}
-
-private fun showUnsavedChangesAlert(save: () -> Unit, revert: () -> Unit) {
- AlertManager.shared.showAlertDialogStacked(
- title = generalGetString(MR.strings.save_admission_question),
- confirmText = generalGetString(MR.strings.save_and_notify_group_members),
- dismissText = generalGetString(MR.strings.exit_without_saving),
- onConfirm = save,
- onDismiss = revert,
- )
-}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt
deleted file mode 100644
index 99e2e3198e..0000000000
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportChatView.kt
+++ /dev/null
@@ -1,145 +0,0 @@
-package chat.simplex.common.views.chat.group
-
-import androidx.compose.foundation.layout.*
-import androidx.compose.material.*
-import androidx.compose.runtime.*
-import androidx.compose.runtime.saveable.rememberSaveable
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.font.FontWeight
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.dp
-import chat.simplex.common.model.*
-import chat.simplex.common.platform.*
-import chat.simplex.common.views.chat.*
-import chat.simplex.common.views.chatlist.*
-import chat.simplex.common.views.helpers.*
-import chat.simplex.res.MR
-import dev.icerock.moko.resources.compose.painterResource
-import dev.icerock.moko.resources.compose.stringResource
-
-@Composable
-private fun MemberSupportChatView(
- chatInfo: ChatInfo,
- memberSupportChatsCtx: ChatModel.ChatsContext,
- staleChatId: State,
- scrollToItemId: MutableState
-) {
- KeyChangeEffect(chatModel.chatId.value) {
- ModalManager.end.closeModals()
- }
- if (appPlatform.isAndroid) {
- DisposableEffect(Unit) {
- onDispose {
- val chat = chatModel.chats.value.firstOrNull { ch -> ch.id == chatInfo.id }
- if (
- memberSupportChatsCtx.isUserSupportChat
- && chat?.chatInfo?.groupInfo_?.membership?.memberPending == true
- ) {
- withBGApi {
- chatModel.chatId.value = null
- }
- }
- }
- }
- }
- ChatView(memberSupportChatsCtx, staleChatId, scrollToItemId, onComposed = {})
-}
-
-@Composable
-fun MemberSupportChatAppBar(
- chatsCtx: ChatModel.ChatsContext,
- scopeMember_: GroupMember?,
- close: () -> Unit,
- onSearchValueChanged: (String) -> Unit
-) {
- val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state }
- val showSearch = rememberSaveable { mutableStateOf(false) }
- val onBackClicked = {
- if (!showSearch.value) {
- close()
- } else {
- onSearchValueChanged("")
- showSearch.value = false
- }
- }
- BackHandler(onBack = onBackClicked)
- if (scopeMember_ != null) {
- DefaultAppBar(
- navigationButton = { NavigationButtonBack(onBackClicked) },
- title = { MemberSupportChatToolbarTitle(scopeMember_) },
- onTitleClick = null,
- onTop = !oneHandUI.value,
- showSearch = showSearch.value,
- onSearchValueChanged = onSearchValueChanged,
- buttons = {
- IconButton({ showSearch.value = true }) {
- Icon(painterResource(MR.images.ic_search), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
- }
- }
- )
- } else {
- DefaultAppBar(
- navigationButton = { NavigationButtonBack(onBackClicked) },
- fixedTitleText = stringResource(MR.strings.support_chat),
- onTitleClick = null,
- onTop = !oneHandUI.value,
- showSearch = showSearch.value,
- onSearchValueChanged = onSearchValueChanged,
- buttons = {
- IconButton({ showSearch.value = true }) {
- Icon(painterResource(MR.images.ic_search), stringResource(MR.strings.search_verb), tint = MaterialTheme.colors.primary)
- }
- }
- )
- }
- ItemsReload(chatsCtx)
-}
-
-@Composable
-fun MemberSupportChatToolbarTitle(member: GroupMember, imageSize: Dp = 40.dp, iconColor: Color = MaterialTheme.colors.secondaryVariant.mixWith(MaterialTheme.colors.onBackground, 0.97f)) {
- Row(
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
- ) {
- MemberProfileImage(size = imageSize * fontSizeSqrtMultiplier, member, iconColor)
- Column(
- Modifier.padding(start = 8.dp),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (member.verified) {
- MemberVerifiedShield()
- }
- Text(
- member.displayName, fontWeight = FontWeight.SemiBold,
- maxLines = 1, overflow = TextOverflow.Ellipsis
- )
- }
- if (member.fullName != "" && member.fullName != member.displayName && member.localAlias.isEmpty()) {
- Text(
- member.fullName,
- maxLines = 1, overflow = TextOverflow.Ellipsis
- )
- }
- }
- }
-}
-
-suspend fun showMemberSupportChatView(staleChatId: State, scrollToItemId: MutableState, chatInfo: ChatInfo, scopeInfo: GroupChatScopeInfo) {
- val memberSupportChatsCtx = ChatModel.ChatsContext(secondaryContextFilter = SecondaryContextFilter.GroupChatScopeContext(scopeInfo))
- openChat(secondaryChatsCtx = memberSupportChatsCtx, chatModel.remoteHostId(), chatInfo)
- ModalManager.end.showCustomModal(true, id = ModalViewId.SECONDARY_CHAT) { close ->
- ModalView({}, showAppBar = false) {
- if (chatInfo is ChatInfo.Group && chatInfo.groupChatScope != null) {
- MemberSupportChatView(chatInfo, memberSupportChatsCtx, staleChatId, scrollToItemId)
- } else {
- LaunchedEffect(Unit) {
- close()
- }
- }
- }
- }
-}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt
deleted file mode 100644
index 298a545c8c..0000000000
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/MemberSupportView.kt
+++ /dev/null
@@ -1,302 +0,0 @@
-package chat.simplex.common.views.chat.group
-
-import SectionBottomSpacer
-import SectionItemView
-import SectionItemViewLongClickable
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.*
-import androidx.compose.foundation.lazy.items
-import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.*
-import androidx.compose.runtime.*
-import androidx.compose.ui.Alignment
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.compose.ui.text.style.TextAlign
-import androidx.compose.ui.text.style.TextOverflow
-import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
-import dev.icerock.moko.resources.compose.stringResource
-import chat.simplex.common.views.helpers.*
-import chat.simplex.common.model.*
-import chat.simplex.common.platform.*
-import chat.simplex.common.ui.theme.*
-import chat.simplex.common.views.chat.*
-import chat.simplex.common.views.chat.item.ItemAction
-import chat.simplex.common.views.chatlist.*
-import chat.simplex.res.MR
-import dev.icerock.moko.resources.compose.painterResource
-import kotlinx.coroutines.*
-
-@Composable
-fun ModalData.MemberSupportView(
- rhId: Long?,
- chat: Chat,
- groupInfo: GroupInfo,
- scrollToItemId: MutableState,
- close: () -> Unit
-) {
- KeyChangeEffect(chatModel.chatId.value) {
- ModalManager.end.closeModals()
- }
- LaunchedEffect(Unit) {
- setGroupMembers(rhId, groupInfo, chatModel)
- }
- ModalView(
- close = close,
- endButtons = { RefreshMembersButton(rhId, groupInfo) }
- ) {
- MemberSupportViewLayout(
- chat,
- groupInfo,
- scrollToItemId
- )
- }
-}
-
-@Composable
-fun RefreshMembersButton(
- rhId: Long?,
- groupInfo: GroupInfo
-) {
- IconButton(
- onClick = {
- withBGApi {
- setGroupMembers(rhId, groupInfo, chatModel)
- }
- }
- ) {
- Icon(
- painterResource(MR.images.ic_refresh),
- contentDescription = null,
- tint = MaterialTheme.colors.primary
- )
- }
-}
-
-@Composable
-private fun ModalData.MemberSupportViewLayout(
- chat: Chat,
- groupInfo: GroupInfo,
- scrollToItemId: MutableState
-) {
- val oneHandUI = remember { ChatController.appPrefs.oneHandUI.state }
- val scope = rememberCoroutineScope()
-
- val membersWithChats = remember { chatModel.groupMembers }.value
- .filter { it.supportChat != null && it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
- .sortedWith(
- compareByDescending { it.memberPending }
- .thenByDescending { (it.supportChat?.mentions ?: 0) > 0 }
- .thenByDescending { (it.supportChat?.memberAttention ?: 0) > 0 }
- .thenByDescending { (it.supportChat?.unread ?: 0) > 0 }
- .thenByDescending { it.supportChat?.chatTs }
- )
-
- val searchText = remember { stateGetOrPut("searchText") { TextFieldValue() } }
- val filteredmembersWithChats = remember(membersWithChats) {
- derivedStateOf {
- val s = searchText.value.text.trim().lowercase()
- if (s.isEmpty()) membersWithChats else membersWithChats.filter { m -> m.anyNameContains(s) }
- }
- }
-
- LazyColumnWithScrollBar(
- contentPadding =
- PaddingValues(
- top = if (oneHandUI.value) WindowInsets.statusBars.asPaddingValues().calculateTopPadding() + DEFAULT_PADDING + 5.dp else topPaddingToContent(false)
- )
- ) {
- item {
- AppBarTitle(stringResource(MR.strings.member_support))
- }
-
- if (membersWithChats.isEmpty()) {
- item {
- Box(Modifier.fillMaxSize().padding(horizontal = DEFAULT_PADDING), contentAlignment = Alignment.Center) {
- Text(generalGetString(MR.strings.no_support_chats), color = MaterialTheme.colors.secondary, textAlign = TextAlign.Center)
- }
- }
- } else {
- item {
- SectionItemView(padding = PaddingValues(start = 14.dp, end = DEFAULT_PADDING_HALF)) {
- MemberListSearchRowView(searchText)
- }
- }
- items(filteredmembersWithChats.value, key = { it.groupMemberId }) { member ->
- Divider()
- val showMenu = remember { mutableStateOf(false) }
- SectionItemViewLongClickable(
- click = {
- val scopeInfo = GroupChatScopeInfo.MemberSupport(groupMember_ = member)
- val supportChatInfo = ChatInfo.Group(groupInfo, groupChatScope = scopeInfo)
- scope.launch {
- showMemberSupportChatView(
- chatModel.chatId,
- scrollToItemId = scrollToItemId,
- supportChatInfo,
- scopeInfo
- )
- }
- },
- longClick = { showMenu.value = true },
- minHeight = 54.dp,
- padding = PaddingValues(horizontal = DEFAULT_PADDING)
- ) {
- Box(contentAlignment = Alignment.CenterStart) {
- DropDownMenuForSupportChat(chat.remoteHostId, member, groupInfo, showMenu)
- SupportChatRow(member)
- }
- }
- }
- item {
- Divider()
- SectionBottomSpacer()
- }
- }
- }
-}
-
-@Composable
-fun SupportChatRow(member: GroupMember) {
- fun memberStatus(): String {
- return if (member.activeConn?.connDisabled == true) {
- generalGetString(MR.strings.member_info_member_disabled)
- } else if (member.activeConn?.connInactive == true) {
- generalGetString(MR.strings.member_info_member_inactive)
- } else if (member.memberPending) {
- member.memberStatus.text
- } else {
- member.memberRole.text
- }
- }
-
- @Composable
- fun SupportChatUnreadIndicator(supportChat: GroupSupportChat) {
- Box(Modifier.widthIn(min = 34.sp.toDp()), contentAlignment = Alignment.TopEnd) {
- Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(4.sp.toDp())) {
- if (supportChat.unread > 0 || supportChat.mentions > 0 || supportChat.memberAttention > 0) {
- val unreadBadgeColor = when {
- supportChat.mentions > 0 || supportChat.memberAttention > 0 -> MaterialTheme.colors.primaryVariant
- else -> MaterialTheme.colors.secondary
- }
- if (supportChat.mentions == 1 && supportChat.unread == 1) {
- Box(modifier = Modifier.offset(y = 2.sp.toDp()).size(15.sp.toDp()).background(unreadBadgeColor, shape = CircleShape), contentAlignment = Alignment.Center) {
- Icon(
- painterResource(MR.images.ic_alternate_email),
- contentDescription = generalGetString(MR.strings.notifications),
- tint = Color.White,
- modifier = Modifier.size(9.sp.toDp())
- )
- }
- } else {
- if (supportChat.mentions > 0 && supportChat.unread > 1) {
- Icon(
- painterResource(MR.images.ic_alternate_email),
- contentDescription = generalGetString(MR.strings.notifications),
- tint = unreadBadgeColor,
- modifier = Modifier.size(12.sp.toDp()).offset(y = 2.sp.toDp())
- )
- }
-
- UnreadBadge(
- text = unreadCountStr(supportChat.unread),
- backgroundColor = unreadBadgeColor,
- yOffset = 2.dp
- )
- }
- }
- }
- }
- }
-
- Row(
- Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceBetween,
- verticalAlignment = Alignment.CenterVertically
- ) {
- Row(
- Modifier.weight(1f).padding(top = MEMBER_ROW_VERTICAL_PADDING, end = DEFAULT_PADDING, bottom = MEMBER_ROW_VERTICAL_PADDING),
- horizontalArrangement = Arrangement.spacedBy(4.dp),
- verticalAlignment = Alignment.CenterVertically
- ) {
- MemberProfileImage(size = MEMBER_ROW_AVATAR_SIZE, member)
- Spacer(Modifier.width(DEFAULT_PADDING_HALF))
- Column {
- Row(verticalAlignment = Alignment.CenterVertically) {
- if (member.verified) {
- MemberVerifiedShield()
- }
- Text(
- member.chatViewName, maxLines = 1, overflow = TextOverflow.Ellipsis,
- color = if (member.memberIncognito) Indigo else Color.Unspecified
- )
- }
-
- Text(
- memberStatus(),
- color = MaterialTheme.colors.secondary,
- fontSize = 12.sp,
- maxLines = 1,
- overflow = TextOverflow.Ellipsis
- )
- }
- }
-
- Row {
- if (member.memberPending) {
- Icon(
- painterResource(MR.images.ic_flag_filled),
- contentDescription = null,
- Modifier.padding(end = 3.dp).size(16.dp),
- tint = MaterialTheme.colors.primaryVariant
- )
- }
- if (member.supportChat != null) {
- SupportChatUnreadIndicator(member.supportChat)
- }
- }
- }
-}
-
-@Composable
-private fun DropDownMenuForSupportChat(rhId: Long?, member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState) {
- DefaultDropdownMenu(showMenu) {
- if (member.memberPending) {
- ItemAction(stringResource(MR.strings.accept_pending_member_button), painterResource(MR.images.ic_check), color = MaterialTheme.colors.primary, onClick = {
- acceptMemberDialog(rhId, groupInfo, member)
- showMenu.value = false
- })
- } else {
- ItemAction(stringResource(MR.strings.delete_member_support_chat_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = {
- deleteMemberSupportChatDialog(rhId, groupInfo, member)
- showMenu.value = false
- })
- }
- }
-}
-
-fun deleteMemberSupportChatDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) {
- AlertManager.shared.showAlertDialog(
- title = generalGetString(MR.strings.delete_member_support_chat_alert_title),
- confirmText = generalGetString(MR.strings.delete_member_support_chat_button),
- onConfirm = {
- deleteMemberSupportChat(rhId, groupInfo, member)
- },
- destructive = true,
- )
-}
-
-private fun deleteMemberSupportChat(rhId: Long?, groupInfo: GroupInfo, member: GroupMember) {
- withBGApi {
- val r = chatModel.controller.apiDeleteMemberSupportChat(rhId, groupInfo.groupId, member.groupMemberId)
- if (r != null) {
- withContext(Dispatchers.Main) {
- chatModel.chatsContext.upsertGroupMember(rhId, r.first, r.second)
- chatModel.chatsContext.updateGroup(rhId, r.first)
- }
- }
- }
-}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
index 6e938aa5c4..2e789df7bc 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt
@@ -86,7 +86,6 @@ fun ChatItemView(
joinGroup: (Long, () -> Unit) -> Unit,
acceptCall: (Contact) -> Unit,
scrollToItem: (Long) -> Unit,
- scrollToItemId: MutableState,
scrollToQuotedItemFromItem: (Long) -> Unit,
acceptFeature: (Contact, ChatFeature, Int?) -> Unit,
openDirectChat: (Long) -> Unit,
@@ -272,7 +271,6 @@ fun ChatItemView(
}
}
- // improvement could be to track "forwarded from" scope and open it
@Composable
fun GoToItemButton(alignStart: Boolean, parentActivated: State) {
val chatTypeApiIdMsgId = cItem.meta.itemForwarded?.chatTypeApiIdMsgId
@@ -326,7 +324,7 @@ fun ChatItemView(
) {
@Composable
fun framedItemView() {
- FramedItemView(chatsCtx, cInfo, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem, scrollToItemId, scrollToQuotedItemFromItem)
+ FramedItemView(cInfo, cItem, uriHandler, imageProvider, linkMode = linkMode, showViaProxy = showViaProxy, showMenu, showTimestamp = showTimestamp, tailVisible = itemSeparation.largeGap, receiveFile, onLinkLongClick, scrollToItem, scrollToQuotedItemFromItem)
}
fun deleteMessageQuestionText(): String {
@@ -637,15 +635,6 @@ fun ChatItemView(
CIEventView(eventItemViewText(reversedChatItems))
}
- @Composable fun PendingReviewEventItemView() {
- Text(
- buildAnnotatedString {
- withStyle(chatEventStyle.copy(fontWeight = FontWeight.Bold)) { append(cItem.content.text) }
- },
- Modifier.padding(horizontal = 6.dp, vertical = 6.dp)
- )
- }
-
@Composable
fun DeletedItem() {
MarkedDeletedItemView(chatsCtx, cItem, cInfo, cInfo.timedMessagesTTL, revealed, showViaProxy = showViaProxy, showTimestamp = showTimestamp)
@@ -722,16 +711,12 @@ fun ChatItemView(
is CIContent.RcvGroupEventContent -> {
when (c.rcvGroupEvent) {
is RcvGroupEvent.MemberCreatedContact -> CIMemberCreatedContactView(cItem, openDirectChat)
- is RcvGroupEvent.NewMemberPendingReview -> PendingReviewEventItemView()
else -> EventItemView()
}
MsgContentItemDropdownMenu()
}
is CIContent.SndGroupEventContent -> {
- when (c.sndGroupEvent) {
- is SndGroupEvent.UserPendingReview -> PendingReviewEventItemView()
- else -> EventItemView()
- }
+ EventItemView()
MsgContentItemDropdownMenu()
}
is CIContent.RcvConnEventContent -> {
@@ -1437,7 +1422,7 @@ fun PreviewChatItemView(
chatItem: ChatItem = ChatItem.getSampleData(1, CIDirection.DirectSnd(), Clock.System.now(), "hello")
) {
ChatItemView(
- chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null),
+ chatsCtx = ChatModel.ChatsContext(contentTag = null),
rhId = null,
ChatInfo.Direct.sampleData,
chatItem,
@@ -1459,7 +1444,6 @@ fun PreviewChatItemView(
joinGroup = { _, _ -> },
acceptCall = { _ -> },
scrollToItem = {},
- scrollToItemId = remember { mutableStateOf(null) },
scrollToQuotedItemFromItem = {},
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
@@ -1488,7 +1472,7 @@ fun PreviewChatItemView(
fun PreviewChatItemViewDeletedContent() {
SimpleXTheme {
ChatItemView(
- chatsCtx = ChatModel.ChatsContext(secondaryContextFilter = null),
+ chatsCtx = ChatModel.ChatsContext(contentTag = null),
rhId = null,
ChatInfo.Direct.sampleData,
ChatItem.getDeletedContentSampleData(),
@@ -1510,7 +1494,6 @@ fun PreviewChatItemViewDeletedContent() {
joinGroup = { _, _ -> },
acceptCall = { _ -> },
scrollToItem = {},
- scrollToItemId = remember { mutableStateOf(null) },
scrollToQuotedItemFromItem = {},
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
index b2beba29e4..fd8a32af64 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt
@@ -27,7 +27,6 @@ import kotlin.math.ceil
@Composable
fun FramedItemView(
- chatsCtx: ChatModel.ChatsContext,
chatInfo: ChatInfo,
ci: ChatItem,
uriHandler: UriHandler? = null,
@@ -40,7 +39,6 @@ fun FramedItemView(
receiveFile: (Long) -> Unit,
onLinkLongClick: (link: String) -> Unit = {},
scrollToItem: (Long) -> Unit = {},
- scrollToItemId: MutableState,
scrollToQuotedItemFromItem: (Long) -> Unit = {},
) {
val sent = ci.chatDir.sent
@@ -255,11 +253,7 @@ fun FramedItemView(
onLongClick = { showMenu.value = true },
onClick = {
if (ci.quotedItem.itemId != null) {
- if (ci.isReport && chatsCtx.secondaryContextFilter != null) {
- scrollToItemId.value = ci.quotedItem.itemId
- } else {
- scrollToItem(ci.quotedItem.itemId)
- }
+ scrollToItem(ci.quotedItem.itemId)
} else {
scrollToQuotedItemFromItem(ci.id)
}
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
index 52b4059eef..958b794bd7 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt
@@ -236,6 +236,7 @@ suspend fun openChat(
suspend fun openLoadedChat(chat: Chat) {
withContext(Dispatchers.Main) {
+ chatModel.chatsContext.chatItemStatuses.clear()
chatModel.chatsContext.chatItems.replaceAll(chat.chatItems)
chatModel.chatId.value = chat.chatInfo.id
chatModel.chatsContext.chatState.clear()
@@ -304,7 +305,7 @@ fun GroupMenuItems(
}
}
GroupMemberStatus.MemAccepted -> {
- if (groupInfo.membership.memberCurrentOrPending) {
+ if (groupInfo.membership.memberCurrent) {
LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu)
}
if (groupInfo.canDelete) {
@@ -326,7 +327,7 @@ fun GroupMenuItems(
}
}
ClearChatAction(chat, showMenu)
- if (groupInfo.membership.memberCurrentOrPending) {
+ if (groupInfo.membership.memberCurrent) {
LeaveGroupAction(chat.remoteHostId, groupInfo, chatModel, showMenu)
}
if (groupInfo.canDelete) {
@@ -614,8 +615,7 @@ fun markChatRead(c: Chat) {
chatModel.controller.apiChatRead(
chat.remoteHostId,
chat.chatInfo.chatType,
- chat.chatInfo.apiId,
- chat.chatInfo.groupChatScope()
+ chat.chatInfo.apiId
)
chat = chatModel.getChat(chat.id) ?: return@withApi
}
@@ -651,7 +651,7 @@ fun markChatUnread(chat: Chat, chatModel: ChatModel) {
if (success) {
withContext(Dispatchers.Main) {
chatModel.chatsContext.replaceChat(chat.remoteHostId, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = true)))
- chatModel.chatsContext.updateChatTagReadInPrimaryContext(chat, wasUnread)
+ chatModel.chatsContext.updateChatTagReadNoContentTag(chat, wasUnread)
}
}
}
@@ -886,7 +886,7 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch
ChatInfo.Direct(contact.copy(chatSettings = chatSettings))
}
is ChatInfo.Group -> with(chatInfo) {
- ChatInfo.Group(groupInfo.copy(chatSettings = chatSettings), groupChatScope = null)
+ ChatInfo.Group(groupInfo.copy(chatSettings = chatSettings))
}
else -> null
}
@@ -914,7 +914,7 @@ fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: Ch
val updatedChat = chatModel.getChat(chatInfo.id)
if (updatedChat != null) {
withContext(Dispatchers.Main) {
- chatModel.chatsContext.updateChatTagReadInPrimaryContext(updatedChat, wasUnread)
+ chatModel.chatsContext.updateChatTagReadNoContentTag(updatedChat, wasUnread)
}
}
val current = currentState?.value
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
index 65a9d175dd..93d512507a 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatPreviewView.kt
@@ -241,8 +241,6 @@ fun ChatPreviewView(
GroupMemberStatus.MemRejected -> Text(stringResource(MR.strings.group_preview_rejected))
GroupMemberStatus.MemInvited -> Text(groupInvitationPreviewText(currentUserProfileDisplayName, cInfo.groupInfo))
GroupMemberStatus.MemAccepted -> Text(stringResource(MR.strings.group_connection_pending), color = MaterialTheme.colors.secondary)
- GroupMemberStatus.MemPendingReview, GroupMemberStatus.MemPendingApproval ->
- Text(stringResource(MR.strings.reviewed_by_admins), color = MaterialTheme.colors.secondary)
else -> {}
}
else -> {}
@@ -365,11 +363,7 @@ fun ChatPreviewView(
if (progressByTimeout) {
progressView()
} else if (chat.chatStats.reportsCount > 0) {
- FlagIcon(color = MaterialTheme.colors.error)
- } else if (chat.supportUnreadCount > 0) {
- FlagIcon(color = MaterialTheme.colors.primary)
- } else if (chat.chatInfo.groupInfo_?.membership?.memberPending == true) {
- FlagIcon(color = MaterialTheme.colors.secondary)
+ GroupReportsIcon()
} else {
IncognitoIcon(chat.chatInfo.incognito)
}
@@ -471,10 +465,17 @@ fun ChatPreviewView(
)
}
} else {
- UnreadBadge(
- text = if (n > 0) unreadCountStr(n) else "",
- backgroundColor = if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant,
- yOffset = 3.dp
+ Text(
+ if (n > 0) unreadCountStr(n) else "",
+ color = Color.White,
+ fontSize = 10.sp,
+ style = TextStyle(textAlign = TextAlign.Center),
+ modifier = Modifier
+ .offset(y = 3.sp.toDp())
+ .background(if (disabled || showNtfsIcon) MaterialTheme.colors.secondary else MaterialTheme.colors.primaryVariant, shape = CircleShape)
+ .badgeLayout()
+ .padding(horizontal = 2.sp.toDp())
+ .padding(vertical = 1.sp.toDp())
)
}
}
@@ -547,11 +548,11 @@ fun IncognitoIcon(incognito: Boolean) {
}
@Composable
-fun FlagIcon(color: Color) {
+fun GroupReportsIcon() {
Icon(
painterResource(MR.images.ic_flag),
contentDescription = null,
- tint = color,
+ tint = MaterialTheme.colors.error,
modifier = Modifier
.size(21.sp.toDp())
.offset(x = 2.sp.toDp())
@@ -566,26 +567,6 @@ private fun groupInvitationPreviewText(currentUserProfileDisplayName: String?, g
stringResource(MR.strings.group_preview_you_are_invited)
}
-@Composable
-fun UnreadBadge(
- text: String,
- backgroundColor: Color,
- yOffset: Dp? = null
-) {
- Text(
- text,
- color = Color.White,
- fontSize = 10.sp,
- style = TextStyle(textAlign = TextAlign.Center),
- modifier = Modifier
- .offset(y = yOffset ?: 0.dp)
- .background(backgroundColor, shape = CircleShape)
- .badgeLayout()
- .padding(horizontal = 2.sp.toDp())
- .padding(vertical = 1.sp.toDp())
- )
-}
-
@Composable
fun unreadCountStr(n: Int): String {
return if (n < 1000) "$n" else "${n / 1000}" + stringResource(MR.strings.thousand_abbreviation)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
index 13351a2111..185ec3925f 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt
@@ -634,7 +634,7 @@ fun HostDisconnectButton(onClick: (() -> Unit)?) {
}
@Composable
-fun BoxScope.userUnreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) {
+fun BoxScope.unreadBadge(unreadCount: Int, userMuted: Boolean, hasPadding: Boolean) {
Text(
if (unreadCount > 0) unreadCountStr(unreadCount) else "",
color = Color.White,
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt
index 2db40f770d..3d913cf957 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt
@@ -45,6 +45,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit, c
withContext(Dispatchers.Main) {
chatModel.chatsContext.updateGroup(rhId = rhId, groupInfo)
chatModel.chatsContext.chatItems.clearAndNotify()
+ chatModel.chatsContext.chatItemStatuses.clear()
chatModel.chatId.value = groupInfo.id
}
setGroupMembers(rhId, groupInfo, chatModel)
diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
index 5bd45ccaab..7ea656e1e4 100644
--- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt
@@ -21,6 +21,7 @@ import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.*
+import chat.simplex.common.BuildConfigCommon
import chat.simplex.common.model.*
import chat.simplex.common.model.ChatController.appPrefs
import chat.simplex.common.platform.*
@@ -127,7 +128,9 @@ fun SettingsLayout(
SectionDividerSpaced()
SectionView(stringResource(MR.strings.settings_section_title_support)) {
- ContributeItem(uriHandler)
+ if (!BuildConfigCommon.ANDROID_BUNDLE) {
+ ContributeItem(uriHandler)
+ }
RateAppItem(uriHandler)
StarOnGithubItem(uriHandler)
}
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
index b5bf2efaff..6726009a5f 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml
@@ -158,8 +158,6 @@
Error loading details
Error adding member(s)
Error joining group
- Error accepting member
- Error deleting chat with member
Cannot receive file
Sender cancelled file transfer.
Unknown servers!
@@ -466,10 +464,6 @@
1 report
%d reports
Member reports
- %d messages
- %d chats with members
- 1 chat with a member
- %d chat(s)
Share message…
@@ -492,6 +486,7 @@
Decoding error
The image cannot be decoded. Please, try a different image or contact developers.
The video cannot be decoded. Please, try a different video or contact developers.
+ you are observer
Files and media prohibited!
Only group owners can enable files and media.
Send direct message to connect
@@ -510,8 +505,6 @@
Report violation: only group moderators will see it.
Report content: only group moderators will see it.
Report other: only group moderators will see it.
- Report sent to moderators
- You can view your reports in Chat with admins.
You can\'t send messages!
contact not ready
@@ -525,9 +518,6 @@
removed from group
you left
can\'t send messages
- you are observer
- reviewed by admins
- member has old version
Image
@@ -1073,7 +1063,6 @@
Your profile is stored on your device and shared only with your contacts. SimpleX servers cannot see your profile.
Edit image
Delete image
- Save admission settings?
Save preferences?
Save and notify contact
Save and notify contacts
@@ -1593,8 +1582,6 @@
invited %1$s
connected
- accepted %1$s
- accepted you
left
changed role of %s to %s
blocked %s
@@ -1606,7 +1593,6 @@
updated group profile
invited via your group link
connected directly
- New member wants to join the group.
you changed role of %s to %s
you changed role for yourself to %s
you blocked %s
@@ -1614,8 +1600,6 @@
you removed %1$s
you left
group profile updated
- you accepted this member
- Please wait for group moderators to review your request to join the group.
%s connected
%s and %s connected
@@ -1675,8 +1659,6 @@
invited
pending approval
pending
- pending review
- review
connecting (introduced)
connecting (introduction invitation)
connecting (accepted)
@@ -1747,7 +1729,6 @@
Receipts are disabled
This group has over %1$d members, delivery receipts are not sent.
Invite
- Chat with admins
FOR CONSOLE
@@ -1784,7 +1765,7 @@
Remove member?
Remove members?
Remove member
- Chat with member
+
Send direct message
Member will be removed from group - this cannot be undone!
Members will be removed from group - this cannot be undone!
@@ -2061,7 +2042,6 @@
Contact preferences
Group preferences
Set group preferences
- Set member admission
Your preferences
Disappearing messages
Direct messages
@@ -2179,29 +2159,6 @@
owners
Enabled for
-
- Member admission
- Review members
- Review members before admitting ("knocking").
- off
- all
-
-
- Chats with members
- No chats with members
- Delete chat
- Delete chat with member?
-
-
- Chat with admins
- Reject
- Reject member?
- Accept
- Accept member
- Member will join the group, accept member?
- Accept as member
- Accept as observer
-
What\'s new
New in %s
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
index 53f31ecba9..6646720c5c 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml
@@ -1014,7 +1014,7 @@
Moderieren
Diese Nachricht wird für alle Mitglieder als moderiert gekennzeichnet.
Sie sind Beobachter
- Sie sind Beobachter
+ Sie können keine Nachrichten versenden!
Beobachter
Anfängliche Rolle
Nachricht des Mitglieds löschen\?
@@ -1465,7 +1465,7 @@
Profil erstellen
%s und %s
Ihrer Gruppe beitreten?
- %1$s.]]>
+ %1$s.]]>
Das ist Ihr eigener Einmal-Link!
%d Nachrichten als gelöscht markiert
Gruppe besteht bereits!
@@ -2462,52 +2462,4 @@
Aus
TCP-Port 443 nur für voreingestellte Server verwenden.
Voreingestellte Server
- %d Chats mit Mitgliedern
- %d Chat(s)
- Meldung wurde an die Moderatoren gesendet
- Sie haben dieses Mitglied übernommen
- Überprüfung der Mitglieder vor der Aufnahme (\"Anklopfen\").
- Überprüfung der Mitglieder
- alle
- Aus
- Als Beobachter übernehmen
- Mitglied übernehmen
- Chats mit Mitgliedern
- Chat mit Administratoren
- Keine Chats mit Mitgliedern
- Entfernen
- hat Sie übernommen
- Chat mit einem Mitglied
- %d Nachrichten
- Ein Mitglied wird der Gruppe beitreten. Übernehmen?
- Ein neues Mitglied will der Gruppe beitreten.
- Überprüfung
- Von Administratoren überprüft
- Aufnahme von Mitgliedern festlegen
- Speichern der Aufnahme-Einstellungen?
- Sie können Ihre Meldungen im Chat mit den Administratoren sehen.
- Chat mit Administratoren
- %1$s übernommen
- Als Mitglied übernehmen
- Fehler beim Übernehmen des Mitglieds
- Aufnahme von Mitgliedern
- Ausstehende Überprüfung
- Chat mit einem Mitglied
- Übernehmen
- Bitte warten Sie auf die Überprüfung Ihrer Anfrage durch die Gruppen-Moderatoren, um der Gruppe beitreten zu können.
- Gruppe wird gelöscht
- Beitrittsanfrage abgelehnt
- Von der Gruppe entfernt
- Sie haben die Gruppe verlassen
- Kontakt deaktiviert
- Nicht synchronisiert
- Fehler beim Löschen des Chats mit dem Mitglied
- Kontakt nicht bereit
- Sie können keine Nachrichten senden!
- Chat löschen
- Chat mit dem Mitglied löschen?
- Mitglied ablehnen?
- Es können keine Nachrichten gesendet werden
- Kontakt gelöscht
- Das Mitglied hat eine alte App-Version
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
index 1a1ca0e8a6..c8897c4063 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml
@@ -350,7 +350,7 @@
Az adatbázis titkosítási jelmondata frissülni fog és a beállításokban lesz tárolva.
Adatbázis-azonosító
Adatbázis-azonosító: %d
- Adatbázis-azonosítók és átvitelelkülönítési beállítások.
+ Adatbázis-azonosítók és átvitel-izolációs beállítások.
Az adatbázis titkosítási jelmondata frissülni fog és a Keystore-ban lesz tárolva.
Az adatbázis titkosítva lesz, a jelmondat pedig a beállításokban lesz tárolva.
Kiszolgáló törlése
@@ -366,7 +366,7 @@
%dmp
Kézbesítési jelentések!
Az eszközön nincs beállítva a képernyőzár. A SimpleX-zár az „Adatvédelem és biztonság” menüben kapcsolható be, miután beállította a képernyőzárat az eszközén.
- Titkosításvisszafejtési hiba
+ Titkosítás visszafejtési hiba
Eltűnik: %s
szerkesztve
Törlés
@@ -532,7 +532,7 @@
Fájlok és médiatartalmak
KONZOLHOZ
Nem sikerült a titkosítást újraegyeztetni.
- Hiba történt a felhasználóprofil törlésekor
+ Hiba történt a felhasználó-profil törlésekor
Csoporttag általi javítás nem támogatott
Adja meg az üdvözlőüzenetet…
Titkosított adatbázis
@@ -676,7 +676,7 @@
Csevegési profil létrehozása
Védett a kéretlen tartalommal szemben
Hordozható eszközök leválasztása
- Különböző nevek, profilképek és átvitelelkülönítés.
+ Különböző nevek, profilképek és átvitel-izoláció.
Elutasítás esetén a feladó NEM kap értesítést.
Szerepkörválasztó kibontása
A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!
@@ -716,7 +716,7 @@
Hamarosan további fejlesztések érkeznek!
A reakciók hozzáadása az üzenetekhez le van tiltva ebben a csevegésben.
Helytelen biztonsági kód!
- Ez akkor fordulhat elő, ha Ön vagy a partnere egy régi adatbázis biztonsági mentését használta.
+ Ez akkor fordulhat elő, ha Ön vagy a partnere régi adatbázis biztonsági mentést használt.
Új számítógép-alkalmazás!
Most már az adminisztrátorok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (megfigyelő szerepkör)
meghívta őt: %1$s
@@ -810,7 +810,7 @@
%s ajánlotta
Csoport elhagyása
%s összes üzenete meg fog jelenni!
- Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült az üzenetet visszafejteni, mert Ön, vagy a partnere egy régi adatbázis biztonsági mentését használta.\n3. A kapcsolat sérült.
+ Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Nem sikerült visszafejteni az üzenetet, mert Ön, vagy a partnere régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült.
megfigyelő
inkognitó a csoporthivatkozáson keresztül
Onion-kiszolgálók használata, ha azok rendelkezésre állnak.
@@ -1026,7 +1026,7 @@
frissítette a csoport profilját
SIMPLEX CHAT TÁMOGATÁSA
SimpleX Chat szolgáltatás
- Ön megfigyelő
+ Nem lehet üzeneteket küldeni!
%s hitelesítve
Jelszó a megjelenítéshez
Adatvédelem és biztonság
@@ -1203,7 +1203,7 @@
Ön eltávolította őt: %1$s
Jelmondat mentése és a csevegés megnyitása
Menti a beállításokat?
- Nincsenek felhasználói azonosítók.
+ Nincsenek felhasználó-azonosítók.
A közvetlen üzenetek küldése a tagok között le van tiltva.
SOCKS-proxy használata?
Hangszóró kikapcsolva
@@ -1345,7 +1345,7 @@
%1$s nevű csoporthoz.]]>
Amikor az alkalmazás fut
Inkognitóprofilt használ ehhez a csoporthoz – fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva
- Átvitelelkülönítés
+ Átvitel-izoláció
Akkor lesz kapcsolódva, ha a meghívási kérése el lesz fogadva, várjon, vagy ellenőrizze később!
A hangüzenetek küldése le van tiltva.
Alkalmazás akkumulátor-használata / Korlátlan módot az alkalmazás beállításaiban.]]>
@@ -1364,8 +1364,8 @@
Adatainak védelme érdekében a SimpleX külön üzenet-azonosítókat használ minden egyes kapcsolatához.
(a megosztáshoz a partnerével)
Csoportmeghívó elküldve
- Frissíti az átvitelelkülönítési módot?
- Átvitelelkülönítés
+ Frissíti az átvitel-izoláció módját?
+ Átvitel-izoláció
Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak.
A csevegési adatbázis nem titkosított – állítson be egy jelmondatot annak védelméhez.
Közvetlen internetkapcsolat használata?
@@ -1395,7 +1395,7 @@
a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet a friss hírekről.]]>
Nem kötelező üdvözlőüzenettel.
Ismeretlen adatbázishiba: %s
- Elrejtheti vagy lenémíthatja a felhasználóprofiljait – koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz.
+ Elrejtheti vagy lenémíthatja a felhasználó-profiljait – koppintson (vagy számítógép-alkalmazásban kattintson) hosszan a profilra a felugró menühöz.
Inkognitóra váltás kapcsolódáskor.
Megoszthat egy hivatkozást vagy QR-kódot – így bárki csatlakozhat a csoporthoz. Ha a csoportot Ön később törli, akkor nem fogja elveszíteni annak tagjait.
Ön csatlakozott ehhez a csoporthoz
@@ -1435,7 +1435,7 @@
A kézbesítési jelentések le vannak tiltva %d csoportban
Néhány nem végzetes hiba történt az importáláskor:
Köszönet a felhasználóknak a Weblate-en való közreműködésért!
- A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címét.
+ A továbbítókiszolgáló csak szükség esetén lesz használva. Egy másik fél megfigyelheti az IP-címet.
Beállítás a rendszer-hitelesítés helyett.
A fogadási cím egy másik kiszolgálóra fog módosulni. A cím módosítása a feladó online állapotba kerülése után fejeződik be.
A csevegés megállítása a csevegési adatbázis exportálásához, importálásához vagy törléséhez. A csevegés megállításakor nem tud üzeneteket fogadni és küldeni.
@@ -1444,7 +1444,7 @@
Jelmondat mentése a beállításokban
Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem lesznek elküldve.
A második jelölés, amit kihagytunk! ✅
- A továbbítókiszolgáló megvédi az IP-címét, de megfigyelheti a hívás időtartamát.
+ A továbbítókiszolgáló megvédi az Ön IP-címét, de megfigyelheti a hívás időtartamát.
Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt.
A mentett WebRTC ICE-kiszolgálók el lesznek távolítva.
A kézbesítési jelentések engedélyezve vannak %d csoportban
@@ -1664,8 +1664,8 @@
Ez a csevegés végpontok közötti titkosítással védett.
Átköltöztetési párbeszédablak megnyitása
Ez a csevegés végpontok közötti kvantumbiztos titkosítással védett.
- végpontok közötti titkosítással, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.]]>
- végpontok közötti kvantumbiztos titkosítással, kompromittálás előtti és utáni titkosságvédelemmel, illetve letagadhatósággal vannak védve.]]>
+ végpontok közötti titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]>
+ végpontok közötti kvantumbiztos titkosítással, sérülés utáni titkosságvédelemmel és -helyreállítással, továbbá letagadhatósággal vannak védve.]]>
Hiba történt az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel.
Keresse meg ezt az engedélyt az Android beállításaiban, és adja meg kézzel.
Engedélyezés a beállításokban
@@ -1749,14 +1749,14 @@
Az IP-cím védelmének érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez.
Üzenet-útválasztási tartalék
PRIVÁT ÜZENET-ÚTVÁLASZTÁS
- Használjon privát útválasztást az ismeretlen kiszolgálókkal, ha az IP-cím nem védett.
+ Használjon privát útválasztást ismeretlen kiszolgálókkal, ha az IP-cím nem védett.
NE küldjön üzeneteket közvetlenül, még akkor sem, ha a saját kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást.
- Tor vagy VPN nélkül az IP-címe láthatóvá válik a fájlkiszolgálók számára.
+ Tor vagy VPN nélkül az Ön IP-címe látható lesz a fájlkiszolgálók számára.
FÁJLOK
IP-cím védelme
Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS-proxy engedélyezve van).
Ismeretlen kiszolgálók!
- Tor vagy VPN nélkül az IP-címe láthatóvá válik a következő XFTP-továbbítókiszolgálók számára:\n%1$s.
+ Tor vagy VPN nélkül az Ön IP-címe látható lesz a következő XFTP-továbbítókiszolgálók számára:\n%1$s.
Összes színmód
Fekete
Színmód
@@ -2072,7 +2072,7 @@
Hang/Videó váltása hívás közben.
Csevegési profilváltás az egyszer használható meghívókhoz.
Továbbfejlesztett biztonság ✅
- A SimpleX-protokollokat a Trail of Bits auditálta.
+ A SimpleX Chat biztonsága a Trail of Bits által lett felülvizsgálva.
Hiba történt a kiszolgálók mentésekor
Nincsenek üzenet-kiszolgálók.
Nincsenek üzenetfogadási kiszolgálók.
@@ -2354,53 +2354,4 @@
Kikapcsolva
Előre beállított kiszolgálók
A 443-as TCP-port használata kizárólag az előre beállított kiszolgálokhoz.
- Hiba a tag befogadásakor
- %d csevegés a tagokkal
- %d üzenet
- 1 csevegés egy taggal
- %d csevegés
- A jelentés el lett küldve a moderátoroknak
- A jelentéseket megtekintheti a „Csevegés az adminisztrátorokkal” menüben.
- függőben lévő áttekintés
- áttekintés
- Csevegés az adminisztrátorokkal
- Csevegés a tagokkal
- Tagbefogadás
- Nincsenek csevegések a tagokkal
- Tagok áttekintése
- Tagok áttekintése a befogadás előtt (kopogtatás).
- Csevegés az adminisztrátorokkal
- A tag csatlakozni akar a csoporthoz, befogadja a tagot?
- Eltávolítás
- Befogadás
- Tag befogadása
- összes
- Csevegés a taggal
- Új tag szeretne csatlakozni a csoporthoz.
- kikapcsolva
- Befogadás megfigyelőként
- Várja meg, amíg a csoport moderátorai áttekintik a csoporthoz való csatlakozási kérelmét.
- befogadta Önt
- Tagbefogadás beállítása
- Elmenti a befogadási beállításokat?
- Ön befogadta ezt a tagot
- Befogadás tagként
- befogadta őt: %1$s
- áttekintve a moderátorok által
- nem lehet üzeneteket küldeni
- partner letiltva
- csoport törölve
- eltávolítva a csoportból
- csatlakozási kérelem elutasítva
- Ön elhagyta a csoportot
- a tag régi verziót használ
- Hiba a taggal való csevegés törlésekor
- Ön nem tud üzeneteket küldeni!
- a kapcsolat nem áll készen
- nincs szinkronizálva
- Törli a taggal való csevegést?
- partner törölve
- Csevegés törlése
- Elutasítás
- Elutasítja a tagot?
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
index b69ed8405f..a269149e99 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/in/strings.xml
@@ -434,7 +434,7 @@
Panggilan berlangsung
Menghubungkan panggilan
Pesan yang terlewati
- Hash dari pesan sebelumnya berbeda.
+ Hash dari pesan sebelumnya berbeda.\"
Privasi & keamanan
Enkripsi berkas lokal
Terima gambar otomatis
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
index d15729be18..6c086835ea 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml
@@ -940,7 +940,7 @@
Errore nell\'aggiornamento del link del gruppo
osservatore
Contatta l\'amministratore del gruppo.
- sei un osservatore
+ Non puoi inviare messaggi!
Sistema
Aggiungi messaggio di benvenuto
Messaggio di benvenuto
@@ -2391,52 +2391,4 @@
Off
Server preimpostati
Usa la porta TCP 443 solo per i server preimpostati.
- 1 chat con un membro
- %d chat
- %d chat con membri
- %d messaggi
- Salvare le impostazioni di ammissione?
- ha accettato %1$s
- ti ha accettato/a
- Attendi che i moderatori del gruppo revisionino la tua richiesta di entrare nel gruppo.
- hai accettato questo membro
- revisiona
- Ammissione del membro
- Nessuna chat con membri
- off
- Revisiona i membri
- Revisiona i membri prima di ammetterli (bussare).
- Accetta
- Chat con amministratori
- Rimuovi
- Il membro entrerà nel gruppo, accettarlo?
- revisionato dagli amministratori
- Accetta membro
- Chatta con gli amministratori
- Accetta come osservatore
- Un nuovo membro vuole entrare nel gruppo.
- tutti
- Chatta con il membro
- Chat con membri
- Errore di accettazione del membro
- Accetta come membro
- Imposta l\'ammissione dei membri
- Segnalazione inviata ai moderatori
- in attesa di revisione
- Puoi vedere le tue segnalazioni nella chat con gli amministratori.
- Non puoi inviare messaggi!
- contatto non pronto
- contatto eliminato
- contatto disattivato
- non sincronizzato
- richiesta di entrare rifiutata
- impossibile inviare messaggi
- il gruppo è eliminato
- il membro ha una versione vecchia
- rimosso dal gruppo
- sei uscito/a
- Eliminare la chat con il membro?
- Rifiutare il membro?
- Elimina chat
- Errore di eliminazione della chat con il membro
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
index dae44e06af..97742f82a8 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml
@@ -1019,7 +1019,7 @@
Сообщение будет удалено для всех членов группы.
Сообщение будет помечено как удаленное для всех членов группы.
Пожалуйста, свяжитесь с админом группы.
- Вы \"читатель\"
+ Вы не можете отправлять сообщения!
только чтение сообщений
читатель
Роль при вступлении
@@ -1132,7 +1132,7 @@
Ошибка расшифровки
Блокировка SimpleX не включена!
Ошибка хэш сообщения
- Хэш предыдущего сообщения отличается.
+ Хэш предыдущего сообщения отличается\"
Подтвердить код
Неправильный код
Заблокировать через
@@ -2467,58 +2467,4 @@
Частные разговоры, группы и Ваши контакты недоступны для операторов серверов.
Настроить операторов серверов
Политика конфиденциальности и условия использования.
- все
- Принять
- Участник хочет присоединиться к группе. Принять?
- группа удалена
- удален из группы
- %d чата(ов)
- контакт не готов
- контакт удален
- не синхронизирован
- запрос на вступление отклонён
- Новый участник хочет присоединиться к группе.
- Пожалуйста, подождите, пока модераторы группы рассмотрят ваш запрос на вступление.
- ожидает одобрения
- Отклонить
- Отклонить участника?
- Ошибка при удалении чата с членом группы
- Полная ссылка
- Ошибка вступления члена группы
- Ссылка не поддерживается
- Эта ссылка требует новую версию. Обновите приложение или попросите Ваш контакт прислать совместимую ссылку.
- %d сообщений
- Вы можете найти Ваши жалобы в Чате с админами.
- Чат с админами
- Чат с членом группы
- выключено
- Одобрять членов
- Чаты с членами группы
- Приём членов в группу
- Одобрять членов для вступления в группу.
- Нет чатов с членами группы
- Принять как читателя
- Принять в группу
- Принять члена
- одобрен админами
- Жалоба отправлена модераторам
- Вы вышли
- нельзя отправлять
- %d чатов с членами группы
- контакт выключен
- член имеет старую версию
- Вы не можете отправлять сообщения!
- Короткая ссылка
- Сохранить настройки вступления?
- Вы приняли этого члена
- рассмотрение
- Установить вступление в группу
- Удалить чат с членом группы?
- Удалить разговор
- принят %1$s
- Чат с админами
- Вы приняты
- 1 чат с членом группы
- SimpleX ссылка канала
- Короткие ссылки (БЕТА)
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
index a962298f19..549cb01b63 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml
@@ -459,7 +459,7 @@
Створити
без зашифрування e2e
контакт має зашифрування e2e
- Хеш попереднього повідомлення інший.
+ Хеш попереднього повідомлення інший.\"
Підтвердити пароль
Новий пароль
Перезапустити
diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
index e78878000c..28a5f6f50d 100644
--- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
+++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml
@@ -929,7 +929,7 @@
观察员
你是观察者
更新群链接错误
- 你是观察员
+ 你无法发送消息!
初始角色
请联系群管理员。
系统
@@ -2375,52 +2375,4 @@
关闭
预设服务器
仅预设服务器使用 TCP 协议 443 端口。
- 接受成员出错
- 举报已发送至 moderators
- %d 个聊天
- 和成员的 %d 个聊天
- %d 条消息
- 接受了 %1$s
- 接受了你
- 你接受了该成员
- 新成员要加入本群。
- 审核
- 待审核
- 全部
- 成员准入
- 关闭
- 删除
- 接受
- 和成员聊天
- 和管理员聊天
- 没有和成员的聊天
- 接受为成员
- 接受成员
- 成员将加入本群,接受成员吗?
- 由管理员审核
- 设置成员入群准许
- 和成员聊天
- 和管理员聊天
- 准许入群前审核成员(knocking)。
- 请等待群的 moderator 审核你加入该群的请求。
- 审核成员
- 保存入群设置?
- 你可以在和管理员和聊天中查看你的举报。
- 接受为观察员
- 和一名成员的一个聊天
- 无法发送消息
- 你离开了
- 删除和成员的聊天出错
- 你无法发送消息!
- 禁用了联系人
- 群被删除了
- 从群被删除了
- 加入请求被拒绝
- 删除聊天
- 删除和成员的聊天吗?
- 未同步
- 成员有旧版本
- 删除了联系人
- 联系人未就绪
- 拒绝成员?
diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt
index 52e845b422..3855835ab6 100644
--- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt
+++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.desktop.kt
@@ -63,7 +63,7 @@ actual fun UserPickerUsersSection(
ProfileImage(size = 55.dp, image = user.profile.image, color = iconColor)
if (u.unreadCount > 0 && !user.activeUser) {
- userUnreadBadge(u.unreadCount, user.showNtfs, true)
+ unreadBadge(u.unreadCount, user.showNtfs, true)
}
}
diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties
index db12d47dbd..aa4c7a7470 100644
--- a/apps/multiplatform/gradle.properties
+++ b/apps/multiplatform/gradle.properties
@@ -24,11 +24,13 @@ android.nonTransitiveRClass=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.jvm.target=11
-android.version_name=6.4-beta.1
-android.version_code=291
+android.version_name=6.3.6
+android.version_code=295
-desktop.version_name=6.4-beta.1
-desktop.version_code=103
+android.bundle=false
+
+desktop.version_name=6.3.6
+desktop.version_code=106
kotlin.version=1.9.23
gradle.plugin.version=8.2.0
diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs
index aa2374f919..faaccbd2bf 100644
--- a/apps/simplex-directory-service/src/Directory/Events.hs
+++ b/apps/simplex-directory-service/src/Directory/Events.hs
@@ -79,7 +79,7 @@ crDirectoryEvent_ = \case
CEvtJoinedGroupMember {groupInfo, member = m}
| pending m -> Just $ DEPendingMember groupInfo m
| otherwise -> Nothing
- CEvtNewChatItems {chatItems = AChatItem _ _ (GroupChat g _scopeInfo) ci : _} -> case ci of
+ CEvtNewChatItems {chatItems = AChatItem _ _ (GroupChat g) ci : _} -> case ci of
ChatItem {chatDir = CIGroupRcv m, content = CIRcvMsgContent (MCText t)} | pending m -> Just $ DEPendingMemberMsg g m (chatItemId' ci) t
_ -> Nothing
CEvtMemberRole {groupInfo, member, toRole}
diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs
index 4909189d40..4517ee9c5b 100644
--- a/apps/simplex-directory-service/src/Directory/Service.hs
+++ b/apps/simplex-directory-service/src/Directory/Service.hs
@@ -167,7 +167,7 @@ acceptMemberHook
when (useMemberFilter img $ rejectNames a) checkName
pure $
if
- | useMemberFilter img (passCaptcha a) -> (GAPendingApproval, GRMember)
+ | useMemberFilter img (passCaptcha a) -> (GAPending, GRMember)
| useMemberFilter img (makeObserver a) -> (GAAccepted, GRObserver)
| otherwise -> (GAAccepted, memberRole)
where
@@ -494,7 +494,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
[] -> textMsg
"" : _ -> textMsg
img : _ -> MCImage "" $ ImageData img
- sendCaptcha mc = sendComposedMessages_ cc (SRGroup groupId $ Just $ GCSMemberSupport (Just gmId)) [(quotedId, MCText noticeText), (Nothing, mc)]
+ sendCaptcha mc = sendComposedMessages_ cc (SRGroup groupId $ Just gmId) [(quotedId, MCText noticeText), (Nothing, mc)]
gmId = groupMemberId' m
approvePendingMember :: DirectoryMemberAcceptance -> GroupInfo -> GroupMember -> IO ()
@@ -503,11 +503,9 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
let role = if useMemberFilter image (makeObserver a) then GRObserver else maybe GRMember (\GroupLinkInfo {memberRole} -> memberRole) gli_
gmId = groupMemberId' m
sendChatCmd cc (APIAcceptMember groupId gmId role) >>= \case
- Right CRMemberAccepted {member} -> do
+ Right CRJoinedGroupMember {} -> do
atomically $ TM.delete gmId $ pendingCaptchas env
- if memberStatus member == GSMemPendingReview
- then logInfo $ "Member " <> viewName displayName <> " accepted and pending review, group " <> tshow groupId <> ":" <> viewGroupName g
- else logInfo $ "Member " <> viewName displayName <> " accepted, group " <> tshow groupId <> ":" <> viewGroupName g
+ logInfo $ "Member " <> viewName displayName <> " accepted, group " <> tshow groupId <> ":" <> viewGroupName g
r -> logError $ "unexpected accept member response: " <> tshow r
dePendingMemberMsg :: GroupInfo -> GroupMember -> ChatItemId -> Text -> IO ()
@@ -518,7 +516,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
Just PendingCaptcha {captchaText, sentAt, attempts}
| ts `diffUTCTime` sentAt > captchaTTL -> sendMemberCaptcha g m (Just ciId) captchaExpired $ attempts - 1
| matchCaptchaStr captchaText msgText -> do
- sendComposedMessages_ cc (SRGroup groupId $ Just $ GCSMemberSupport (Just $ groupMemberId' m)) [(Just ciId, MCText $ "Correct, you joined the group " <> n)]
+ sendComposedMessages_ cc (SRGroup groupId $ Just $ groupMemberId' m) [(Just ciId, MCText $ "Correct, you joined the group " <> n)]
approvePendingMember a g m
| attempts >= maxCaptchaAttempts -> rejectPendingMember tooManyAttempts
| otherwise -> sendMemberCaptcha g m (Just ciId) (wrongCaptcha attempts) attempts
@@ -528,7 +526,7 @@ directoryServiceEvent st opts@DirectoryOpts {adminUsers, superUsers, serviceName
a = groupMemberAcceptance g
rejectPendingMember rjctNotice = do
let gmId = groupMemberId' m
- sendComposedMessages cc (SRGroup groupId $ Just $ GCSMemberSupport (Just gmId)) [MCText rjctNotice]
+ sendComposedMessages cc (SRGroup groupId $ Just gmId) [MCText rjctNotice]
sendChatCmd cc (APIRemoveMembers groupId [gmId] False) >>= \case
Right (CRUserDeletedMembers _ _ (_ : _) _) -> do
atomically $ TM.delete gmId $ pendingCaptchas env
diff --git a/cabal.project b/cabal.project
index 48b75a86cd..f406b9820e 100644
--- a/cabal.project
+++ b/cabal.project
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package
type: git
location: https://github.com/simplex-chat/simplexmq.git
- tag: d352d518c2b3a42bc7a298954dde799422e1457f
+ tag: 3d62a383d5dcae6529d6d866233857182bcb4d47
source-repository-package
type: git
diff --git a/docs/rfcs/2025-03-07-group-knocking.md b/docs/rfcs/2025-03-07-group-knocking.md
deleted file mode 100644
index fedbbfaf72..0000000000
--- a/docs/rfcs/2025-03-07-group-knocking.md
+++ /dev/null
@@ -1,38 +0,0 @@
-# Group knocking
-
-## Problem
-
-In v6.3 release we added an option to "manually" approve members before introducing to group, based on decision made via `acceptMember` chat hook. Currently it's not supported in UI clients, and only used for directory service bot captcha challenge.
-
-The goal of next improvement is to let:
-- admins screen members before admitting to group, while not limiting communication with joining member to a single admin (and also removing the need for admin to be a highly available host of the group link);
-- and group owners set up other arbitrary automated challenges or entry rules, while still being able to advertise groups in directory service.
-
-## Solution
-
-Group link host (further host), knowing group requires admin approval, would initially only introduce member pending approval to admins. Admins can connect with member for screening, meanwhile host would be forwarding messages as usual between connecting members. As a result of screening, pending member can either be removed, or approved by admins.
-
-Upon acceptance, for further member connections to not depend on availability of admins, host should not only forward acceptance message, but also introduce remaining members to now accepted joining member. Respectively, admins' clients should not introduce members for approved members who are not their invitees.
-
-For group owners to be able to set up alternative automated challenges, these are some possible alternatives:
-- We could add a new role `Approver`, so that instead of adding all \[possibly human\] admins, host would initially introduce only approvers.
-- It could be an orthogonal to role member setting (would require protocol extension).
-- List of "approver" member IDs could be communicated to host client.
-
-### Implementation details draft
-
-Host needs to have knowledge whether to automatically accept, or only introduce admins/approvers.
-
-```sql
-ALTER TABLE group_profiles ADD COLUMN approval TEXT; -- comma separated member IDs; null - automatic introduction
-
--- or
-
-ALTER TABLE group_profiles ADD COLUMN approval INTEGER; -- if based on `Approver` role
-```
-
-Alternatively, a different extension of protocol could be done in order to communicate group approval rule from owner to host outside of group profile (special messages).
-
-Admins/approvers need to have separate conversation per pending member, requires adding scope to chat items.
-
-Host to have specific processing of forwarded `XGrpLinkAcpt` - continue introduction of remaining members.
diff --git a/images/privacy-guides.jpg b/images/privacy-guides.jpg
index f15a8862f7..5876d10c02 100644
Binary files a/images/privacy-guides.jpg and b/images/privacy-guides.jpg differ
diff --git a/images/whonix-logo.jpg b/images/whonix-logo.jpg
new file mode 100644
index 0000000000..c6fc4729b2
Binary files /dev/null and b/images/whonix-logo.jpg differ
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index 68a6054ef0..84f9d0db34 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."d352d518c2b3a42bc7a298954dde799422e1457f" = "1rha84pfpaqx3mf218szkfra334vhijqf17hanxqmp1sicfbf1x3";
+ "https://github.com/simplex-chat/simplexmq.git"."3d62a383d5dcae6529d6d866233857182bcb4d47" = "133xm8jkim7agd6drwm3lbx1z7v8nf4l3asrm46ag3n2q201yfxc";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
diff --git a/simplex-chat.cabal b/simplex-chat.cabal
index 553f2ec6cd..96b2f941c7 100644
--- a/simplex-chat.cabal
+++ b/simplex-chat.cabal
@@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack
name: simplex-chat
-version: 6.4.0.2
+version: 6.3.6.0
category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat
@@ -105,7 +105,6 @@ library
Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links
Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission
- Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope
else
exposed-modules:
Simplex.Chat.Archive
@@ -237,7 +236,6 @@ library
Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links
Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission
- Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope
other-modules:
Paths_simplex_chat
hs-source-dirs:
diff --git a/src/Simplex/Chat/Bot.hs b/src/Simplex/Chat/Bot.hs
index 9b92c8b800..73a2970c61 100644
--- a/src/Simplex/Chat/Bot.hs
+++ b/src/Simplex/Chat/Bot.hs
@@ -95,7 +95,7 @@ deleteMessage cc ct chatItemId = do
r -> putStrLn $ "unexpected delete message response: " <> show r
contactRef :: Contact -> ChatRef
-contactRef ct = ChatRef CTDirect (contactId' ct) Nothing
+contactRef = ChatRef CTDirect . contactId'
printLog :: ChatController -> ChatLogLevel -> String -> IO ()
printLog cc level s
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index 97bf26fd84..02e2e121b3 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -360,14 +360,11 @@ data ChatCommand
| APIAddMember GroupId ContactId GroupMemberRole
| APIJoinGroup {groupId :: GroupId, enableNtfs :: MsgFilter}
| APIAcceptMember GroupId GroupMemberId GroupMemberRole
- | APIDeleteMemberSupportChat GroupId GroupMemberId
| APIMembersRole GroupId (NonEmpty GroupMemberId) GroupMemberRole
| APIBlockMembersForAll GroupId (NonEmpty GroupMemberId) Bool
| APIRemoveMembers {groupId :: GroupId, groupMemberIds :: Set GroupMemberId, withMessages :: Bool}
| APILeaveGroup GroupId
| APIListMembers GroupId
- -- | APIDeleteGroupConversations GroupId (NonEmpty GroupConversationId)
- -- | APIArchiveGroupConversations GroupId (NonEmpty GroupConversationId)
| APIUpdateGroupProfile GroupId GroupProfile
| APICreateGroupLink GroupId GroupMemberRole CreateShortLink
| APIGroupLinkMemberRole GroupId GroupMemberRole
@@ -470,7 +467,7 @@ data ChatCommand
| ForwardMessage {toChatName :: ChatName, fromContactName :: ContactName, forwardedMsg :: Text}
| ForwardGroupMessage {toChatName :: ChatName, fromGroupName :: GroupName, fromMemberName_ :: Maybe ContactName, forwardedMsg :: Text}
| ForwardLocalMessage {toChatName :: ChatName, forwardedMsg :: Text}
- | SendMessage SendName Text
+ | SendMessage ChatName Text
| SendMemberContactMessage GroupName ContactName Text
| SendLiveMessage ChatName Text
| SendMessageQuote {contactName :: ContactName, msgDir :: AMsgDirection, quotedMsg :: Text, message :: Text}
@@ -484,7 +481,6 @@ data ChatCommand
| NewGroup IncognitoEnabled GroupProfile
| AddMember GroupName ContactName GroupMemberRole
| JoinGroup {groupName :: GroupName, enableNtfs :: MsgFilter}
- | AcceptMember GroupName ContactName GroupMemberRole
| MemberRole GroupName ContactName GroupMemberRole
| BlockForAll GroupName ContactName Bool
| RemoveMembers {groupName :: GroupName, members :: Set ContactName, withMessages :: Bool}
@@ -492,7 +488,6 @@ data ChatCommand
| DeleteGroup GroupName
| ClearGroup GroupName
| ListMembers GroupName
- | ListMemberSupportChats GroupName
| APIListGroups UserId (Maybe ContactId) (Maybe String)
| ListGroups (Maybe ContactName) (Maybe String)
| UpdateGroupNames GroupName GroupProfile
@@ -528,7 +523,6 @@ data ChatCommand
| SetContactFeature AChatFeature ContactName (Maybe FeatureAllowed)
| SetGroupFeature AGroupFeatureNoRole GroupName GroupFeatureEnabled
| SetGroupFeatureRole AGroupFeatureRole GroupName GroupFeatureEnabled (Maybe GroupMemberRole)
- | SetGroupMemberAdmissionReview GroupName (Maybe MemberCriteria)
| SetUserTimedMessages Bool -- UserId (not used in UI)
| SetContactTimedMessages ContactName (Maybe TimedMessagesEnabled)
| SetGroupTimedMessages GroupName (Maybe Int)
@@ -632,9 +626,9 @@ data ChatResponse
| CRGroupMemberInfo {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats}
| CRQueueInfo {user :: User, rcvMsgInfo :: Maybe RcvMsgInfo, queueInfo :: ServerQueueInfo}
| CRContactSwitchStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
- | CEvtGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
+ | CRGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
| CRContactSwitchAborted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
- | CEvtGroupMemberSwitchAborted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
+ | CRGroupMemberSwitchAborted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
| CRContactRatchetSyncStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats}
| CRGroupMemberRatchetSyncStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats}
| CRContactCode {user :: User, contact :: Contact, connectionCode :: Text}
@@ -654,9 +648,6 @@ data ChatResponse
| CRWelcome {user :: User}
| CRGroupCreated {user :: User, groupInfo :: GroupInfo}
| CRGroupMembers {user :: User, group :: Group}
- | CRMemberSupportChats {user :: User, groupInfo :: GroupInfo, members :: [GroupMember]}
- -- | CRGroupConversationsArchived {user :: User, groupInfo :: GroupInfo, archivedGroupConversations :: [GroupConversation]}
- -- | CRGroupConversationsDeleted {user :: User, groupInfo :: GroupInfo, deletedGroupConversations :: [GroupConversation]}
| CRContactsList {user :: User, contacts :: [Contact]}
| CRUserContactLink {user :: User, contactLink :: UserContactLink}
| CRUserContactLinkUpdated {user :: User, contactLink :: UserContactLink}
@@ -678,7 +669,6 @@ data ChatResponse
| CRSentConfirmation {user :: User, connection :: PendingContactConnection}
| CRSentInvitation {user :: User, connection :: PendingContactConnection, customUserProfile :: Maybe Profile}
| CRSentInvitationToContact {user :: User, contact :: Contact, customUserProfile :: Maybe Profile}
- | CRItemsReadForChat {user :: User, chatInfo :: AChatInfo}
| CRContactDeleted {user :: User, contact :: Contact}
| CRChatCleared {user :: User, chatInfo :: AChatInfo}
| CRUserContactLinkCreated {user :: User, connLinkContact :: CreatedLinkContact}
@@ -704,8 +694,6 @@ data ChatResponse
| CRContactPrefsUpdated {user :: User, fromContact :: Contact, toContact :: Contact}
| CRNetworkStatuses {user_ :: Maybe User, networkStatuses :: [ConnNetworkStatus]}
| CRJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
- | CRMemberAccepted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
- | CRMemberSupportChatDeleted {user :: User, groupInfo :: GroupInfo, member :: GroupMember}
| CRMembersRoleUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], toRole :: GroupMemberRole}
| CRMembersBlockedForAllUser {user :: User, groupInfo :: GroupInfo, members :: [GroupMember], blocked :: Bool}
| CRGroupUpdated {user :: User, fromGroup :: GroupInfo, toGroup :: GroupInfo, member_ :: Maybe GroupMember}
@@ -814,7 +802,6 @@ data ChatEvent
| CEvtUserJoinedGroup {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember}
| CEvtJoinedGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember} -- there is the same command response
| CEvtJoinedGroupMemberConnecting {user :: User, groupInfo :: GroupInfo, hostMember :: GroupMember, member :: GroupMember}
- | CEvtMemberAcceptedByOther {user :: User, groupInfo :: GroupInfo, acceptingMember :: GroupMember, member :: GroupMember}
| CEvtMemberRole {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, fromRole :: GroupMemberRole, toRole :: GroupMemberRole}
| CEvtMemberBlockedForAll {user :: User, groupInfo :: GroupInfo, byMember :: GroupMember, member :: GroupMember, blocked :: Bool}
| CEvtConnectedToGroupMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, memberContact :: Maybe Contact}
@@ -841,7 +828,7 @@ data ChatEvent
| CEvtRemoteHostConnected {remoteHost :: RemoteHostInfo}
| CEvtRemoteHostStopped {remoteHostId_ :: Maybe RemoteHostId, rhsState :: RemoteHostSessionState, rhStopReason :: RemoteHostStopReason}
| CEvtRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo, ctrlAppInfo_ :: Maybe CtrlAppInfo, appVersion :: AppVersion, compatible :: Bool}
- | CEvtRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text}
+ | CEvtRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text}
| CEvtRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason}
| CEvtContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption}
| CEvtContactDisabled {user :: User, contact :: Contact}
@@ -912,15 +899,16 @@ logEventToFile = \case
_ -> False
_ -> False
+-- (Maybe GroupMemberId) can later be changed to GroupSndScope = GSSAll | GSSAdmins | GSSMember GroupMemberId
data SendRef
= SRDirect ContactId
- | SRGroup GroupId (Maybe GroupChatScope)
+ | SRGroup GroupId (Maybe GroupMemberId)
deriving (Eq, Show)
sendToChatRef :: SendRef -> ChatRef
sendToChatRef = \case
- SRDirect cId -> ChatRef CTDirect cId Nothing
- SRGroup gId scope -> ChatRef CTGroup gId scope
+ SRDirect cId -> ChatRef CTDirect cId
+ SRGroup gId _ -> ChatRef CTGroup gId
data ChatPagination
= CPLast Int
diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs
index 20f9468cd6..8c475b111b 100644
--- a/src/Simplex/Chat/Library/Commands.hs
+++ b/src/Simplex/Chat/Library/Commands.hs
@@ -27,7 +27,6 @@ import Control.Monad.Reader
import qualified Data.Aeson as J
import Data.Attoparsec.ByteString.Char8 (Parser)
import qualified Data.Attoparsec.ByteString.Char8 as A
-import qualified Data.Attoparsec.Combinator as A
import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (ByteString)
import qualified Data.ByteString.Char8 as B
@@ -501,17 +500,17 @@ processChatCommand' vr = \case
(errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user pendingConnections pagination query)
unless (null errs) $ toView $ CEvtChatErrors (map ChatErrorStore errs)
pure $ CRApiChats user previews
- APIGetChat (ChatRef cType cId scope_) contentFilter pagination search -> withUser $ \user -> case cType of
+ APIGetChat (ChatRef cType cId) contentFilter pagination search -> withUser $ \user -> case cType of
-- TODO optimize queries calculating ChatStats, currently they're disabled
CTDirect -> do
- when (isJust contentFilter) $ throwCmdError "content filter not supported"
+ when (isJust contentFilter) $ throwChatError $ CECommandError "content filter not supported"
(directChat, navInfo) <- withFastStore (\db -> getDirectChat db vr user cId pagination search)
pure $ CRApiChat user (AChat SCTDirect directChat) navInfo
CTGroup -> do
- (groupChat, navInfo) <- withFastStore (\db -> getGroupChat db vr user cId scope_ contentFilter pagination search)
+ (groupChat, navInfo) <- withFastStore (\db -> getGroupChat db vr user cId contentFilter pagination search)
pure $ CRApiChat user (AChat SCTGroup groupChat) navInfo
CTLocal -> do
- when (isJust contentFilter) $ throwCmdError "content filter not supported"
+ when (isJust contentFilter) $ throwChatError $ CECommandError "content filter not supported"
(localChat, navInfo) <- withFastStore (\db -> getLocalChat db user cId pagination search)
pure $ CRApiChat user (AChat SCTLocal localChat) navInfo
CTContactRequest -> throwCmdError "not implemented"
@@ -532,30 +531,29 @@ processChatCommand' vr = \case
getForwardedFromItem :: User -> ChatItem c d -> CM (Maybe AChatItem)
getForwardedFromItem user ChatItem {meta = CIMeta {itemForwarded}} = case itemForwarded of
Just (CIFFContact _ _ (Just ctId) (Just fwdItemId)) ->
- Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTDirect ctId Nothing) fwdItemId)
+ Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTDirect ctId) fwdItemId)
Just (CIFFGroup _ _ (Just gId) (Just fwdItemId)) ->
- -- TODO [knocking] getAChatItem doesn't differentiate how to read based on scope - it should, instead of using group filter
- Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTGroup gId Nothing) fwdItemId)
+ Just <$> withFastStore (\db -> getAChatItem db vr user (ChatRef CTGroup gId) fwdItemId)
_ -> pure Nothing
APISendMessages sendRef live itemTTL cms -> withUser $ \user -> mapM_ assertAllowedContent' cms >> case sendRef of
SRDirect chatId -> do
mapM_ assertNoMentions cms
withContactLock "sendMessage" chatId $
sendContactContentMessages user chatId live itemTTL (L.map composedMessageReq cms)
- SRGroup chatId gsScope ->
+ SRGroup chatId directMemId_ ->
withGroupLock "sendMessage" chatId $ do
(gInfo, cmrs) <- withFastStore $ \db -> do
g <- getGroupInfo db vr user chatId
(g,) <$> mapM (composedMessageReqMentions db user g) cms
- sendGroupContentMessages user gInfo gsScope live itemTTL cmrs
+ sendGroupContentMessages user gInfo directMemId_ live itemTTL cmrs
APICreateChatTag (ChatTagData emoji text) -> withUser $ \user -> withFastStore' $ \db -> do
_ <- createChatTag db user emoji text
CRChatTags user <$> getUserChatTags db user
- APISetChatTags (ChatRef cType chatId scope) tagIds -> withUser $ \user -> case cType of
+ APISetChatTags (ChatRef cType chatId) tagIds -> withUser $ \user -> case cType of
CTDirect -> withFastStore' $ \db -> do
updateDirectChatTags db chatId (maybe [] L.toList tagIds)
CRTagsUpdated user <$> getUserChatTags db user <*> getDirectChatTags db chatId
- CTGroup | isNothing scope -> withFastStore' $ \db -> do
+ CTGroup -> withFastStore' $ \db -> do
updateGroupChatTags db chatId (maybe [] L.toList tagIds)
CRTagsUpdated user <$> getUserChatTags db user <*> getGroupChatTags db chatId
_ -> throwCmdError "not supported"
@@ -573,18 +571,26 @@ processChatCommand' vr = \case
createNoteFolderContentItems user folderId (L.map composedMessageReq cms)
APIReportMessage gId reportedItemId reportReason reportText -> withUser $ \user ->
withGroupLock "reportMessage" gId $ do
- gInfo <- withFastStore $ \db -> getGroupInfo db vr user gId
- let mc = MCReport reportText reportReason
+ (gInfo, ms) <-
+ withFastStore $ \db -> do
+ gInfo <- getGroupInfo db vr user gId
+ (gInfo,) <$> liftIO (getGroupModerators db vr user gInfo)
+ let ms' = filter compatibleModerator ms
+ mc = MCReport reportText reportReason
cm = ComposedMessage {fileSource = Nothing, quotedItemId = Just reportedItemId, msgContent = mc, mentions = M.empty}
- -- TODO [knocking] reports sent to support scope may be wrong
- sendGroupContentMessages user gInfo (Just $ GCSMemberSupport Nothing) False Nothing [composedMessageReq cm]
+ when (null ms') $ throwChatError $ CECommandError "no moderators support receiving reports"
+ let numFileInvs = length $ filter memberCurrent ms'
+ sendGroupContentMessages_ user gInfo Nothing ms' numFileInvs False Nothing [composedMessageReq cm]
+ where
+ compatibleModerator GroupMember {activeConn, memberChatVRange} =
+ maxVersion (maybe memberChatVRange peerChatVRange activeConn) >= contentReportsVersion
ReportMessage {groupName, contactName_, reportReason, reportedMessage} -> withUser $ \user -> do
gId <- withFastStore $ \db -> getGroupIdByName db user groupName
reportedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user gId contactName_ reportedMessage
processChatCommand $ APIReportMessage gId reportedItemId reportReason ""
- APIUpdateChatItem (ChatRef cType chatId scope) itemId live (UpdatedMessage mc mentions) -> withUser $ \user -> assertAllowedContent mc >> case cType of
+ APIUpdateChatItem (ChatRef cType chatId) itemId live (UpdatedMessage mc mentions) -> withUser $ \user -> assertAllowedContent mc >> case cType of
CTDirect -> withContactLock "updateChatItem" chatId $ do
- unless (null mentions) $ throwCmdError "mentions are not supported in this chat"
+ unless (null mentions) $ throwChatError $ CECommandError "mentions are not supported in this chat"
ct@Contact {contactId} <- withFastStore $ \db -> getContact db vr user chatId
assertDirectAllowed user MDSnd ct XMsgUpdate_
cci <- withFastStore $ \db -> getDirectCIWithReactions db user ct itemId
@@ -595,41 +601,37 @@ processChatCommand' vr = \case
let changed = mc /= oldMC
if changed || fromMaybe False itemLive
then do
- let event = XMsgUpdate itemSharedMId mc M.empty (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive) Nothing
- (SndMessage {msgId}, _) <- sendDirectContactMessage user ct event
+ (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XMsgUpdate itemSharedMId mc M.empty (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive))
ci' <- withFastStore' $ \db -> do
currentTs <- liftIO getCurrentTime
when changed $
addInitialAndNewCIVersions db itemId (chatItemTs' ci, oldMC) (currentTs, mc)
let edited = itemLive /= Just True
updateDirectChatItem' db user contactId ci (CISndMsgContent mc) edited live Nothing $ Just msgId
- startUpdatedTimedItemThread user (ChatRef CTDirect contactId Nothing) ci ci'
+ startUpdatedTimedItemThread user (ChatRef CTDirect contactId) ci ci'
pure $ CRChatItemUpdated user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci')
else pure $ CRChatItemNotChanged user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci)
_ -> throwChatError CEInvalidChatItemUpdate
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
CTGroup -> withGroupLock "updateChatItem" chatId $ do
- gInfo@GroupInfo {groupId, membership} <- withFastStore $ \db -> getGroupInfo db vr user chatId
- when (isNothing scope) $ assertUserGroupRole gInfo GRAuthor
+ Group gInfo@GroupInfo {groupId, membership} ms <- withFastStore $ \db -> getGroup db vr user chatId
+ assertUserGroupRole gInfo GRAuthor
let (_, ft_) = msgContentTexts mc
if prohibitedSimplexLinks gInfo membership ft_
then throwCmdError ("feature not allowed " <> T.unpack (groupFeatureNameText GFSimplexLinks))
else do
- -- TODO [knocking] check chat item scope?
cci <- withFastStore $ \db -> getGroupCIWithReactions db user gInfo itemId
case cci of
CChatItem SMDSnd ci@ChatItem {meta = CIMeta {itemSharedMsgId, itemTimed, itemLive, editable}, content = ciContent} -> do
case (ciContent, itemSharedMsgId, editable) of
(CISndMsgContent oldMC, Just itemSharedMId, True) -> do
- (chatScopeInfo, recipients) <- getGroupRecipients vr user gInfo scope groupKnockingVersion
let changed = mc /= oldMC
if changed || fromMaybe False itemLive
then do
ciMentions <- withFastStore $ \db -> getCIMentions db user gInfo ft_ mentions
- let msgScope = toMsgScope gInfo <$> chatScopeInfo
- mentions' = M.map (\CIMention {memberId} -> MsgMention {memberId}) ciMentions
- event = XMsgUpdate itemSharedMId mc mentions' (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive) msgScope
- SndMessage {msgId} <- sendGroupMessage user gInfo scope recipients event
+ let mentions' = M.map (\CIMention {memberId} -> MsgMention {memberId}) ciMentions
+ -- TODO [knocking] send separately to pending approval member
+ SndMessage {msgId} <- sendGroupMessage user gInfo ms (XMsgUpdate itemSharedMId mc mentions' (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive))
ci' <- withFastStore' $ \db -> do
currentTs <- liftIO getCurrentTime
when changed $
@@ -637,13 +639,13 @@ processChatCommand' vr = \case
let edited = itemLive /= Just True
ci' <- updateGroupChatItem db user groupId ci (CISndMsgContent mc) edited live $ Just msgId
updateGroupCIMentions db gInfo ci' ciMentions
- startUpdatedTimedItemThread user (ChatRef CTGroup groupId scope) ci ci'
- pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo chatScopeInfo) ci')
- else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo chatScopeInfo) ci)
+ startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
+ pure $ CRChatItemUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci')
+ else pure $ CRChatItemNotChanged user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci)
_ -> throwChatError CEInvalidChatItemUpdate
CChatItem SMDRcv _ -> throwChatError CEInvalidChatItemUpdate
CTLocal -> do
- unless (null mentions) $ throwCmdError "mentions are not supported in this chat"
+ unless (null mentions) $ throwChatError $ CECommandError "mentions are not supported in this chat"
(nf@NoteFolder {noteFolderId}, cci) <- withFastStore $ \db -> (,) <$> getNoteFolder db user chatId <*> getLocalChatItem db user chatId itemId
case cci of
CChatItem SMDSnd ci@ChatItem {content = CISndMsgContent oldMC}
@@ -656,7 +658,7 @@ processChatCommand' vr = \case
_ -> throwChatError CEInvalidChatItemUpdate
CTContactRequest -> throwCmdError "not supported"
CTContactConnection -> throwCmdError "not supported"
- APIDeleteChatItem (ChatRef cType chatId scope) itemIds mode -> withUser $ \user -> case cType of
+ APIDeleteChatItem (ChatRef cType chatId) itemIds mode -> withUser $ \user -> case cType of
CTDirect -> withContactLock "deleteChatItem" chatId $ do
(ct, items) <- getCommandDirectChatItems user chatId itemIds
deletions <- case mode of
@@ -675,22 +677,18 @@ processChatCommand' vr = \case
pure $ CRChatItemsDeleted user deletions True False
CTGroup -> withGroupLock "deleteChatItem" chatId $ do
(gInfo, items) <- getCommandGroupChatItems user chatId itemIds
- -- TODO [knocking] check scope for all items?
deletions <- case mode of
- CIDMInternal -> do
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- deleteGroupCIs user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
- CIDMInternalMark -> do
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- markGroupCIsDeleted user gInfo chatScopeInfo items Nothing =<< liftIO getCurrentTime
+ CIDMInternal -> deleteGroupCIs user gInfo items Nothing =<< liftIO getCurrentTime
+ CIDMInternalMark -> markGroupCIsDeleted user gInfo items Nothing =<< liftIO getCurrentTime
CIDMBroadcast -> do
- (chatScopeInfo, recipients) <- getGroupRecipients vr user gInfo scope groupKnockingVersion
+ ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
assertDeletable items
assertUserGroupRole gInfo GRObserver -- can still delete messages sent earlier
let msgIds = itemsMsgIds items
events = L.nonEmpty $ map (`XMsgDel` Nothing) msgIds
- mapM_ (sendGroupMessages user gInfo Nothing recipients) events
- delGroupChatItems user gInfo chatScopeInfo items False
+ -- TODO [knocking] validate: only current members or only single pending approval member
+ mapM_ (sendGroupMessages user gInfo ms) events
+ delGroupChatItems user gInfo items False
pure $ CRChatItemsDeleted user deletions True False
CTLocal -> do
(nf, items) <- getCommandLocalChatItems user chatId itemIds
@@ -713,10 +711,8 @@ processChatCommand' vr = \case
itemsMsgIds = mapMaybe (\(CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId}}) -> itemSharedMsgId)
APIDeleteMemberChatItem gId itemIds -> withUser $ \user -> withGroupLock "deleteChatItem" gId $ do
(gInfo, items) <- getCommandGroupChatItems user gId itemIds
- -- TODO [knocking] check scope is Nothing for all items? (prohibit moderation in support chats?)
ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
- let recipients = filter memberCurrent ms
- deletions <- delGroupChatItemsForMembers user gInfo Nothing recipients items
+ deletions <- delGroupChatItemsForMembers user gInfo ms items
pure $ CRChatItemsDeleted user deletions True False
APIArchiveReceivedReports gId -> withUser $ \user -> withFastStore $ \db -> do
g <- getGroupInfo db vr user gId
@@ -725,30 +721,27 @@ processChatCommand' vr = \case
pure $ CRGroupChatItemsDeleted user g ciIds True (Just $ membership g)
APIDeleteReceivedReports gId itemIds mode -> withUser $ \user -> withGroupLock "deleteReports" gId $ do
(gInfo, items) <- getCommandGroupChatItems user gId itemIds
- unless (all isRcvReport items) $ throwCmdError "some items are not received reports"
- -- TODO [knocking] scope can be different for each item if reports are from different members
- -- TODO (currently we pass Nothing as scope which is wrong)
+ unless (all isRcvReport items) $ throwChatError $ CECommandError "some items are not received reports"
deletions <- case mode of
- CIDMInternal -> deleteGroupCIs user gInfo Nothing items Nothing =<< liftIO getCurrentTime
- CIDMInternalMark -> markGroupCIsDeleted user gInfo Nothing items Nothing =<< liftIO getCurrentTime
+ CIDMInternal -> deleteGroupCIs user gInfo items Nothing =<< liftIO getCurrentTime
+ CIDMInternalMark -> markGroupCIsDeleted user gInfo items Nothing =<< liftIO getCurrentTime
CIDMBroadcast -> do
ms <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
- let recipients = filter memberCurrent ms
- delGroupChatItemsForMembers user gInfo Nothing recipients items
+ delGroupChatItemsForMembers user gInfo ms items
pure $ CRChatItemsDeleted user deletions True False
where
isRcvReport = \case
CChatItem _ ChatItem {content = CIRcvMsgContent (MCReport {})} -> True
_ -> False
- APIChatItemReaction (ChatRef cType chatId scope) itemId add reaction -> withUser $ \user -> case cType of
+ APIChatItemReaction (ChatRef cType chatId) itemId add reaction -> withUser $ \user -> case cType of
CTDirect ->
withContactLock "chatItemReaction" chatId $
withFastStore (\db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId) >>= \case
(ct, CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}}) -> do
unless (featureAllowed SCFReactions forUser ct) $
- throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions)
+ throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions))
unless (ciReactionAllowed ci) $
- throwCmdError "reaction not allowed - chat item has no content"
+ throwChatError (CECommandError "reaction not allowed - chat item has no content")
rs <- withFastStore' $ \db -> getDirectReactions db ct itemSharedMId True
checkReactionAllowed rs
(SndMessage {msgId}, _) <- sendDirectContactMessage user ct $ XMsgReact itemSharedMId Nothing reaction add
@@ -759,48 +752,46 @@ processChatCommand' vr = \case
let ci' = CChatItem md ci {reactions}
r = ACIReaction SCTDirect SMDSnd (DirectChat ct) $ CIReaction CIDirectSnd ci' createdAt reaction
pure $ CRChatItemReaction user add r
- _ -> throwCmdError "reaction not possible - no shared item ID"
+ _ -> throwChatError $ CECommandError "reaction not possible - no shared item ID"
CTGroup ->
withGroupLock "chatItemReaction" chatId $ do
- -- TODO [knocking] check chat item scope?
- (g@GroupInfo {membership}, CChatItem md ci) <- withFastStore $ \db -> do
- g <- getGroupInfo db vr user chatId
- (g,) <$> getGroupCIWithReactions db user g itemId
- (chatScopeInfo, recipients) <- getGroupRecipients vr user g scope groupKnockingVersion
+ (Group g@GroupInfo {membership} ms, CChatItem md ci) <- withFastStore $ \db -> do
+ gr@(Group g _) <- getGroup db vr user chatId
+ (gr,) <$> getGroupCIWithReactions db user g itemId
case ci of
ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} -> do
unless (groupFeatureAllowed SGFReactions g) $
- throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions)
+ throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions))
unless (ciReactionAllowed ci) $
- throwCmdError "reaction not allowed - chat item has no content"
+ throwChatError (CECommandError "reaction not allowed - chat item has no content")
let GroupMember {memberId = itemMemberId} = chatItemMember g ci
rs <- withFastStore' $ \db -> getGroupReactions db g membership itemMemberId itemSharedMId True
checkReactionAllowed rs
- SndMessage {msgId} <- sendGroupMessage user g scope recipients (XMsgReact itemSharedMId (Just itemMemberId) reaction add)
+ -- TODO [knocking] send separately to pending approval member
+ SndMessage {msgId} <- sendGroupMessage user g ms (XMsgReact itemSharedMId (Just itemMemberId) reaction add)
createdAt <- liftIO getCurrentTime
reactions <- withFastStore' $ \db -> do
setGroupReaction db g membership itemMemberId itemSharedMId True reaction add msgId createdAt
liftIO $ getGroupCIReactions db g itemMemberId itemSharedMId
let ci' = CChatItem md ci {reactions}
- r = ACIReaction SCTGroup SMDSnd (GroupChat g chatScopeInfo) $ CIReaction CIGroupSnd ci' createdAt reaction
+ r = ACIReaction SCTGroup SMDSnd (GroupChat g) $ CIReaction CIGroupSnd ci' createdAt reaction
pure $ CRChatItemReaction user add r
- _ -> throwCmdError "invalid reaction"
+ _ -> throwChatError $ CECommandError "reaction not possible - no shared item ID"
CTLocal -> throwCmdError "not supported"
CTContactRequest -> throwCmdError "not supported"
CTContactConnection -> throwCmdError "not supported"
where
checkReactionAllowed rs = do
when ((reaction `elem` rs) == add) $
- throwCmdError $ "reaction already " <> if add then "added" else "removed"
+ throwChatError (CECommandError $ "reaction already " <> if add then "added" else "removed")
when (add && length rs >= maxMsgReactions) $
- throwCmdError "too many reactions"
+ throwChatError (CECommandError "too many reactions")
APIGetReactionMembers userId groupId itemId reaction -> withUserId userId $ \user -> do
memberReactions <- withStore $ \db -> do
CChatItem _ ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}} <- getGroupChatItem db user groupId itemId
liftIO $ getReactionMembers db vr user groupId itemSharedMId reaction
pure $ CRReactionMembers user memberReactions
- -- TODO [knocking] forward from scope?
- APIPlanForwardChatItems (ChatRef fromCType fromChatId _scope) itemIds -> withUser $ \user -> case fromCType of
+ APIPlanForwardChatItems (ChatRef fromCType fromChatId) itemIds -> withUser $ \user -> case fromCType of
CTDirect -> planForward user . snd =<< getCommandDirectChatItems user fromChatId itemIds
CTGroup -> planForward user . snd =<< getCommandGroupChatItems user fromChatId itemIds
CTLocal -> planForward user . snd =<< getCommandLocalChatItems user fromChatId itemIds
@@ -846,8 +837,7 @@ processChatCommand' vr = \case
MCFile t -> t /= ""
MCReport {} -> True
MCUnknown {} -> True
- -- TODO [knocking] forward from / to scope
- APIForwardChatItems toChat@(ChatRef toCType toChatId toScope) fromChat@(ChatRef fromCType fromChatId _fromScope) itemIds itemTTL -> withUser $ \user -> case toCType of
+ APIForwardChatItems toChat@(ChatRef toCType toChatId) fromChat@(ChatRef fromCType fromChatId) itemIds itemTTL -> withUser $ \user -> case toCType of
CTDirect -> do
cmrs <- prepareForward user
case L.nonEmpty cmrs of
@@ -861,7 +851,7 @@ processChatCommand' vr = \case
Just cmrs' ->
withGroupLock "forwardChatItem, to group" toChatId $ do
gInfo <- withFastStore $ \db -> getGroupInfo db vr user toChatId
- sendGroupContentMessages user gInfo toScope False itemTTL cmrs'
+ sendGroupContentMessages user gInfo Nothing False itemTTL cmrs'
Nothing -> pure $ CRNewChatItems user []
CTLocal -> do
cmrs <- prepareForward user
@@ -888,7 +878,6 @@ processChatCommand' vr = \case
forwardName Contact {profile = LocalProfile {displayName, localAlias}}
| localAlias /= "" = localAlias
| otherwise = displayName
- -- TODO [knocking] from scope?
CTGroup -> withGroupLock "forwardChatItem, from group" fromChatId $ do
(gInfo, items) <- getCommandGroupChatItems user fromChatId itemIds
catMaybes <$> mapM (\ci -> ciComposeMsgReq gInfo ci <$$> prepareMsgReq ci) items
@@ -914,8 +903,8 @@ processChatCommand' vr = \case
ciComposeMsgReq (CChatItem _ ci) (mc', file) =
let ciff = forwardCIFF ci Nothing
in (composedMessage file mc', ciff, msgContentTexts mc', M.empty)
- CTContactRequest -> throwCmdError "not supported"
- CTContactConnection -> throwCmdError "not supported"
+ CTContactRequest -> throwChatError $ CECommandError "not supported"
+ CTContactConnection -> throwChatError $ CECommandError "not supported"
where
prepareMsgReq :: CChatItem c -> CM (Maybe (MsgContent, Maybe CryptoFile))
prepareMsgReq (CChatItem _ ci) = forwardMsgContent ci $>>= forwardContent ci
@@ -988,7 +977,7 @@ processChatCommand' vr = \case
pure $ prefix <> formattedDate <> ext
APIUserRead userId -> withUserId userId $ \user -> withFastStore' (`setUserChatsRead` user) >> ok user
UserRead -> withUser $ \User {userId} -> processChatCommand $ APIUserRead userId
- APIChatRead chatRef@(ChatRef cType chatId scope) -> withUser $ \_ -> case cType of
+ APIChatRead chatRef@(ChatRef cType chatId) -> withUser $ \_ -> case cType of
CTDirect -> do
user <- withFastStore $ \db -> getUserByContactId db chatId
ts <- liftIO getCurrentTime
@@ -999,14 +988,11 @@ processChatCommand' vr = \case
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
ok user
CTGroup -> do
- (user, gInfo) <- withFastStore $ \db -> do
- user <- getUserByGroupId db chatId
- gInfo <- getGroupInfo db vr user chatId
- pure (user, gInfo)
+ user <- withFastStore $ \db -> getUserByGroupId db chatId
ts <- liftIO getCurrentTime
timedItems <- withFastStore' $ \db -> do
timedItems <- getGroupUnreadTimedItems db user chatId
- updateGroupChatItemsRead db user gInfo scope
+ updateGroupChatItemsRead db user chatId
setGroupChatItemsDeleteAt db user chatId timedItems ts
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
ok user
@@ -1016,40 +1002,31 @@ processChatCommand' vr = \case
ok user
CTContactRequest -> throwCmdError "not supported"
CTContactConnection -> throwCmdError "not supported"
- APIChatItemsRead chatRef@(ChatRef cType chatId scope) itemIds -> withUser $ \_ -> case cType of
+ APIChatItemsRead chatRef@(ChatRef cType chatId) itemIds -> withUser $ \_ -> case cType of
CTDirect -> do
- (user, ct) <- withFastStore $ \db -> do
- user <- getUserByContactId db chatId
- ct <- getContact db vr user chatId
- pure (user, ct)
+ user <- withFastStore $ \db -> getUserByContactId db chatId
timedItems <- withFastStore' $ \db -> do
timedItems <- updateDirectChatItemsReadList db user chatId itemIds
setDirectChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
- pure $ CRItemsReadForChat user (AChatInfo SCTDirect $ DirectChat ct)
+ ok user
CTGroup -> do
- (user, gInfo) <- withFastStore $ \db -> do
- user <- getUserByGroupId db chatId
- gInfo <- getGroupInfo db vr user chatId
- pure (user, gInfo)
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- (timedItems, gInfo') <- withFastStore $ \db -> do
- (timedItems, gInfo') <- updateGroupChatItemsReadList db vr user gInfo chatScopeInfo itemIds
- timedItems' <- liftIO $ setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
- pure (timedItems', gInfo')
+ user <- withFastStore $ \db -> getUserByGroupId db chatId
+ timedItems <- withFastStore' $ \db -> do
+ timedItems <- updateGroupChatItemsReadList db user chatId itemIds
+ setGroupChatItemsDeleteAt db user chatId timedItems =<< getCurrentTime
forM_ timedItems $ \(itemId, deleteAt) -> startProximateTimedItemThread user (chatRef, itemId) deleteAt
- pure $ CRItemsReadForChat user (AChatInfo SCTGroup $ GroupChat gInfo' Nothing)
+ ok user
CTLocal -> throwCmdError "not supported"
CTContactRequest -> throwCmdError "not supported"
CTContactConnection -> throwCmdError "not supported"
- APIChatUnread (ChatRef cType chatId scope) unreadChat -> withUser $ \user -> case cType of
+ APIChatUnread (ChatRef cType chatId) unreadChat -> withUser $ \user -> case cType of
CTDirect -> do
withFastStore $ \db -> do
ct <- getContact db vr user chatId
liftIO $ updateContactUnreadChat db user ct unreadChat
ok user
- -- TODO [knocking] set support chat as unread?
- CTGroup | isNothing scope -> do
+ CTGroup -> do
withFastStore $ \db -> do
Group {groupInfo} <- getGroup db vr user chatId
liftIO $ updateGroupUnreadChat db user groupInfo unreadChat
@@ -1060,7 +1037,7 @@ processChatCommand' vr = \case
liftIO $ updateNoteFolderUnreadChat db user nf unreadChat
ok user
_ -> throwCmdError "not supported"
- APIDeleteChat cRef@(ChatRef cType chatId scope) cdm -> withUser $ \user@User {userId} -> case cType of
+ APIDeleteChat cRef@(ChatRef cType chatId) cdm -> withUser $ \user@User {userId} -> case cType of
CTDirect -> do
ct <- withFastStore $ \db -> getContact db vr user chatId
filesInfo <- withFastStore' $ \db -> getContactFileInfo db user ct
@@ -1099,7 +1076,7 @@ processChatCommand' vr = \case
deleteAgentConnectionAsync acId
withFastStore' $ \db -> deletePendingContactConnection db userId chatId
pure $ CRContactConnectionDeleted user conn
- CTGroup | isNothing scope -> do
+ CTGroup -> do
Group gInfo@GroupInfo {membership} members <- withFastStore $ \db -> getGroup db vr user chatId
let GroupMember {memberRole = membershipMemRole} = membership
let isOwner = membershipMemRole == GROwner
@@ -1109,8 +1086,8 @@ processChatCommand' vr = \case
withGroupLock "deleteChat group" chatId . procCmd $ do
deleteCIFiles user filesInfo
let doSendDel = memberActive membership && isOwner
- recipients = filter memberCurrentOrPending members
- when doSendDel . void $ sendGroupMessage' user gInfo recipients XGrpDel
+ -- TODO [knocking] send to pending approval members (move `memberCurrent` filter from sendGroupMessages_ to call sites)
+ when doSendDel . void $ sendGroupMessage' user gInfo members XGrpDel
deleteGroupLinkIfExists user gInfo
deleteMembersConnections' user members doSendDel
updateCIGroupInvitationStatus user gInfo CIGISRejected `catchChatError` \_ -> pure ()
@@ -1119,22 +1096,23 @@ processChatCommand' vr = \case
withFastStore' $ \db -> deleteGroupMembers db user gInfo
withFastStore' $ \db -> deleteGroup db user gInfo
pure $ CRGroupDeletedUser user gInfo
- _ -> throwCmdError "not supported"
- APIClearChat (ChatRef cType chatId scope) -> withUser $ \user@User {userId} -> case cType of
+ CTLocal -> throwCmdError "not supported"
+ CTContactRequest -> throwCmdError "not supported"
+ APIClearChat (ChatRef cType chatId) -> withUser $ \user@User {userId} -> case cType of
CTDirect -> do
ct <- withFastStore $ \db -> getContact db vr user chatId
filesInfo <- withFastStore' $ \db -> getContactFileInfo db user ct
deleteCIFiles user filesInfo
withFastStore' $ \db -> deleteContactCIs db user ct
pure $ CRChatCleared user (AChatInfo SCTDirect $ DirectChat ct)
- CTGroup | isNothing scope -> do
+ CTGroup -> do
gInfo <- withFastStore $ \db -> getGroupInfo db vr user chatId
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
deleteCIFiles user filesInfo
withFastStore' $ \db -> deleteGroupChatItemsMessages db user gInfo
membersToDelete <- withFastStore' $ \db -> getGroupMembersForExpiration db vr user gInfo
forM_ membersToDelete $ \m -> withFastStore' $ \db -> deleteGroupMember db user m
- pure $ CRChatCleared user (AChatInfo SCTGroup $ GroupChat gInfo Nothing)
+ pure $ CRChatCleared user (AChatInfo SCTGroup $ GroupChat gInfo)
CTLocal -> do
nf <- withFastStore $ \db -> getNoteFolder db user chatId
filesInfo <- withFastStore' $ \db -> getNoteFolderFileInfo db user nf
@@ -1142,7 +1120,8 @@ processChatCommand' vr = \case
withFastStore' $ \db -> deleteNoteFolderFiles db userId nf
withFastStore' $ \db -> deleteNoteFolderCIs db user nf
pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf)
- _ -> throwCmdError "not supported"
+ CTContactConnection -> throwCmdError "not supported"
+ CTContactRequest -> throwCmdError "not supported"
APIAcceptContact incognito connReqId -> withUser $ \_ -> do
userContactLinkId <- withFastStore $ \db -> getUserContactLinkIdByCReq db connReqId
withUserContactLock "acceptContact" userContactLinkId $ do
@@ -1189,7 +1168,7 @@ processChatCommand' vr = \case
forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci]
ok user
- else throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFCalls)
+ else throwCmdError ("feature not allowed " <> T.unpack (chatFeatureNameText CFCalls))
SendCallInvitation cName callType -> withUser $ \user -> do
contactId <- withFastStore $ \db -> getContactIdByName db user cName
processChatCommand $ APISendCallInvitation contactId callType
@@ -1202,7 +1181,7 @@ processChatCommand' vr = \case
timed_ <- contactCITimed ct
updateDirectChatItemView user ct chatItemId aciContent False False timed_ Nothing
forM_ (timed_ >>= timedDeleteAt') $
- startProximateTimedItemThread user (ChatRef CTDirect contactId Nothing, chatItemId)
+ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId)
pure Nothing
_ -> throwChatError . CECallState $ callStateTag callState
APISendCallOffer contactId WebRTCCallOffer {callType, rtcSession} ->
@@ -1292,13 +1271,13 @@ processChatCommand' vr = \case
pure user'
when (userId == uId') $ chatWriteVar currentUser $ Just (user :: User) {uiThemes}
ok user'
- APISetChatUIThemes (ChatRef cType chatId scope) uiThemes -> withUser $ \user -> case cType of
+ APISetChatUIThemes (ChatRef cType chatId) uiThemes -> withUser $ \user -> case cType of
CTDirect -> do
withFastStore $ \db -> do
ct <- getContact db vr user chatId
liftIO $ setContactUIThemes db user ct uiThemes
ok user
- CTGroup | isNothing scope -> do
+ CTGroup -> do
withFastStore $ \db -> do
g <- getGroupInfo db vr user chatId
liftIO $ setGroupUIThemes db user g uiThemes
@@ -1333,9 +1312,9 @@ processChatCommand' vr = \case
SetUserProtoServers (AProtocolType (p :: SProtocolType p)) srvs -> withUser $ \user@User {userId} -> withServerProtocol p $ do
userServers_ <- liftIO . groupByOperator =<< withFastStore (`getUserServers` user)
case L.nonEmpty userServers_ of
- Nothing -> throwCmdError "no servers"
+ Nothing -> throwChatError $ CECommandError "no servers"
Just userServers -> case srvs of
- [] -> throwCmdError "no servers"
+ [] -> throwChatError $ CECommandError "no servers"
_ -> do
srvs' <- mapM aUserServer srvs
processChatCommand $ APISetUserServers userId $ L.map (updatedServers p srvs') userServers
@@ -1343,7 +1322,7 @@ processChatCommand' vr = \case
aUserServer :: AProtoServerWithAuth -> CM (AUserServer p)
aUserServer (AProtoServerWithAuth p' srv) = case testEquality p p' of
Just Refl -> pure $ AUS SDBNew $ newUserServer srv
- Nothing -> throwCmdError $ "incorrect server protocol: " <> B.unpack (strEncode srv)
+ Nothing -> throwChatError $ CECommandError $ "incorrect server protocol: " <> B.unpack (strEncode srv)
APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user ->
lift $ CRServerTestResult user srv <$> withAgent' (\a -> testProtocolServer a (aUserId user) server)
TestProtoServer srv -> withUser $ \User {userId} ->
@@ -1383,7 +1362,7 @@ processChatCommand' vr = \case
CRUserServers user <$> (liftIO . groupByOperator =<< getUserServers db user)
APISetUserServers userId userServers -> withUserId userId $ \user -> do
errors <- validateAllUsersServers userId $ L.toList userServers
- unless (null errors) $ throwCmdError $ "user servers validation error(s): " <> show errors
+ unless (null errors) $ throwChatError (CECommandError $ "user servers validation error(s): " <> show errors)
uss <- withFastStore $ \db -> do
ts <- liftIO getCurrentTime
mapM (setUserServers db user ts) userServers
@@ -1417,7 +1396,7 @@ processChatCommand' vr = \case
currentTs <- liftIO getCurrentTime
acceptConditions db condId opIds currentTs
CRServerOperatorConditions <$> getServerOperators db
- APISetChatTTL userId (ChatRef cType chatId scope) newTTL_ ->
+ APISetChatTTL userId (ChatRef cType chatId) newTTL_ ->
withUserId userId $ \user -> checkStoreNotChanged $ withChatLock "setChatTTL" $ do
(oldTTL_, globalTTL, ttlCount) <- withStore' $ \db ->
(,,) <$> getSetChatTTL db <*> getChatItemTTL db user <*> getChatTTLCount db user
@@ -1431,26 +1410,25 @@ processChatCommand' vr = \case
where
getSetChatTTL db = case cType of
CTDirect -> getDirectChatTTL db chatId <* setDirectChatTTL db chatId newTTL_
- CTGroup | isNothing scope -> getGroupChatTTL db chatId <* setGroupChatTTL db chatId newTTL_
+ CTGroup -> getGroupChatTTL db chatId <* setGroupChatTTL db chatId newTTL_
_ -> pure Nothing
expireChat user globalTTL = do
currentTs <- liftIO getCurrentTime
case cType of
CTDirect -> expireContactChatItems user vr globalTTL chatId
- CTGroup | isNothing scope ->
+ CTGroup ->
let createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
in expireGroupChatItems user vr globalTTL createdAtCutoff chatId
- _ -> throwCmdError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
SetChatTTL chatName newTTL -> withUser' $ \user@User {userId} -> do
chatRef <- getChatRef user chatName
processChatCommand $ APISetChatTTL userId chatRef newTTL
GetChatTTL chatName -> withUser' $ \user -> do
- -- TODO [knocking] support scope in CLI apis
- ChatRef cType chatId _ <- getChatRef user chatName
+ ChatRef cType chatId <- getChatRef user chatName
ttl <- case cType of
CTDirect -> withFastStore' (`getDirectChatTTL` chatId)
CTGroup -> withFastStore' (`getGroupChatTTL` chatId)
- _ -> throwCmdError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
pure $ CRChatItemTTL user ttl
APISetChatItemTTL userId newTTL -> withUserId userId $ \user ->
checkStoreNotChanged $
@@ -1481,7 +1459,7 @@ processChatCommand' vr = \case
ReconnectServer userId srv -> withUserId userId $ \user -> do
lift (withAgent' $ \a -> reconnectSMPServer a (aUserId user) srv)
ok_
- APISetChatSettings (ChatRef cType chatId scope) chatSettings -> withUser $ \user -> case cType of
+ APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of
CTDirect -> do
ct <- withFastStore $ \db -> do
ct <- getContact db vr user chatId
@@ -1490,7 +1468,7 @@ processChatCommand' vr = \case
forM_ (contactConnId ct) $ \connId ->
withAgent $ \a -> toggleConnectionNtfs a connId (chatHasNtfs chatSettings)
ok user
- CTGroup | isNothing scope -> do
+ CTGroup -> do
ms <- withFastStore $ \db -> do
Group _ ms <- getGroup db vr user chatId
liftIO $ updateGroupSettings db user chatId chatSettings
@@ -1544,7 +1522,7 @@ processChatCommand' vr = \case
case memberConnId m of
Just connId -> do
connectionStats <- withAgent (\a -> switchConnectionAsync a "" connId)
- pure $ CEvtGroupMemberSwitchStarted user g m connectionStats
+ pure $ CRGroupMemberSwitchStarted user g m connectionStats
_ -> throwChatError CEGroupMemberNotActive
APIAbortSwitchContact contactId -> withUser $ \user -> do
ct <- withFastStore $ \db -> getContact db vr user contactId
@@ -1558,7 +1536,7 @@ processChatCommand' vr = \case
case memberConnId m of
Just connId -> do
connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId
- pure $ CEvtGroupMemberSwitchAborted user g m connectionStats
+ pure $ CRGroupMemberSwitchAborted user g m connectionStats
_ -> throwChatError CEGroupMemberNotActive
APISyncContactRatchet contactId force -> withUser $ \user -> withContactLock "syncContactRatchet" contactId $ do
ct <- withFastStore $ \db -> getContact db vr user contactId
@@ -1573,9 +1551,8 @@ processChatCommand' vr = \case
case memberConnId m of
Just connId -> do
cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a connId PQSupportOff force
- (g', m', scopeInfo) <- mkGroupChatScope g m
- createInternalChatItem user (CDGroupSnd g' scopeInfo) (CISndConnEvent . SCERatchetSync rss . Just $ groupMemberRef m') Nothing
- pure $ CRGroupMemberRatchetSyncStarted user g' m' cStats
+ createInternalChatItem user (CDGroupSnd g) (CISndConnEvent . SCERatchetSync rss . Just $ groupMemberRef m) Nothing
+ pure $ CRGroupMemberRatchetSyncStarted user g m cStats
_ -> throwChatError CEGroupMemberNotActive
APIGetContactCode contactId -> withUser $ \user -> do
ct@Contact {activeConn} <- withFastStore $ \db -> getContact db vr user contactId
@@ -1694,25 +1671,10 @@ processChatCommand' vr = \case
case (pccConnStatus, connLinkInv) of
(ConnNew, Just (CCLink cReqInv _)) -> do
newUser <- privateGetUser newUserId
- conn' <- ifM (canKeepLink cReqInv newUser) (updateConnRecord user conn newUser) (recreateConn user conn newUser)
+ conn' <- recreateConn user conn newUser
pure $ CRConnectionUserChanged user conn conn' newUser
_ -> throwChatError CEConnectionUserChangeProhibited
where
- canKeepLink :: ConnReqInvitation -> User -> CM Bool
- canKeepLink (CRInvitationUri crData _) newUser = do
- let ConnReqUriData {crSmpQueues = q :| _} = crData
- SMPQueueUri {queueAddress = SMPQueueAddress {smpServer}} = q
- newUserServers <-
- map protoServer' . L.filter (\ServerCfg {enabled} -> enabled)
- <$> getKnownAgentServers SPSMP newUser
- pure $ smpServer `elem` newUserServers
- updateConnRecord user@User {userId} conn@PendingContactConnection {customUserProfileId} newUser = do
- withAgent $ \a -> changeConnectionUser a (aUserId user) (aConnId' conn) (aUserId newUser)
- withFastStore' $ \db -> do
- conn' <- updatePCCUser db userId conn newUserId
- forM_ customUserProfileId $ \profileId ->
- deletePCCIncognitoProfile db user profileId
- pure conn'
recreateConn user conn@PendingContactConnection {customUserProfileId, connLinkInv} newUser = do
subMode <- chatReadVar subscriptionMode
let userData = shortLinkUserData $ isJust $ connShortLink =<< connLinkInv
@@ -1746,7 +1708,7 @@ processChatCommand' vr = \case
-- retrying join after error
pcc <- withFastStore $ \db -> getPendingContactConnection db userId connId
joinPreparedConn (aConnId conn) pcc dm
- Just ent -> throwCmdError $ "connection exists: " <> show (connEntityInfo ent)
+ Just ent -> throwChatError $ CECommandError $ "connection exists: " <> show (connEntityInfo ent)
where
joinNewConn chatV dm = do
connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup'
@@ -1769,19 +1731,19 @@ processChatCommand' vr = \case
Connect _ Nothing -> throwChatError CEInvalidConnReq
APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do
ct@Contact {activeConn, profile = LocalProfile {contactLink}} <- withFastStore $ \db -> getContact db vr user contactId
- when (isJust activeConn) $ throwCmdError "contact already has connection"
+ when (isJust activeConn) $ throwChatError (CECommandError "contact already has connection")
ccLink <- case contactLink of
Just (CLFull cReq) -> pure $ CCLink cReq Nothing
Just (CLShort sLnk) -> do
cReq <- getShortLinkConnReq user sLnk
pure $ CCLink cReq $ Just sLnk
- Nothing -> throwCmdError "no address in contact profile"
+ Nothing -> throwChatError (CECommandError "no address in contact profile")
connectContactViaAddress user incognito ct ccLink
ConnectSimplex incognito -> withUser $ \user -> do
plan <- contactRequestPlan user adminContactReq `catchChatError` const (pure $ CPContactAddress CAPOk)
connectWithPlan user incognito (ACCL SCMContact (CCLink adminContactReq Nothing)) plan
- DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId Nothing) cdm
- ClearContact cName -> withContactName cName $ \chatId -> APIClearChat $ ChatRef CTDirect chatId Nothing
+ DeleteContact cName cdm -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId) cdm
+ ClearContact cName -> withContactName cName $ APIClearChat . ChatRef CTDirect
APIListContacts userId -> withUserId userId $ \user ->
CRContactsList user <$> withFastStore' (\db -> getUserContacts db vr user)
ListContacts -> withUser $ \User {userId} ->
@@ -1824,7 +1786,7 @@ processChatCommand' vr = \case
processChatCommand $ APISetProfileAddress userId onOff
APIAddressAutoAccept userId autoAccept_ -> withUserId userId $ \user -> do
forM_ autoAccept_ $ \AutoAccept {businessAddress, acceptIncognito} ->
- when (businessAddress && acceptIncognito) $ throwCmdError "requests to business address cannot be accepted incognito"
+ when (businessAddress && acceptIncognito) $ throwChatError $ CECommandError "requests to business address cannot be accepted incognito"
contactLink <- withFastStore (\db -> updateUserAddressAutoAccept db user autoAccept_)
pure $ CRUserContactLinkUpdated user contactLink
AddressAutoAccept autoAccept_ -> withUser $ \User {userId} ->
@@ -1839,21 +1801,21 @@ processChatCommand' vr = \case
contactId <- withFastStore $ \db -> getContactIdByName db user fromContactName
forwardedItemId <- withFastStore $ \db -> getDirectChatItemIdByText' db user contactId forwardedMsg
toChatRef <- getChatRef user toChatName
- processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId Nothing) (forwardedItemId :| []) Nothing
+ processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTDirect contactId) (forwardedItemId :| []) Nothing
ForwardGroupMessage toChatName fromGroupName fromMemberName_ forwardedMsg -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user fromGroupName
forwardedItemId <- withFastStore $ \db -> getGroupChatItemIdByText db user groupId fromMemberName_ forwardedMsg
toChatRef <- getChatRef user toChatName
- processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId Nothing) (forwardedItemId :| []) Nothing
+ processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTGroup groupId) (forwardedItemId :| []) Nothing
ForwardLocalMessage toChatName forwardedMsg -> withUser $ \user -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
forwardedItemId <- withFastStore $ \db -> getLocalChatItemIdByText' db user folderId forwardedMsg
toChatRef <- getChatRef user toChatName
- processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId Nothing) (forwardedItemId :| []) Nothing
- SendMessage sendName msg -> withUser $ \user -> do
+ processChatCommand $ APIForwardChatItems toChatRef (ChatRef CTLocal folderId) (forwardedItemId :| []) Nothing
+ SendMessage (ChatName cType name) msg -> withUser $ \user -> do
let mc = MCText msg
- case sendName of
- SNDirect name ->
+ case cType of
+ CTDirect ->
withFastStore' (\db -> runExceptT $ getContactIdByName db user name) >>= \case
Right ctId -> do
let sendRef = SRDirect ctId
@@ -1868,18 +1830,18 @@ processChatCommand' vr = \case
throwChatError $ CEContactNotFound name (Just suspectedMember)
_ ->
throwChatError $ CEContactNotFound name Nothing
- SNGroup name scope_ -> do
- (gId, cScope_, mentions) <- withFastStore $ \db -> do
+ CTGroup -> do
+ (gId, mentions) <- withFastStore $ \db -> do
gId <- getGroupIdByName db user name
- cScope_ <-
- forM scope_ $ \(GSNMemberSupport mName_) ->
- GCSMemberSupport <$> mapM (getGroupMemberIdByName db user gId) mName_
- (gId,cScope_,) <$> liftIO (getMessageMentions db user gId msg)
- let sendRef = SRGroup gId cScope_
+ (gId,) <$> liftIO (getMessageMentions db user gId msg)
+ let sendRef = SRGroup gId Nothing
processChatCommand $ APISendMessages sendRef False Nothing [ComposedMessage Nothing Nothing mc mentions]
- SNLocal -> do
- folderId <- withFastStore (`getUserNoteFolderId` user)
- processChatCommand $ APICreateChatItems folderId [composedMessage Nothing mc]
+ CTLocal
+ | name == "" -> do
+ folderId <- withFastStore (`getUserNoteFolderId` user)
+ processChatCommand $ APICreateChatItems folderId [composedMessage Nothing mc]
+ | otherwise -> throwChatError $ CECommandError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
SendMemberContactMessage gName mName msg -> withUser $ \user -> do
(gId, mId) <- getGroupAndMemberId user gName mName
m <- withFastStore $ \db -> getGroupMember db vr user gId mId
@@ -1887,7 +1849,7 @@ processChatCommand' vr = \case
case memberContactId m of
Nothing -> do
g <- withFastStore $ \db -> getGroupInfo db vr user gId
- unless (groupFeatureMemberAllowed SGFDirectMessages (membership g) g) $ throwCmdError "direct messages not allowed"
+ unless (groupFeatureMemberAllowed SGFDirectMessages (membership g) g) $ throwChatError $ CECommandError "direct messages not allowed"
toView $ CEvtNoMemberContactCreating user g m
processChatCommand (APICreateMemberContact gId mId) >>= \case
CRNewMemberContact _ ct@Contact {contactId} _ _ -> do
@@ -1940,7 +1902,7 @@ processChatCommand' vr = \case
combineResults _ _ (Left e) = Left e
createCI :: DB.Connection -> User -> UTCTime -> (Contact, SndMessage) -> IO ()
createCI db user createdAt (ct, sndMsg) =
- void $ createNewSndChatItem db user (CDDirectSnd ct) sndMsg (CISndMsgContent mc) Nothing Nothing Nothing False createdAt
+ void $ createNewSndChatItem db user (CDDirectSnd ct) Nothing sndMsg (CISndMsgContent mc) Nothing Nothing Nothing False createdAt
SendMessageQuote cName (AMsgDirection msgDir) quotedMsg msg -> withUser $ \user@User {userId} -> do
contactId <- withFastStore $ \db -> getContactIdByName db user cName
quotedItemId <- withFastStore $ \db -> getDirectChatItemIdByText db userId contactId msgDir quotedMsg
@@ -1973,7 +1935,7 @@ processChatCommand' vr = \case
-- [incognito] generate incognito profile for group membership
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
gInfo <- withFastStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile
- let cd = CDGroupSnd gInfo Nothing
+ let cd = CDGroupSnd gInfo
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createGroupFeatureItems user cd CISndGroupFeature gInfo
pure $ CRGroupCreated user gInfo
@@ -2041,79 +2003,27 @@ processChatCommand' vr = \case
updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` eToView
pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing
Nothing -> throwChatError $ CEContactNotActive ct
- APIAcceptMember groupId gmId role -> withUser $ \user@User {userId} -> do
+ APIAcceptMember groupId gmId role -> withUser $ \user -> do
(gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user gmId
- assertUserGroupRole gInfo GRModerator
- case memberStatus m of
- GSMemPendingApproval | memberCategory m == GCInviteeMember -> do -- only host can approve
- let GroupInfo {groupProfile = GroupProfile {memberAdmission}} = gInfo
- case memberConn m of
- Just mConn ->
- case memberAdmission >>= review of
- Just MCAll -> do
- introduceToModerators vr user gInfo m
- withFastStore' $ \db -> updateGroupMemberStatus db userId m GSMemPendingReview
- let m' = m {memberStatus = GSMemPendingReview}
- pure $ CRMemberAccepted user gInfo m'
- Nothing -> do
- let msg = XGrpLinkAcpt GAAccepted role (memberId' m)
- void $ sendDirectMemberMessage mConn msg groupId
- introduceToRemaining vr user gInfo m {memberRole = role}
- when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo m
- (m', gInfo') <- withFastStore' $ \db -> do
- m' <- updateGroupMemberAccepted db user m GSMemConnected role
- gInfo' <- updateGroupMembersRequireAttention db user gInfo m m'
- pure (m', gInfo')
- -- create item in both scopes
- createInternalChatItem user (CDGroupRcv gInfo' Nothing m') (CIRcvGroupEvent RGEMemberConnected) Nothing
- let scopeInfo = Just GCSIMemberSupport {groupMember_ = Just m'}
- gEvent = SGEMemberAccepted gmId (fromLocalProfile $ memberProfile m')
- createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent gEvent) Nothing
- pure $ CRMemberAccepted user gInfo' m'
- Nothing -> throwChatError CEGroupMemberNotActive
- GSMemPendingReview -> do
- let scope = Just $ GCSMemberSupport $ Just (groupMemberId' m)
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
- let rcpModMs' = filter memberCurrent modMs
- msg = XGrpLinkAcpt GAAccepted role (memberId' m)
- void $ sendGroupMessage user gInfo scope ([m] <> rcpModMs') msg
- when (maxVersion (memberChatVRange m) < groupKnockingVersion) $
- forM_ (memberConn m) $ \mConn -> do
- let msg2 = XMsgNew $ MCSimple $ extMsgContent (MCText acceptedToGroupMessage) Nothing
- void $ sendDirectMemberMessage mConn msg2 groupId
- when (memberCategory m == GCInviteeMember) $ do
- introduceToRemaining vr user gInfo m {memberRole = role}
- when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo m
- (m', gInfo') <- withFastStore' $ \db -> do
- m' <- updateGroupMemberAccepted db user m newMemberStatus role
- gInfo' <- updateGroupMembersRequireAttention db user gInfo m m'
- pure (m', gInfo')
- -- create item in both scopes
- createInternalChatItem user (CDGroupRcv gInfo' Nothing m') (CIRcvGroupEvent RGEMemberConnected) Nothing
- let scopeInfo = Just GCSIMemberSupport {groupMember_ = Just m'}
- gEvent = SGEMemberAccepted gmId (fromLocalProfile $ memberProfile m')
- createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent gEvent) Nothing
- pure $ CRMemberAccepted user gInfo' m'
- where
- newMemberStatus = case memberConn m of
- Just c | connReady c -> GSMemConnected
- _ -> GSMemAnnounced
- _ -> throwCmdError "member should be pending approval and invitee, or pending review and not invitee"
- APIDeleteMemberSupportChat groupId gmId -> withUser $ \user -> do
- (gInfo, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user gmId
- when (isNothing $ supportChat m) $ throwCmdError "member has no support chat"
- when (memberPending m) $ throwCmdError "member is pending"
- (gInfo', m') <- withFastStore' $ \db -> deleteGroupMemberSupportChat db user gInfo m
- pure $ CRMemberSupportChatDeleted user gInfo' m'
+ assertUserGroupRole gInfo GRAdmin
+ when (memberStatus m /= GSMemPendingApproval) $ throwChatError $ CECommandError "member is not pending approval"
+ case memberConn m of
+ Just mConn -> do
+ let msg = XGrpLinkAcpt role
+ void $ sendDirectMemberMessage mConn msg groupId
+ m' <- withFastStore' $ \db -> updateGroupMemberAccepted db user m role
+ introduceToGroup vr user gInfo m'
+ pure $ CRJoinedGroupMember user gInfo m'
+ _ -> throwChatError CEGroupMemberNotActive
APIMembersRole groupId memberIds newRole -> withUser $ \user ->
withGroupLock "memberRole" groupId . procCmd $ do
g@(Group gInfo members) <- withFastStore $ \db -> getGroup db vr user groupId
- when (selfSelected gInfo) $ throwCmdError "can't change role for self"
+ when (selfSelected gInfo) $ throwChatError $ CECommandError "can't change role for self"
let (invitedMems, currentMems, unchangedMems, maxRole, anyAdmin, anyPending) = selectMembers members
when (length invitedMems + length currentMems + length unchangedMems /= length memberIds) $ throwChatError CEGroupMemberNotFound
when (length memberIds > 1 && (anyAdmin || newRole >= GRAdmin)) $
- throwCmdError "can't change role of multiple members when admins selected, or new role is admin"
- when anyPending $ throwCmdError "can't change role of members pending approval"
+ throwChatError $ CECommandError "can't change role of multiple members when admins selected, or new role is admin"
+ when anyPending $ throwChatError $ CECommandError "can't change role of members pending approval"
assertUserGroupRole gInfo $ maximum ([GRAdmin, maxRole, newRole] :: [GroupMemberRole])
(errs1, changed1) <- changeRoleInvitedMems user gInfo invitedMems
(errs2, changed2, acis) <- changeRoleCurrentMems user g currentMems
@@ -2130,7 +2040,7 @@ processChatCommand' vr = \case
| groupMemberId `elem` memberIds =
let maxRole' = max maxRole memberRole
anyAdmin' = anyAdmin || memberRole >= GRAdmin
- anyPending' = anyPending || memberPending m
+ anyPending' = anyPending || memberStatus == GSMemPendingApproval
in
if
| memberRole == newRole -> (invited, current, m : unchanged, maxRole', anyAdmin', anyPending')
@@ -2156,13 +2066,12 @@ processChatCommand' vr = \case
Nothing -> pure ([], [], [])
Just memsToChange' -> do
let events = L.map (\GroupMember {memberId} -> XGrpMemRole memberId newRole) memsToChange'
- recipients = filter memberCurrent members
- (msgs_, _gsr) <- sendGroupMessages user gInfo Nothing recipients events
+ (msgs_, _gsr) <- sendGroupMessages user gInfo members events
let itemsData = zipWith (fmap . sndItemData) memsToChange (L.toList msgs_)
- cis_ <- saveSndChatItems user (CDGroupSnd gInfo Nothing) itemsData Nothing False
+ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) Nothing itemsData Nothing False
when (length cis_ /= length memsToChange) $ logError "changeRoleCurrentMems: memsToChange and cis_ length mismatch"
(errs, changed) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (updMember db) memsToChange)
- let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo Nothing)) $ rights cis_
+ let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) $ rights cis_
pure (errs, changed, acis)
where
sndItemData :: GroupMember -> SndMessage -> NewSndChatItemData c
@@ -2176,11 +2085,11 @@ processChatCommand' vr = \case
APIBlockMembersForAll groupId memberIds blockFlag -> withUser $ \user ->
withGroupLock "blockForAll" groupId . procCmd $ do
Group gInfo members <- withFastStore $ \db -> getGroup db vr user groupId
- when (selfSelected gInfo) $ throwCmdError "can't block/unblock self"
+ when (selfSelected gInfo) $ throwChatError $ CECommandError "can't block/unblock self"
let (blockMems, remainingMems, maxRole, anyAdmin, anyPending) = selectMembers members
when (length blockMems /= length memberIds) $ throwChatError CEGroupMemberNotFound
- when (length memberIds > 1 && anyAdmin) $ throwCmdError "can't block/unblock multiple members when admins selected"
- when anyPending $ throwCmdError "can't block/unblock members pending approval"
+ when (length memberIds > 1 && anyAdmin) $ throwChatError $ CECommandError "can't block/unblock multiple members when admins selected"
+ when anyPending $ throwChatError $ CECommandError "can't block/unblock members pending approval"
assertUserGroupRole gInfo $ max GRModerator maxRole
blockMembers user gInfo blockMems remainingMems
where
@@ -2188,25 +2097,24 @@ processChatCommand' vr = \case
selectMembers :: [GroupMember] -> ([GroupMember], [GroupMember], GroupMemberRole, Bool, Bool)
selectMembers = foldr' addMember ([], [], GRObserver, False, False)
where
- addMember m@GroupMember {groupMemberId, memberRole} (block, remaining, maxRole, anyAdmin, anyPending)
+ addMember m@GroupMember {groupMemberId, memberRole, memberStatus} (block, remaining, maxRole, anyAdmin, anyPending)
| groupMemberId `elem` memberIds =
let maxRole' = max maxRole memberRole
anyAdmin' = anyAdmin || memberRole >= GRAdmin
- anyPending' = anyPending || memberPending m
+ anyPending' = anyPending || memberStatus == GSMemPendingApproval
in (m : block, remaining, maxRole', anyAdmin', anyPending')
| otherwise = (block, m : remaining, maxRole, anyAdmin, anyPending)
blockMembers :: User -> GroupInfo -> [GroupMember] -> [GroupMember] -> CM ChatResponse
blockMembers user gInfo blockMems remainingMems = case L.nonEmpty blockMems of
- Nothing -> throwCmdError "no members to block/unblock"
+ Nothing -> throwChatError $ CECommandError "no members to block/unblock"
Just blockMems' -> do
let mrs = if blockFlag then MRSBlocked else MRSUnrestricted
events = L.map (\GroupMember {memberId} -> XGrpMemRestrict memberId MemberRestrictions {restriction = mrs}) blockMems'
- recipients = filter memberCurrent remainingMems
- (msgs_, _gsr) <- sendGroupMessages_ user gInfo recipients events
+ (msgs_, _gsr) <- sendGroupMessages user gInfo remainingMems events
let itemsData = zipWith (fmap . sndItemData) blockMems (L.toList msgs_)
- cis_ <- saveSndChatItems user (CDGroupSnd gInfo Nothing) itemsData Nothing False
+ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) Nothing itemsData Nothing False
when (length cis_ /= length blockMems) $ logError "blockMembers: blockMems and cis_ length mismatch"
- let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo Nothing)) $ rights cis_
+ let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) $ rights cis_
unless (null acis) $ toView $ CEvtNewChatItems user acis
(errs, blocked) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (updateGroupMemberBlocked db user gInfo mrs) blockMems)
unless (null errs) $ toView $ CEvtChatErrors errs
@@ -2222,43 +2130,34 @@ processChatCommand' vr = \case
APIRemoveMembers {groupId, groupMemberIds, withMessages} -> withUser $ \user ->
withGroupLock "removeMembers" groupId . procCmd $ do
Group gInfo members <- withFastStore $ \db -> getGroup db vr user groupId
- let (count, invitedMems, pendingApprvMems, pendingRvwMems, currentMems, maxRole, anyAdmin) = selectMembers members
+ let (count, invitedMems, pendingMems, currentMems, maxRole, anyAdmin) = selectMembers members
memCount = S.size groupMemberIds
when (count /= memCount) $ throwChatError CEGroupMemberNotFound
- when (memCount > 1 && anyAdmin) $ throwCmdError "can't remove multiple members when admins selected"
+ when (memCount > 1 && anyAdmin) $ throwChatError $ CECommandError "can't remove multiple members when admins selected"
assertUserGroupRole gInfo $ max GRAdmin maxRole
(errs1, deleted1) <- deleteInvitedMems user invitedMems
- let recipients = filter memberCurrent members
- (errs2, deleted2, acis2) <- deleteMemsSend user gInfo Nothing recipients currentMems
- (errs3, deleted3, acis3) <-
- foldM (\acc m -> deletePendingMember acc user gInfo [m] m) ([], [], []) pendingApprvMems
- let moderators = filter (\GroupMember {memberRole} -> memberRole >= GRModerator) members
- (errs4, deleted4, acis4) <-
- foldM (\acc m -> deletePendingMember acc user gInfo (m : moderators) m) ([], [], []) pendingRvwMems
- let acis = acis2 <> acis3 <> acis4
- errs = errs1 <> errs2 <> errs3 <> errs4
- deleted = deleted1 <> deleted2 <> deleted3 <> deleted4
- -- Read group info with updated membersRequireAttention
- gInfo' <- withFastStore $ \db -> getGroupInfo db vr user groupId
- let acis' = map (updateACIGroupInfo gInfo') acis
- unless (null acis') $ toView $ CEvtNewChatItems user acis'
+ (errs2, deleted2, acis2) <- deleteMemsSend user gInfo members currentMems
+ rs <- forM pendingMems $ \m -> deleteMemsSend user gInfo [m] [m] -- TODO [knocking]
+ let (errs3, deleted3, acis3) = concatTuples rs
+ acis = acis2 <> acis3
+ errs = errs1 <> errs2 <> errs3
+ unless (null acis) $ toView $ CEvtNewChatItems user acis
unless (null errs) $ toView $ CEvtChatErrors errs
- when withMessages $ deleteMessages user gInfo' deleted
- pure $ CRUserDeletedMembers user gInfo' deleted withMessages -- same order is not guaranteed
+ when withMessages $ deleteMessages user gInfo $ currentMems <> pendingMems
+ pure $ CRUserDeletedMembers user gInfo (deleted1 <> deleted2 <> deleted3) withMessages -- same order is not guaranteed
where
- selectMembers :: [GroupMember] -> (Int, [GroupMember], [GroupMember], [GroupMember], [GroupMember], GroupMemberRole, Bool)
- selectMembers = foldl' addMember (0, [], [], [], [], GRObserver, False)
+ selectMembers :: [GroupMember] -> (Int, [GroupMember], [GroupMember], [GroupMember], GroupMemberRole, Bool)
+ selectMembers = foldl' addMember (0, [], [], [], GRObserver, False)
where
- addMember acc@(n, invited, pendingApprv, pendingRvw, current, maxRole, anyAdmin) m@GroupMember {groupMemberId, memberStatus, memberRole}
+ addMember acc@(n, invited, pending, current, maxRole, anyAdmin) m@GroupMember {groupMemberId, memberStatus, memberRole}
| groupMemberId `S.member` groupMemberIds =
let maxRole' = max maxRole memberRole
anyAdmin' = anyAdmin || memberRole >= GRAdmin
n' = n + 1
in case memberStatus of
- GSMemInvited -> (n', m : invited, pendingApprv, pendingRvw, current, maxRole', anyAdmin')
- GSMemPendingApproval -> (n', invited, m : pendingApprv, pendingRvw, current, maxRole', anyAdmin')
- GSMemPendingReview -> (n', invited, pendingApprv, m : pendingRvw, current, maxRole', anyAdmin')
- _ -> (n', invited, pendingApprv, pendingRvw, m : current, maxRole', anyAdmin')
+ GSMemInvited -> (n', m : invited, pending, current, maxRole', anyAdmin')
+ GSMemPendingApproval -> (n', invited, m : pending, current, maxRole', anyAdmin')
+ _ -> (n', invited, pending, m : current, maxRole', anyAdmin')
| otherwise = acc
deleteInvitedMems :: User -> [GroupMember] -> CM ([ChatError], [GroupMember])
deleteInvitedMems user memsToDelete = do
@@ -2268,24 +2167,18 @@ processChatCommand' vr = \case
delMember db m = do
deleteGroupMember db user m
pure m {memberStatus = GSMemRemoved}
- deletePendingMember :: ([ChatError], [GroupMember], [AChatItem]) -> User -> GroupInfo -> [GroupMember] -> GroupMember -> CM ([ChatError], [GroupMember], [AChatItem])
- deletePendingMember (accErrs, accDeleted, accACIs) user gInfo recipients m = do
- (m', scopeInfo) <- mkMemberSupportChatInfo m
- (errs, deleted, acis) <- deleteMemsSend user gInfo (Just scopeInfo) recipients [m']
- pure (errs <> accErrs, deleted <> accDeleted, acis <> accACIs)
- deleteMemsSend :: User -> GroupInfo -> Maybe GroupChatScopeInfo -> [GroupMember] -> [GroupMember] -> CM ([ChatError], [GroupMember], [AChatItem])
- deleteMemsSend user gInfo chatScopeInfo recipients memsToDelete = case L.nonEmpty memsToDelete of
+ deleteMemsSend :: User -> GroupInfo -> [GroupMember] -> [GroupMember] -> CM ([ChatError], [GroupMember], [AChatItem])
+ deleteMemsSend user gInfo sendToMems memsToDelete = case L.nonEmpty memsToDelete of
Nothing -> pure ([], [], [])
Just memsToDelete' -> do
- let chatScope = toChatScope <$> chatScopeInfo
- events = L.map (\GroupMember {memberId} -> XGrpMemDel memberId withMessages) memsToDelete'
- (msgs_, _gsr) <- sendGroupMessages user gInfo chatScope recipients events
+ let events = L.map (\GroupMember {memberId} -> XGrpMemDel memberId withMessages) memsToDelete'
+ (msgs_, _gsr) <- sendGroupMessages user gInfo sendToMems events
let itemsData = zipWith (fmap . sndItemData) memsToDelete (L.toList msgs_)
- cis_ <- saveSndChatItems user (CDGroupSnd gInfo chatScopeInfo) itemsData Nothing False
+ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) Nothing itemsData Nothing False
when (length cis_ /= length memsToDelete) $ logError "deleteCurrentMems: memsToDelete and cis_ length mismatch"
deleteMembersConnections' user memsToDelete True
(errs, deleted) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (delMember db) memsToDelete)
- let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo chatScopeInfo)) $ rights cis_
+ let acis = map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) $ rights cis_
pure (errs, deleted, acis)
where
sndItemData :: GroupMember -> SndMessage -> NewSndChatItemData c
@@ -2294,47 +2187,37 @@ processChatCommand' vr = \case
ts = ciContentTexts content
in NewSndChatItemData msg content ts M.empty Nothing Nothing Nothing
delMember db m = do
- -- We're in a function used in batch member deletion, and since we're passing same gInfo for each member,
- -- voided result (updated group info) may have incorrect state of membersRequireAttention.
- -- To avoid complicating code by chaining group info updates,
- -- instead we re-read it once after deleting all members before response.
- void $ deleteOrUpdateMemberRecordIO db user gInfo m
+ deleteOrUpdateMemberRecordIO db user m
pure m {memberStatus = GSMemRemoved}
deleteMessages user gInfo@GroupInfo {membership} ms
| groupFeatureMemberAllowed SGFFullDelete membership gInfo = deleteGroupMembersCIs user gInfo ms membership
| otherwise = markGroupMembersCIsDeleted user gInfo ms membership
+ concatTuples :: [([a], [b], [c])] -> ([a], [b], [c])
+ concatTuples xs = (concat as, concat bs, concat cs)
+ where (as, bs, cs) = unzip3 xs
APILeaveGroup groupId -> withUser $ \user@User {userId} -> do
Group gInfo@GroupInfo {membership} members <- withFastStore $ \db -> getGroup db vr user groupId
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
withGroupLock "leaveGroup" groupId . procCmd $ do
cancelFilesInProgress user filesInfo
- let recipients = filter memberCurrentOrPending members
- msg <- sendGroupMessage' user gInfo recipients XGrpLeave
- (gInfo', scopeInfo) <- mkLocalGroupChatScope gInfo
- ci <- saveSndChatItem user (CDGroupSnd gInfo' scopeInfo) msg (CISndGroupEvent SGEUserLeft)
- toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo' scopeInfo) ci]
+ -- TODO [knocking] send to pending approval members (move `memberCurrent` filter from sendGroupMessages_ to call sites)
+ msg <- sendGroupMessage' user gInfo members XGrpLeave
+ ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft)
+ toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci]
-- TODO delete direct connections that were unused
- deleteGroupLinkIfExists user gInfo'
+ deleteGroupLinkIfExists user gInfo
-- member records are not deleted to keep history
deleteMembersConnections' user members True
withFastStore' $ \db -> updateGroupMemberStatus db userId membership GSMemLeft
- pure $ CRLeftMemberUser user gInfo' {membership = membership {memberStatus = GSMemLeft}}
+ pure $ CRLeftMemberUser user gInfo {membership = membership {memberStatus = GSMemLeft}}
APIListMembers groupId -> withUser $ \user ->
CRGroupMembers user <$> withFastStore (\db -> getGroup db vr user groupId)
- -- -- validate: prohibit to delete/archive if member is pending (has to communicate approval or rejection)
- -- APIDeleteGroupConversations groupId _gcId -> withUser $ \user -> do
- -- _gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
- -- ok_ -- CRGroupConversationsArchived
- -- APIArchiveGroupConversations groupId _gcId -> withUser $ \user -> do
- -- _gInfo <- withFastStore $ \db -> getGroupInfo db vr user groupId
- -- ok_ -- CRGroupConversationsDeleted
AddMember gName cName memRole -> withUser $ \user -> do
(groupId, contactId) <- withFastStore $ \db -> (,) <$> getGroupIdByName db user gName <*> getContactIdByName db user cName
processChatCommand $ APIAddMember groupId contactId memRole
JoinGroup gName enableNtfs -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
processChatCommand $ APIJoinGroup groupId enableNtfs
- AcceptMember gName gMemberName memRole -> withMemberName gName gMemberName $ \gId gMemberId -> APIAcceptMember gId gMemberId memRole
MemberRole gName gMemberName memRole -> withMemberName gName gMemberName $ \gId gMemberId -> APIMembersRole gId [gMemberId] memRole
BlockForAll gName gMemberName blocked -> withMemberName gName gMemberName $ \gId gMemberId -> APIBlockMembersForAll gId [gMemberId] blocked
RemoveMembers gName gMemberNames withMessages -> withUser $ \user -> do
@@ -2348,18 +2231,13 @@ processChatCommand' vr = \case
processChatCommand $ APILeaveGroup groupId
DeleteGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand $ APIDeleteChat (ChatRef CTGroup groupId Nothing) (CDMFull True)
+ processChatCommand $ APIDeleteChat (ChatRef CTGroup groupId) (CDMFull True)
ClearGroup gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- processChatCommand $ APIClearChat (ChatRef CTGroup groupId Nothing)
+ processChatCommand $ APIClearChat (ChatRef CTGroup groupId)
ListMembers gName -> withUser $ \user -> do
groupId <- withFastStore $ \db -> getGroupIdByName db user gName
processChatCommand $ APIListMembers groupId
- ListMemberSupportChats gName -> withUser $ \user -> do
- groupId <- withFastStore $ \db -> getGroupIdByName db user gName
- (Group gInfo members) <- withFastStore $ \db -> getGroup db vr user groupId
- let memberSupportChats = filter (isJust . supportChat) members
- pure $ CRMemberSupportChats user gInfo memberSupportChats
APIListGroups userId contactId_ search_ -> withUserId userId $ \user ->
CRGroupsList user <$> withFastStore' (\db -> getUserGroupsWithSummary db vr user contactId_ search_)
ListGroups cName_ search_ -> withUser $ \user@User {userId} -> do
@@ -2406,11 +2284,11 @@ processChatCommand' vr = \case
APICreateMemberContact gId gMemberId -> withUser $ \user -> do
(g, m) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId
assertUserGroupRole g GRAuthor
- unless (groupFeatureMemberAllowed SGFDirectMessages (membership g) g) $ throwCmdError "direct messages not allowed"
+ unless (groupFeatureMemberAllowed SGFDirectMessages (membership g) g) $ throwChatError $ CECommandError "direct messages not allowed"
case memberConn m of
Just mConn@Connection {peerChatVRange} -> do
unless (maxVersion peerChatVRange >= groupDirectInvVersion) $ throwChatError CEPeerChatVRangeIncompatible
- when (isJust $ memberContactId m) $ throwCmdError "member contact already exists"
+ when (isJust $ memberContactId m) $ throwChatError $ CECommandError "member contact already exists"
subMode <- chatReadVar subscriptionMode
-- TODO PQ should negotitate contact connection with PQSupportOn?
(connId, CCLink cReq _) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing Nothing IKPQOff subMode
@@ -2422,11 +2300,10 @@ processChatCommand' vr = \case
_ -> throwChatError CEGroupMemberNotActive
APISendMemberContactInvitation contactId msgContent_ -> withUser $ \user -> do
(g@GroupInfo {groupId}, m, ct, cReq) <- withFastStore $ \db -> getMemberContact db vr user contactId
- when (contactGrpInvSent ct) $ throwCmdError "x.grp.direct.inv already sent"
+ when (contactGrpInvSent ct) $ throwChatError $ CECommandError "x.grp.direct.inv already sent"
case memberConn m of
Just mConn -> do
- -- TODO [knocking] send in correct scope - modiy API
- let msg = XGrpDirectInv cReq msgContent_ Nothing
+ let msg = XGrpDirectInv cReq msgContent_
(sndMsg, _, _) <- sendDirectMemberMessage mConn msg groupId
withFastStore' $ \db -> setContactGrpInvSent db ct True
let ct' = ct {contactGrpInvSent = True}
@@ -2457,7 +2334,7 @@ processChatCommand' vr = \case
processChatCommand $ APISendMessages (SRGroup groupId Nothing) False Nothing [ComposedMessage Nothing (Just quotedItemId) mc mentions]
ClearNoteFolder -> withUser $ \user -> do
folderId <- withFastStore (`getUserNoteFolderId` user)
- processChatCommand $ APIClearChat (ChatRef CTLocal folderId Nothing)
+ processChatCommand $ APIClearChat (ChatRef CTLocal folderId)
LastChats count_ -> withUser' $ \user -> do
let count = fromMaybe 5000 count_
(errs, previews) <- partitionEithers <$> withFastStore' (\db -> getChatPreviews db vr user False (PTLast count) clqNoFilters)
@@ -2494,7 +2371,7 @@ processChatCommand' vr = \case
SendFile chatName f -> withUser $ \user -> do
chatRef <- getChatRef user chatName
case chatRef of
- ChatRef CTLocal folderId _ -> processChatCommand $ APICreateChatItems folderId [composedMessage (Just f) (MCFile "")]
+ ChatRef CTLocal folderId -> processChatCommand $ APICreateChatItems folderId [composedMessage (Just f) (MCFile "")]
_ -> withSendRef chatRef $ \sendRef -> processChatCommand $ APISendMessages sendRef False Nothing [composedMessage (Just f) (MCFile "")]
SendImage chatName f@(CryptoFile fPath _) -> withUser $ \user -> do
chatRef <- getChatRef user chatName
@@ -2531,21 +2408,18 @@ processChatCommand' vr = \case
| otherwise -> do
fileAgentConnIds <- cancelSndFile user ftm fts True
deleteAgentConnectionsAsync fileAgentConnIds
- cref_ <- withFastStore' $ \db -> lookupChatRefByFileId db user fileId
- aci_ <- withFastStore $ \db -> lookupChatItemByFileId db vr user fileId
- case (cref_, aci_) of
- (Nothing, _) ->
- pure $ CRSndFileCancelled user Nothing ftm fts
- (Just (ChatRef CTDirect contactId _), Just aci) -> do
+ withFastStore (\db -> liftIO $ lookupChatRefByFileId db user fileId) >>= \case
+ Nothing -> pure ()
+ Just (ChatRef CTDirect contactId) -> do
(contact, sharedMsgId) <- withFastStore $ \db -> (,) <$> getContact db vr user contactId <*> getSharedMsgIdByFileId db userId fileId
void . sendDirectContactMessage user contact $ XFileCancel sharedMsgId
- pure $ CRSndFileCancelled user (Just aci) ftm fts
- (Just (ChatRef CTGroup groupId scope), Just aci) -> do
- (gInfo, sharedMsgId) <- withFastStore $ \db -> (,) <$> getGroupInfo db vr user groupId <*> getSharedMsgIdByFileId db userId fileId
- (_chatScopeInfo, recipients) <- getGroupRecipients vr user gInfo scope groupKnockingVersion
- void . sendGroupMessage user gInfo scope recipients $ XFileCancel sharedMsgId
- pure $ CRSndFileCancelled user (Just aci) ftm fts
- (Just _, _) -> throwChatError $ CEFileInternal "invalid chat ref for file transfer"
+ Just (ChatRef CTGroup groupId) -> do
+ (Group gInfo ms, sharedMsgId) <- withFastStore $ \db -> (,) <$> getGroup db vr user groupId <*> getSharedMsgIdByFileId db userId fileId
+ -- TODO [knocking] send separately to pending approval member
+ void . sendGroupMessage user gInfo ms $ XFileCancel sharedMsgId
+ Just _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer"
+ ci <- withFastStore $ \db -> lookupChatItemByFileId db vr user fileId
+ pure $ CRSndFileCancelled user ci ftm fts
where
fileCancelledOrCompleteSMP SndFileTransfer {fileStatus = s} =
s == FSCancelled || (s == FSComplete && isNothing xftpSndFile)
@@ -2572,7 +2446,7 @@ processChatCommand' vr = \case
pure $ CRFileTransferStatus user fileStatus
Just ci@(AChatItem _ _ _ ChatItem {file}) -> case file of
Just CIFile {fileProtocol = FPLocal} ->
- throwCmdError "not supported for local files"
+ throwChatError $ CECommandError "not supported for local files"
Just CIFile {fileProtocol = FPXFTP} ->
pure $ CRFileTransferStatusXFTP user ci
_ -> do
@@ -2599,11 +2473,6 @@ processChatCommand' vr = \case
SetGroupFeatureRole (AGFR f) gName enabled role ->
updateGroupProfileByName gName $ \p ->
p {groupPreferences = Just . setGroupPreferenceRole f enabled role $ groupPreferences p}
- SetGroupMemberAdmissionReview gName reviewAdmissionApplication ->
- updateGroupProfileByName gName $ \p@GroupProfile {memberAdmission} ->
- case memberAdmission of
- Nothing -> p {memberAdmission = Just (emptyGroupMemberAdmission :: GroupMemberAdmission) {review = reviewAdmissionApplication}}
- Just ma -> p {memberAdmission = Just (ma :: GroupMemberAdmission) {review = reviewAdmissionApplication}}
SetUserTimedMessages onOff -> withUser $ \user@User {profile} -> do
let allowed = if onOff then FAYes else FANo
pref = TimedMessagesPreference allowed Nothing
@@ -2717,18 +2586,17 @@ processChatCommand' vr = \case
ok_ = pure $ CRCmdOk Nothing
ok = pure . CRCmdOk . Just
getChatRef :: User -> ChatName -> CM ChatRef
- getChatRef user (ChatName cType name) = do
- chatId <- case cType of
+ getChatRef user (ChatName cType name) =
+ ChatRef cType <$> case cType of
CTDirect -> withFastStore $ \db -> getContactIdByName db user name
CTGroup -> withFastStore $ \db -> getGroupIdByName db user name
CTLocal
| name == "" -> withFastStore (`getUserNoteFolderId` user)
- | otherwise -> throwCmdError "not supported"
- _ -> throwCmdError "not supported"
- pure $ ChatRef cType chatId Nothing
+ | otherwise -> throwChatError $ CECommandError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
getChatRefAndMentions :: User -> ChatName -> Text -> CM (ChatRef, Map MemberName GroupMemberId)
getChatRefAndMentions user cName msg = do
- chatRef@(ChatRef cType chatId _) <- getChatRef user cName
+ chatRef@(ChatRef cType chatId) <- getChatRef user cName
(chatRef,) <$> case cType of
CTGroup -> withFastStore' $ \db -> getMessageMentions db user chatId msg
_ -> pure []
@@ -2763,17 +2631,17 @@ processChatCommand' vr = \case
withFastStore' $ \db -> setConnectionVerified db user connId Nothing
pure $ CRConnectionVerified user False code'
getSentChatItemIdByText :: User -> ChatRef -> Text -> CM Int64
- getSentChatItemIdByText user@User {userId, localDisplayName} (ChatRef cType cId _scope) msg = case cType of
+ getSentChatItemIdByText user@User {userId, localDisplayName} (ChatRef cType cId) msg = case cType of
CTDirect -> withFastStore $ \db -> getDirectChatItemIdByText db userId cId SMDSnd msg
CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText db user cId (Just localDisplayName) msg
CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText db user cId SMDSnd msg
- _ -> throwCmdError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
getChatItemIdByText :: User -> ChatRef -> Text -> CM Int64
- getChatItemIdByText user (ChatRef cType cId _scope) msg = case cType of
+ getChatItemIdByText user (ChatRef cType cId) msg = case cType of
CTDirect -> withFastStore $ \db -> getDirectChatItemIdByText' db user cId msg
CTGroup -> withFastStore $ \db -> getGroupChatItemIdByText' db user cId msg
CTLocal -> withFastStore $ \db -> getLocalChatItemIdByText' db user cId msg
- _ -> throwCmdError "not supported"
+ _ -> throwChatError $ CECommandError "not supported"
connectViaContact :: User -> IncognitoEnabled -> CreatedLinkContact -> CM ChatResponse
connectViaContact user@User {userId} incognito (CCLink cReq@(CRContactUri ConnReqUriData {crClientData}) sLnk) = withInvitationLock "connectViaContact" (strEncode cReq) $ do
let groupLinkId = crClientData >>= decodeJSON >>= \(CRDataGroup gli) -> Just gli
@@ -2932,18 +2800,15 @@ processChatCommand' vr = \case
GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} <-
withStore $ \db -> getGroupMemberByMemberId db vr user g businessId
let p'' = p' {displayName, fullName, image} :: GroupProfile
- recipients = filter memberCurrentOrPending oldMs
- void $ sendGroupMessage user g' Nothing recipients (XGrpInfo p'')
+ -- TODO [knocking] send to pending approval members (move `memberCurrent` filter from sendGroupMessages_ to call sites)
+ void $ sendGroupMessage user g' oldMs (XGrpInfo p'')
let ps' = fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
- recipients = filter memberCurrentOrPending newMs
- sendGroupMessage user g' Nothing recipients $ XGrpPrefs ps'
- Nothing -> do
- let recipients = filter memberCurrentOrPending ms
- sendGroupMessage user g' Nothing recipients (XGrpInfo p')
- let cd = CDGroupSnd g' Nothing
+ sendGroupMessage user g' newMs $ XGrpPrefs ps'
+ Nothing -> sendGroupMessage user g' ms (XGrpInfo p')
+ let cd = CDGroupSnd g'
unless (sameGroupProfileInfo p p') $ do
ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p')
- toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g' Nothing) ci]
+ toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat g') ci]
createGroupFeatureChangedItems user cd CISndGroupFeature g g'
pure $ CRGroupUpdated user g g' Nothing
checkValidName :: GroupName -> CM ()
@@ -2958,14 +2823,16 @@ processChatCommand' vr = \case
when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined g)
when (memberRemoved membership) $ throwChatError CEGroupMemberUserRemoved
unless (memberActive membership) $ throwChatError CEGroupMemberNotActive
- delGroupChatItemsForMembers :: User -> GroupInfo -> Maybe GroupChatScopeInfo -> [GroupMember] -> [CChatItem 'CTGroup] -> CM [ChatItemDeletion]
- delGroupChatItemsForMembers user gInfo chatScopeInfo ms items = do
+ delGroupChatItemsForMembers :: User -> GroupInfo -> [GroupMember] -> [CChatItem 'CTGroup] -> CM [ChatItemDeletion]
+ delGroupChatItemsForMembers user gInfo ms items = do
assertDeletable gInfo items
assertUserGroupRole gInfo GRAdmin -- TODO GRModerator when most users migrate
let msgMemIds = itemsMsgMemIds gInfo items
events = L.nonEmpty $ map (\(msgId, memId) -> XMsgDel msgId (Just memId)) msgMemIds
- mapM_ (sendGroupMessages_ user gInfo ms) events
- delGroupChatItems user gInfo chatScopeInfo items True
+ -- TODO [knocking] validate: only current members or only single pending approval member,
+ -- TODO or prohibit pending approval members (only moderation and reports use this)
+ mapM_ (sendGroupMessages user gInfo ms) events
+ delGroupChatItems user gInfo items True
where
assertDeletable :: GroupInfo -> [CChatItem 'CTGroup] -> CM ()
assertDeletable GroupInfo {membership = GroupMember {memberRole = membershipMemRole}} items' =
@@ -2985,16 +2852,16 @@ processChatCommand' vr = \case
CIGroupRcv GroupMember {memberId} -> (msgId, memberId)
CIGroupSnd -> (msgId, membershipMemId)
- delGroupChatItems :: User -> GroupInfo -> Maybe GroupChatScopeInfo -> [CChatItem 'CTGroup] -> Bool -> CM [ChatItemDeletion]
- delGroupChatItems user gInfo@GroupInfo {membership} chatScopeInfo items moderation = do
+ delGroupChatItems :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Bool -> CM [ChatItemDeletion]
+ delGroupChatItems user gInfo@GroupInfo {membership} items moderation = do
deletedTs <- liftIO getCurrentTime
when moderation $ do
ciIds <- concat <$> withStore' (\db -> forM items $ \(CChatItem _ ci) -> markMessageReportsDeleted db user gInfo ci membership deletedTs)
unless (null ciIds) $ toView $ CEvtGroupChatItemsDeleted user gInfo ciIds True (Just membership)
let m = if moderation then Just membership else Nothing
if groupFeatureMemberAllowed SGFFullDelete membership gInfo
- then deleteGroupCIs user gInfo chatScopeInfo items m deletedTs
- else markGroupCIsDeleted user gInfo chatScopeInfo items m deletedTs
+ then deleteGroupCIs user gInfo items m deletedTs
+ else markGroupCIsDeleted user gInfo items m deletedTs
updateGroupProfileByName :: GroupName -> (GroupProfile -> GroupProfile) -> CM ChatResponse
updateGroupProfileByName gName update = withUser $ \user -> do
g@(Group GroupInfo {groupProfile = p} _) <- withStore $ \db ->
@@ -3073,7 +2940,7 @@ processChatCommand' vr = \case
ci <- saveSndChatItem' user (CDDirectSnd ct) msg content Nothing Nothing Nothing timed_ False
toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDSnd (DirectChat ct) ci]
forM_ (timed_ >>= timedDeleteAt') $
- startProximateTimedItemThread user (ChatRef CTDirect contactId Nothing, chatItemId' ci)
+ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci)
drgRandomBytes :: Int -> CM ByteString
drgRandomBytes n = asks random >>= atomically . C.randomBytes n
privateGetUser :: UserId -> CM User
@@ -3137,8 +3004,8 @@ processChatCommand' vr = \case
gId <- getGroupIdByName db user name
GroupInfo {chatSettings} <- getGroupInfo db vr user gId
pure (gId, chatSettings)
- _ -> throwCmdError "not supported"
- processChatCommand $ APISetChatSettings (ChatRef cType chatId Nothing) $ updateSettings chatSettings
+ _ -> throwChatError $ CECommandError "not supported"
+ processChatCommand $ APISetChatSettings (ChatRef cType chatId) $ updateSettings chatSettings
connectPlan :: User -> AConnectionLink -> CM (ACreatedConnLink, ConnectionPlan)
connectPlan user (ACL SCMInvitation cLink) = case cLink of
CLFull cReq -> invitationReqAndPlan cReq Nothing
@@ -3165,7 +3032,7 @@ processChatCommand' vr = \case
withFastStore' (\db -> getGroupInfoViaUserShortLink db vr user l') >>= \case
Just (cReq, g) -> pure (ACCL SCMContact $ CCLink cReq (Just l'), CPGroupLink (GLPOwnLink g))
Nothing -> getShortLinkConnReq user l' >>= (`contactReqAndPlan` Just l')
- CCTChannel -> throwCmdError "channel links are not supported in this version"
+ CCTChannel -> throwChatError $ CECommandError "channel links are not supported in this version"
where
contactReqAndPlan cReq sLnk_ = do
plan <- contactRequestPlan user cReq `catchChatError` (pure . CPError)
@@ -3204,7 +3071,7 @@ processChatCommand' vr = \case
| otherwise -> case ct_ of
Just ct -> pure $ CPInvitationLink (ILPKnown ct)
Nothing -> throwChatError $ CEInternalError "ready RcvDirectMsgConnection connection should have associated contact"
- _ -> throwCmdError "found connection entity is not RcvDirectMsgConnection"
+ _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection"
contactRequestPlan :: User -> ConnReqContact -> CM ConnectionPlan
contactRequestPlan user (CRContactUri crData) = do
let ConnReqUriData {crClientData} = crData
@@ -3227,7 +3094,7 @@ processChatCommand' vr = \case
| contactDeleted ct -> pure $ CPContactAddress CAPOk
| otherwise -> pure $ CPContactAddress (CAPKnown ct)
Just (RcvGroupMsgConnection _ gInfo _) -> groupPlan gInfo
- Just _ -> throwCmdError "found connection entity is not RcvDirectMsgConnection or RcvGroupMsgConnection"
+ Just _ -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection or RcvGroupMsgConnection"
-- group link
Just _ ->
withFastStore' (\db -> getGroupInfoByUserContactLinkConnReq db vr user cReqSchemas) >>= \case
@@ -3241,7 +3108,7 @@ processChatCommand' vr = \case
(Nothing, Just (RcvDirectMsgConnection _ (Just ct)))
| not (contactReady ct) && contactActive ct -> pure $ CPGroupLink (GLPConnectingProhibit gInfo_)
| otherwise -> pure $ CPGroupLink GLPOk
- (Nothing, Just _) -> throwCmdError "found connection entity is not RcvDirectMsgConnection"
+ (Nothing, Just _) -> throwChatError $ CECommandError "found connection entity is not RcvDirectMsgConnection"
(Just gInfo, _) -> groupPlan gInfo
where
groupPlan gInfo@GroupInfo {membership}
@@ -3290,18 +3157,18 @@ processChatCommand' vr = \case
timed_ <- contactCITimed ct
updateDirectChatItemView user ct itemId aciContent False False timed_ Nothing
forM_ (timed_ >>= timedDeleteAt') $
- startProximateTimedItemThread user (ChatRef CTDirect contactId Nothing, itemId)
+ startProximateTimedItemThread user (ChatRef CTDirect contactId, itemId)
_ -> pure () -- prohibited
assertAllowedContent :: MsgContent -> CM ()
assertAllowedContent = \case
- MCReport {} -> throwCmdError "sending reports via this API is not supported"
+ MCReport {} -> throwChatError $ CECommandError "sending reports via this API is not supported"
_ -> pure ()
assertAllowedContent' :: ComposedMessage -> CM ()
assertAllowedContent' ComposedMessage {msgContent} = assertAllowedContent msgContent
assertNoMentions :: ComposedMessage -> CM ()
assertNoMentions ComposedMessage {mentions}
| null mentions = pure ()
- | otherwise = throwCmdError "mentions are not supported in this chat"
+ | otherwise = throwChatError $ CECommandError "mentions are not supported in this chat"
sendContactContentMessages :: User -> ContactId -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
sendContactContentMessages user contactId live itemTTL cmrs = do
assertMultiSendable live cmrs
@@ -3313,7 +3180,7 @@ processChatCommand' vr = \case
assertVoiceAllowed :: Contact -> CM ()
assertVoiceAllowed ct =
when (not (featureAllowed SCFVoice forUser ct) && any (\(ComposedMessage {msgContent}, _, _, _) -> isVoice msgContent) cmrs) $
- throwCmdError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFVoice)
+ throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFVoice))
processComposedMessages :: Contact -> CM ChatResponse
processComposedMessages ct = do
(fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers
@@ -3322,11 +3189,11 @@ processChatCommand' vr = \case
msgs_ <- sendDirectContactMessages user ct $ L.map XMsgNew msgContainers
let itemsData = prepareSndItemsData (L.toList cmrs) (L.toList ciFiles_) (L.toList quotedItems_) msgs_
when (length itemsData /= length cmrs) $ logError "sendContactContentMessages: cmrs and itemsData length mismatch"
- r@(_, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) itemsData timed_ live
+ r@(_, cis) <- partitionEithers <$> saveSndChatItems user (CDDirectSnd ct) Nothing itemsData timed_ live
processSendErrs r
forM_ (timed_ >>= timedDeleteAt') $ \deleteAt ->
forM_ cis $ \ci ->
- startProximateTimedItemThread user (ChatRef CTDirect contactId Nothing, chatItemId' ci) deleteAt
+ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) deleteAt
pure $ CRNewChatItems user (map (AChatItem SCTDirect SMDSnd (DirectChat ct)) cis)
where
setupSndFileTransfers :: CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd)))
@@ -3341,8 +3208,8 @@ processChatCommand' vr = \case
prepareMsgs cmsFileInvs timed_ = withFastStore $ \db ->
forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded, _, _), fInv_) -> do
case (quotedItemId, itemForwarded) of
- (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live) Nothing), Nothing)
- (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live) Nothing), Nothing)
+ (Nothing, Nothing) -> pure (MCSimple (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live)), Nothing)
+ (Nothing, Just _) -> pure (MCForward (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live)), Nothing)
(Just qiId, Nothing) -> do
CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, file} <-
getDirectChatItem db user contactId qiId
@@ -3350,7 +3217,7 @@ processChatCommand' vr = \case
let msgRef = MsgRef {msgId = itemSharedMsgId, sentAt = itemTs, sent, memberId = Nothing}
qmc = quoteContent mc origQmc file
quotedItem = CIQuote {chatDir = qd, itemId = Just qiId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc, formattedText}
- pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live) Nothing), Just quotedItem)
+ pure (MCQuote QuotedMsg {msgRef, content = qmc} (ExtMsgContent mc M.empty fInv_ (ttl' <$> timed_) (justTrue live)), Just quotedItem)
(Just _, Just _) -> throwError SEInvalidQuote
where
quoteData :: ChatItem c d -> ExceptT StoreError IO (MsgContent, CIQDirection 'CTDirect, Bool)
@@ -3358,69 +3225,70 @@ processChatCommand' vr = \case
quoteData ChatItem {content = CISndMsgContent qmc} = pure (qmc, CIQDirectSnd, True)
quoteData ChatItem {content = CIRcvMsgContent qmc} = pure (qmc, CIQDirectRcv, False)
quoteData _ = throwError SEInvalidQuote
- sendGroupContentMessages :: User -> GroupInfo -> Maybe GroupChatScope -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
- sendGroupContentMessages user gInfo scope live itemTTL cmrs = do
+ sendGroupContentMessages :: User -> GroupInfo -> Maybe GroupMemberId -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
+ sendGroupContentMessages user gInfo@GroupInfo {membership} directMemId_ live itemTTL cmrs = do
assertMultiSendable live cmrs
- (chatScopeInfo, recipients) <- getGroupRecipients vr user gInfo scope modsCompatVersion
- sendGroupContentMessages_ user gInfo scope chatScopeInfo recipients live itemTTL cmrs
- where
- hasReport = any (\(ComposedMessage {msgContent}, _, _, _) -> isReport msgContent) cmrs
- modsCompatVersion = if hasReport then contentReportsVersion else groupKnockingVersion
- sendGroupContentMessages_ :: User -> GroupInfo -> Maybe GroupChatScope -> Maybe GroupChatScopeInfo -> [GroupMember] -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
- sendGroupContentMessages_ user gInfo@GroupInfo {groupId, membership} scope chatScopeInfo recipients live itemTTL cmrs = do
+ (ms, numFileInvs, notInHistory_) <- case directMemId_ of
+ Nothing -> do
+ ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
+ pure (ms, length $ filter memberCurrent ms, Nothing)
+ Just dmId -> do
+ when (dmId == groupMemberId' membership) $ throwChatError $ CECommandError "cannot send to self"
+ dm <- withFastStore $ \db -> getGroupMemberById db vr user dmId
+ unless (memberStatus dm == GSMemPendingApproval) $ throwChatError $ CECommandError "cannot send directly to member not pending approval"
+ pure ([dm], 1, Just NotInHistory)
+ sendGroupContentMessages_ user gInfo notInHistory_ ms numFileInvs live itemTTL cmrs
+ sendGroupContentMessages_ :: User -> GroupInfo -> Maybe NotInHistory -> [GroupMember] -> Int -> Bool -> Maybe Int -> NonEmpty ComposedMessageReq -> CM ChatResponse
+ sendGroupContentMessages_ user gInfo@GroupInfo {groupId, membership} notInHistory_ ms numFileInvs live itemTTL cmrs = do
+ -- TODO [knocking] pass GroupSndScope?
+ let allowedRole = case ms of
+ [m] | memberCategory m == GCHostMember && memberStatus membership == GSMemPendingApproval -> Nothing
+ _ -> Just GRAuthor
forM_ allowedRole $ assertUserGroupRole gInfo
assertGroupContentAllowed
processComposedMessages
where
- allowedRole :: Maybe GroupMemberRole
- allowedRole = case scope of
- Nothing -> Just GRAuthor
- Just (GCSMemberSupport Nothing)
- | memberPending membership -> Nothing
- | otherwise -> Just GRObserver
- Just (GCSMemberSupport (Just _gmId)) -> Just GRModerator
assertGroupContentAllowed :: CM ()
assertGroupContentAllowed =
case findProhibited (L.toList cmrs) of
- Just f -> throwCmdError $ "feature not allowed " <> T.unpack (groupFeatureNameText f)
+ Just f -> throwChatError (CECommandError $ "feature not allowed " <> T.unpack (groupFeatureNameText f))
Nothing -> pure ()
where
findProhibited :: [ComposedMessageReq] -> Maybe GroupFeature
findProhibited =
foldr'
- (\(ComposedMessage {fileSource, msgContent = mc}, _, (_, ft), _) acc -> prohibitedGroupContent gInfo membership chatScopeInfo mc ft fileSource True <|> acc)
+ (\(ComposedMessage {fileSource, msgContent = mc}, _, (_, ft), _) acc -> prohibitedGroupContent gInfo membership mc ft fileSource True <|> acc)
Nothing
- processComposedMessages :: CM ChatResponse
+ processComposedMessages :: CM ChatResponse
processComposedMessages = do
- (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers (length recipients)
+ (fInvs_, ciFiles_) <- L.unzip <$> setupSndFileTransfers numFileInvs
timed_ <- sndGroupCITimed live gInfo itemTTL
(chatMsgEvents, quotedItems_) <- L.unzip <$> prepareMsgs (L.zip cmrs fInvs_) timed_
- (msgs_, gsr) <- sendGroupMessages user gInfo Nothing recipients chatMsgEvents
+ (msgs_, gsr) <- sendGroupMessages user gInfo ms chatMsgEvents
let itemsData = prepareSndItemsData (L.toList cmrs) (L.toList ciFiles_) (L.toList quotedItems_) (L.toList msgs_)
- cis_ <- saveSndChatItems user (CDGroupSnd gInfo chatScopeInfo) itemsData timed_ live
+ cis_ <- saveSndChatItems user (CDGroupSnd gInfo) notInHistory_ itemsData timed_ live
when (length cis_ /= length cmrs) $ logError "sendGroupContentMessages: cmrs and cis_ length mismatch"
createMemberSndStatuses cis_ msgs_ gsr
let r@(_, cis) = partitionEithers cis_
processSendErrs r
forM_ (timed_ >>= timedDeleteAt') $ \deleteAt ->
forM_ cis $ \ci ->
- startProximateTimedItemThread user (ChatRef CTGroup groupId scope, chatItemId' ci) deleteAt
- pure $ CRNewChatItems user (map (AChatItem SCTGroup SMDSnd (GroupChat gInfo chatScopeInfo)) cis)
+ startProximateTimedItemThread user (ChatRef CTGroup groupId, chatItemId' ci) deleteAt
+ pure $ CRNewChatItems user (map (AChatItem SCTGroup SMDSnd (GroupChat gInfo)) cis)
where
setupSndFileTransfers :: Int -> CM (NonEmpty (Maybe FileInvitation, Maybe (CIFile 'MDSnd)))
setupSndFileTransfers n =
forM cmrs $ \(ComposedMessage {fileSource = file_}, _, _, _) -> case file_ of
Just file -> do
fileSize <- checkSndFile file
- (fInv, ciFile) <- xftpSndFileTransfer user file fileSize n $ CGGroup gInfo recipients
+ (fInv, ciFile) <- xftpSndFileTransfer user file fileSize n $ CGGroup gInfo ms
pure (Just fInv, Just ciFile)
Nothing -> pure (Nothing, Nothing)
prepareMsgs :: NonEmpty (ComposedMessageReq, Maybe FileInvitation) -> Maybe CITimed -> CM (NonEmpty (ChatMsgEvent 'Json, Maybe (CIQuote 'CTGroup)))
prepareMsgs cmsFileInvs timed_ = withFastStore $ \db ->
forM cmsFileInvs $ \((ComposedMessage {quotedItemId, msgContent = mc}, itemForwarded, _, ciMentions), fInv_) ->
- let msgScope = toMsgScope gInfo <$> chatScopeInfo
- mentions = M.map (\CIMention {memberId} -> MsgMention {memberId}) ciMentions
- in prepareGroupMsg db user gInfo msgScope mc mentions quotedItemId itemForwarded fInv_ timed_ live
+ let mentions = M.map (\CIMention {memberId} -> MsgMention {memberId}) ciMentions
+ in prepareGroupMsg db user gInfo mc mentions quotedItemId itemForwarded fInv_ timed_ live
createMemberSndStatuses ::
[Either ChatError (ChatItem 'CTGroup 'MDSnd)] ->
NonEmpty (Either ChatError SndMessage) ->
@@ -3465,7 +3333,7 @@ processChatCommand' vr = \case
-- UI doesn't allow composing with multiple quotes, so api prohibits it as well, and doesn't bother
-- batching retrieval of quoted messages (prepareMsgs).
when (live || length (L.filter (\(ComposedMessage {quotedItemId}, _, _, _) -> isJust quotedItemId) cmrs) > 1) $
- throwCmdError "invalid multi send: live and more than one quote not supported"
+ throwChatError (CECommandError "invalid multi send: live and more than one quote not supported")
xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> CM (FileInvitation, CIFile 'MDSnd)
xftpSndFileTransfer user file fileSize n contactOrGroup = do
(fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup
@@ -3548,7 +3416,7 @@ processChatCommand' vr = \case
assertNoQuotes :: CM ()
assertNoQuotes =
when (any (\(ComposedMessage {quotedItemId}, _, _, _) -> isJust quotedItemId) cmrs) $
- throwCmdError "createNoteFolderContentItems: quotes not supported"
+ throwChatError (CECommandError "createNoteFolderContentItems: quotes not supported")
createLocalFiles :: NoteFolder -> UTCTime -> CM (NonEmpty (Maybe (CIFile 'MDSnd)))
createLocalFiles nf createdAt =
forM cmrs $ \(ComposedMessage {fileSource = file_}, _, _, _) ->
@@ -3571,9 +3439,9 @@ processChatCommand' vr = \case
CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId)
withSendRef :: ChatRef -> (SendRef -> CM ChatResponse) -> CM ChatResponse
withSendRef chatRef a = case chatRef of
- ChatRef CTDirect cId _ -> a $ SRDirect cId
- ChatRef CTGroup gId scope -> a $ SRGroup gId scope
- _ -> throwCmdError "not supported"
+ ChatRef CTDirect cId -> a $ SRDirect cId
+ ChatRef CTGroup gId -> a $ SRGroup gId Nothing
+ _ -> throwChatError $ CECommandError "not supported"
protocolServers :: UserProtocol p => SProtocolType p -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP]) -> ([Maybe ServerOperator], [UserServer 'PSMP], [UserServer 'PXFTP])
protocolServers p (operators, smpServers, xftpServers) = case p of
@@ -4108,14 +3976,11 @@ chatCommandP =
"/_add #" *> (APIAddMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
"/_join #" *> (APIJoinGroup <$> A.decimal <*> pure MFAll), -- needs to be changed to support in UI
"/_accept member #" *> (APIAcceptMember <$> A.decimal <* A.space <*> A.decimal <*> memberRole),
- "/_delete member chat #" *> (APIDeleteMemberSupportChat <$> A.decimal <* A.space <*> A.decimal),
"/_member role #" *> (APIMembersRole <$> A.decimal <*> _strP <*> memberRole),
"/_block #" *> (APIBlockMembersForAll <$> A.decimal <*> _strP <* " blocked=" <*> onOffP),
"/_remove #" *> (APIRemoveMembers <$> A.decimal <*> _strP <*> (" messages=" *> onOffP <|> pure False)),
"/_leave #" *> (APILeaveGroup <$> A.decimal),
"/_members #" *> (APIListMembers <$> A.decimal),
- -- "/_archive conversations #" *> (APIArchiveGroupConversations <$> A.decimal <*> _strP),
- -- "/_delete conversations #" *> (APIDeleteGroupConversations <$> A.decimal <*> _strP),
"/_server test " *> (APITestProtoServer <$> A.decimal <* A.space <*> strP),
"/smp test " *> (TestProtoServer . AProtoServerWithAuth SPSMP <$> strP),
"/xftp test " *> (TestProtoServer . AProtoServerWithAuth SPXFTP <$> strP),
@@ -4196,7 +4061,6 @@ chatCommandP =
"/_group " *> (APINewGroup <$> A.decimal <*> incognitoOnOffP <* A.space <*> jsonP),
("/add " <|> "/a ") *> char_ '#' *> (AddMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)),
("/join " <|> "/j ") *> char_ '#' *> (JoinGroup <$> displayNameP <*> (" mute" $> MFNone <|> pure MFAll)),
- "/accept member " *> char_ '#' *> (AcceptMember <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> (memberRole <|> pure GRMember)),
("/member role " <|> "/mr ") *> char_ '#' *> (MemberRole <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <*> memberRole),
"/block for all #" *> (BlockForAll <$> displayNameP <* A.space <*> (char_ '@' *> displayNameP) <*> pure True),
"/unblock for all #" *> (BlockForAll <$> displayNameP <* A.space <*> (char_ '@' *> displayNameP) <*> pure False),
@@ -4208,7 +4072,6 @@ chatCommandP =
"/clear #" *> (ClearGroup <$> displayNameP),
"/clear " *> char_ '@' *> (ClearContact <$> displayNameP),
("/members " <|> "/ms ") *> char_ '#' *> (ListMembers <$> displayNameP),
- "/member support chats #" *> (ListMemberSupportChats <$> displayNameP),
"/_groups" *> (APIListGroups <$> A.decimal <*> optional (" @" *> A.decimal) <*> optional (A.space *> stringP)),
("/groups" <|> "/gs") *> (ListGroups <$> optional (" @" *> displayNameP) <*> optional (A.space *> stringP)),
"/_group_profile #" *> (APIUpdateGroupProfile <$> A.decimal <* A.space <*> jsonP),
@@ -4243,7 +4106,8 @@ chatCommandP =
ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <* A.space <* A.char '@' <*> (Just <$> displayNameP) <* A.space <*> msgTextP,
ForwardGroupMessage <$> chatNameP <* " <- #" <*> displayNameP <*> pure Nothing <* A.space <*> msgTextP,
ForwardLocalMessage <$> chatNameP <* " <- * " <*> msgTextP,
- SendMessage <$> sendNameP <* A.space <*> msgTextP,
+ SendMessage <$> chatNameP <* A.space <*> msgTextP,
+ "/* " *> (SendMessage (ChatName CTLocal "") <$> msgTextP),
"@#" *> (SendMemberContactMessage <$> displayNameP <* A.space <* char_ '@' <*> displayNameP <* A.space <*> msgTextP),
"/live " *> (SendLiveMessage <$> chatNameP <*> (A.space *> msgTextP <|> pure "")),
(">@" <|> "> @") *> sendMsgQuote (AMsgDirection SMDRcv),
@@ -4307,7 +4171,6 @@ chatCommandP =
"/set disappear " *> (SetUserTimedMessages <$> (("yes" $> True) <|> ("no" $> False))),
"/set reports #" *> (SetGroupFeature (AGFNR SGFReports) <$> displayNameP <*> _strP),
"/set links #" *> (SetGroupFeatureRole (AGFR SGFSimplexLinks) <$> displayNameP <*> _strP <*> optional memberRole),
- "/set admission review #" *> (SetGroupMemberAdmissionReview <$> displayNameP <*> (A.space *> memberCriteriaP)),
("/incognito" <* optional (A.space *> onOffP)) $> ChatHelp HSIncognito,
"/set device name " *> (SetLocalDeviceName <$> textP),
"/list remote hosts" $> ListRemoteHosts,
@@ -4413,7 +4276,6 @@ chatCommandP =
history = Just HistoryGroupPreference {enable = FEOn}
}
pure GroupProfile {displayName = gName, fullName, description = Nothing, image = Nothing, groupPreferences, memberAdmission = Nothing}
- memberCriteriaP = ("all" $> Just MCAll) <|> ("off" $> Nothing)
fullNameP = A.space *> textP <|> pure ""
textP = safeDecodeUtf8 <$> A.takeByteString
pwdP = jsonP <|> (UserPwd . safeDecodeUtf8 <$> A.takeTill (== ' '))
@@ -4444,29 +4306,10 @@ chatCommandP =
CTLocal -> pure $ ChatName CTLocal ""
ct -> ChatName ct <$> displayNameP
chatNameP' = ChatName <$> (chatTypeP <|> pure CTDirect) <*> displayNameP
- chatRefP = do
- chatTypeP >>= \case
- CTGroup -> ChatRef CTGroup <$> A.decimal <*> optional gcScopeP
- cType -> (\chatId -> ChatRef cType chatId Nothing) <$> A.decimal
+ chatRefP = ChatRef <$> chatTypeP <*> A.decimal
sendRefP =
(A.char '@' $> SRDirect <*> A.decimal)
- <|> (A.char '#' $> SRGroup <*> A.decimal <*> optional gcScopeP)
- gcScopeP = "(_support" *> (GCSMemberSupport <$> optional (A.char ':' *> A.decimal)) <* A.char ')'
- sendNameP =
- (A.char '@' $> SNDirect <*> displayNameP)
- <|> (A.char '#' $> SNGroup <*> displayNameP <*> gScopeNameP)
- <|> ("/*" $> SNLocal)
- gScopeNameP =
- (supportPfx *> (Just . GSNMemberSupport <$> optional supportMember) <* A.char ')')
- -- this branch fails on "(support" followed by incorrect syntax,
- -- to avoid sending message to the whole group as `optional gScopeNameP` would do
- <|> (optional supportPfx >>= mapM (\_ -> fail "bad chat scope"))
- where
- supportPfx = A.takeWhile isSpace *> "(support"
- supportMember = safeDecodeUtf8 <$> (A.char ':' *> A.takeWhile isSpace *> (A.take . lengthTillLastParen =<< A.lookAhead displayNameP_))
- lengthTillLastParen s = case B.unsnoc s of
- Just (_, ')') -> B.length s - 1
- _ -> B.length s
+ <|> (A.char '#' $> SRGroup <*> A.decimal <*> optional (" @" *> A.decimal))
msgCountP = A.space *> A.decimal <|> pure 10
ciTTLDecimal = ("default" $> Nothing) <|> (Just <$> A.decimal)
ciTTL =
@@ -4532,11 +4375,7 @@ chatCommandP =
char_ = optional . A.char
displayNameP :: Parser Text
-displayNameP = safeDecodeUtf8 <$> displayNameP_
-{-# INLINE displayNameP #-}
-
-displayNameP_ :: Parser ByteString
-displayNameP_ = quoted '\'' <|> takeNameTill (\c -> isSpace c || c == ',')
+displayNameP = safeDecodeUtf8 <$> (quoted '\'' <|> takeNameTill (\c -> isSpace c || c == ','))
where
takeNameTill p =
A.peekChar' >>= \c ->
diff --git a/src/Simplex/Chat/Library/Internal.hs b/src/Simplex/Chat/Library/Internal.hs
index bdc5e4b920..bbefbcfde0 100644
--- a/src/Simplex/Chat/Library/Internal.hs
+++ b/src/Simplex/Chat/Library/Internal.hs
@@ -37,7 +37,7 @@ import Data.Foldable (foldr')
import Data.Functor (($>))
import Data.Functor.Identity
import Data.Int (Int64)
-import Data.List (find, foldl', mapAccumL, partition)
+import Data.List (find, mapAccumL, partition)
import Data.List.NonEmpty (NonEmpty (..), (<|))
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
@@ -190,13 +190,13 @@ toggleNtf m ntfOn =
forM_ (memberConnId m) $ \connId ->
withAgent (\a -> toggleConnectionNtfs a connId ntfOn) `catchChatError` eToView
-prepareGroupMsg :: DB.Connection -> User -> GroupInfo -> Maybe MsgScope -> MsgContent -> Map MemberName MsgMention -> Maybe ChatItemId -> Maybe CIForwardedFrom -> Maybe FileInvitation -> Maybe CITimed -> Bool -> ExceptT StoreError IO (ChatMsgEvent 'Json, Maybe (CIQuote 'CTGroup))
-prepareGroupMsg db user g@GroupInfo {membership} msgScope mc mentions quotedItemId_ itemForwarded fInv_ timed_ live = case (quotedItemId_, itemForwarded) of
+prepareGroupMsg :: DB.Connection -> User -> GroupInfo -> MsgContent -> Map MemberName MsgMention -> Maybe ChatItemId -> Maybe CIForwardedFrom -> Maybe FileInvitation -> Maybe CITimed -> Bool -> ExceptT StoreError IO (ChatMsgEvent 'Json, Maybe (CIQuote 'CTGroup))
+prepareGroupMsg db user g@GroupInfo {membership} mc mentions quotedItemId_ itemForwarded fInv_ timed_ live = case (quotedItemId_, itemForwarded) of
(Nothing, Nothing) ->
- let mc' = MCSimple $ ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live) msgScope
+ let mc' = MCSimple $ ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live)
in pure (XMsgNew mc', Nothing)
(Nothing, Just _) ->
- let mc' = MCForward $ ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live) msgScope
+ let mc' = MCForward $ ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live)
in pure (XMsgNew mc', Nothing)
(Just quotedItemId, Nothing) -> do
CChatItem _ qci@ChatItem {meta = CIMeta {itemTs, itemSharedMsgId}, formattedText, mentions = quoteMentions, file} <-
@@ -206,7 +206,7 @@ prepareGroupMsg db user g@GroupInfo {membership} msgScope mc mentions quotedItem
qmc = quoteContent mc origQmc file
(qmc', ft', _) = updatedMentionNames qmc formattedText quoteMentions
quotedItem = CIQuote {chatDir = qd, itemId = Just quotedItemId, sharedMsgId = itemSharedMsgId, sentAt = itemTs, content = qmc', formattedText = ft'}
- mc' = MCQuote QuotedMsg {msgRef, content = qmc'} (ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live) msgScope)
+ mc' = MCQuote QuotedMsg {msgRef, content = qmc'} (ExtMsgContent mc mentions fInv_ (ttl' <$> timed_) (justTrue live))
pure (XMsgNew mc', Just quotedItem)
(Just _, Just _) -> throwError SEInvalidQuote
where
@@ -323,12 +323,12 @@ quoteContent mc qmc ciFile_
qFileName = maybe qText (T.pack . getFileName) ciFile_
qTextOrFile = if T.null qText then qFileName else qText
-prohibitedGroupContent :: GroupInfo -> GroupMember -> Maybe GroupChatScopeInfo -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature
-prohibitedGroupContent gInfo@GroupInfo {membership = GroupMember {memberRole = userRole}} m scopeInfo mc ft file_ sent
+prohibitedGroupContent :: GroupInfo -> GroupMember -> MsgContent -> Maybe MarkdownList -> Maybe f -> Bool -> Maybe GroupFeature
+prohibitedGroupContent gInfo@GroupInfo {membership = GroupMember {memberRole = userRole}} m mc ft file_ sent
| isVoice mc && not (groupFeatureMemberAllowed SGFVoice m gInfo) = Just GFVoice
- | isNothing scopeInfo && not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
- | isNothing scopeInfo && isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports
- | isNothing scopeInfo && prohibitedSimplexLinks gInfo m ft = Just GFSimplexLinks
+ | not (isVoice mc) && isJust file_ && not (groupFeatureMemberAllowed SGFFiles m gInfo) = Just GFFiles
+ | isReport mc && (badReportUser || not (groupFeatureAllowed SGFReports gInfo)) = Just GFReports
+ | prohibitedSimplexLinks gInfo m ft = Just GFSimplexLinks
| otherwise = Nothing
where
-- admins cannot send reports, non-admins cannot receive reports
@@ -453,53 +453,20 @@ deleteDirectCIs user ct items = do
deleteDirectChatItem db user ct ci
pure $ contactDeletion md ct ci Nothing
-deleteGroupCIs :: User -> GroupInfo -> Maybe GroupChatScopeInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion]
-deleteGroupCIs user gInfo chatScopeInfo items byGroupMember_ deletedTs = do
+deleteGroupCIs :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion]
+deleteGroupCIs user gInfo items byGroupMember_ deletedTs = do
let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items
deleteCIFiles user ciFilesInfo
(errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (deleteItem db) items)
unless (null errs) $ toView $ CEvtChatErrors errs
- vr <- chatVersionRange
- deletions' <- case chatScopeInfo of
- Nothing -> pure deletions
- Just scopeInfo@GCSIMemberSupport {groupMember_} -> do
- let decStats = countDeletedUnreadItems groupMember_ deletions
- gInfo' <- withFastStore' $ \db -> updateGroupScopeUnreadStats db vr user gInfo scopeInfo decStats
- pure $ map (updateDeletionGroupInfo gInfo') deletions
- pure deletions'
+ pure deletions
where
deleteItem :: DB.Connection -> CChatItem 'CTGroup -> IO ChatItemDeletion
deleteItem db (CChatItem md ci) = do
ci' <- case byGroupMember_ of
Just m -> Just <$> updateGroupChatItemModerated db user gInfo ci m deletedTs
Nothing -> Nothing <$ deleteGroupChatItem db user gInfo ci
- pure $ groupDeletion md gInfo chatScopeInfo ci ci'
- countDeletedUnreadItems :: Maybe GroupMember -> [ChatItemDeletion] -> (Int, Int, Int)
- countDeletedUnreadItems scopeMember_ = foldl' countItem (0, 0, 0)
- where
- countItem :: (Int, Int, Int) -> ChatItemDeletion -> (Int, Int, Int)
- countItem (!unread, !unanswered, !mentions) ChatItemDeletion {deletedChatItem}
- | aChatItemIsRcvNew deletedChatItem =
- let unread' = unread + 1
- unanswered' = case (scopeMember_, aChatItemRcvFromMember deletedChatItem) of
- (Just scopeMember, Just rcvFromMember)
- | groupMemberId' rcvFromMember == groupMemberId' scopeMember -> unanswered + 1
- _ -> unanswered
- mentions' = if isACIUserMention deletedChatItem then mentions + 1 else mentions
- in (unread', unanswered', mentions')
- | otherwise = (unread, unanswered, mentions)
- updateDeletionGroupInfo :: GroupInfo -> ChatItemDeletion -> ChatItemDeletion
- updateDeletionGroupInfo gInfo' ChatItemDeletion {deletedChatItem, toChatItem} =
- ChatItemDeletion
- { deletedChatItem = updateACIGroupInfo gInfo' deletedChatItem,
- toChatItem = updateACIGroupInfo gInfo' <$> toChatItem
- }
-
-updateACIGroupInfo :: GroupInfo -> AChatItem -> AChatItem
-updateACIGroupInfo gInfo' = \case
- AChatItem SCTGroup dir (GroupChat _gInfo chatScopeInfo) ci ->
- AChatItem SCTGroup dir (GroupChat gInfo' chatScopeInfo) ci
- aci -> aci
+ pure $ groupDeletion md gInfo ci ci'
deleteGroupMemberCIs :: MsgDirectionI d => User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> CM ()
deleteGroupMemberCIs user gInfo member byGroupMember msgDir = do
@@ -550,8 +517,8 @@ markDirectCIsDeleted user ct items deletedTs = do
ci' <- markDirectChatItemDeleted db user ct ci deletedTs
pure $ contactDeletion md ct ci (Just ci')
-markGroupCIsDeleted :: User -> GroupInfo -> Maybe GroupChatScopeInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion]
-markGroupCIsDeleted user gInfo chatScopeInfo items byGroupMember_ deletedTs = do
+markGroupCIsDeleted :: User -> GroupInfo -> [CChatItem 'CTGroup] -> Maybe GroupMember -> UTCTime -> CM [ChatItemDeletion]
+markGroupCIsDeleted user gInfo items byGroupMember_ deletedTs = do
let ciFilesInfo = mapMaybe (\(CChatItem _ ChatItem {file}) -> mkCIFileInfo <$> file) items
cancelFilesInProgress user ciFilesInfo
(errs, deletions) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (markDeleted db) items)
@@ -561,7 +528,7 @@ markGroupCIsDeleted user gInfo chatScopeInfo items byGroupMember_ deletedTs = do
where
markDeleted db (CChatItem md ci) = do
ci' <- markGroupChatItemDeleted db user gInfo ci byGroupMember_ deletedTs
- pure $ groupDeletion md gInfo chatScopeInfo ci (Just ci')
+ pure $ groupDeletion md gInfo ci (Just ci')
markGroupMemberCIsDeleted :: User -> GroupInfo -> GroupMember -> GroupMember -> CM ()
markGroupMemberCIsDeleted user gInfo member byGroupMember = do
@@ -581,10 +548,10 @@ markGroupMemberCIsDeleted_ db user gInfo member byGroupMember deletedTs = do
markMemberCIsDeleted db user gInfo member byGroupMember deletedTs
pure fs
-groupDeletion :: MsgDirectionI d => SMsgDirection d -> GroupInfo -> Maybe GroupChatScopeInfo -> ChatItem 'CTGroup d -> Maybe (ChatItem 'CTGroup d) -> ChatItemDeletion
-groupDeletion md g chatScopeInfo ci ci' = ChatItemDeletion (gItem ci) (gItem <$> ci')
+groupDeletion :: MsgDirectionI d => SMsgDirection d -> GroupInfo -> ChatItem 'CTGroup d -> Maybe (ChatItem 'CTGroup d) -> ChatItemDeletion
+groupDeletion md g ci ci' = ChatItemDeletion (gItem ci) (gItem <$> ci')
where
- gItem = AChatItem SCTGroup md (GroupChat g chatScopeInfo)
+ gItem = AChatItem SCTGroup md (GroupChat g)
contactDeletion :: MsgDirectionI d => SMsgDirection d -> Contact -> ChatItem 'CTDirect d -> Maybe (ChatItem 'CTDirect d) -> ChatItemDeletion
contactDeletion md ct ci ci' = ChatItemDeletion (ctItem ci) (ctItem <$> ci')
@@ -598,7 +565,7 @@ updateCallItemStatus user ct@Contact {contactId} Call {chatItemId} receivedStatu
timed_ <- callTimed ct aciContent
updateDirectChatItemView user ct chatItemId aciContent False False timed_ msgId_
forM_ (timed_ >>= timedDeleteAt') $
- startProximateTimedItemThread user (ChatRef CTDirect contactId Nothing, chatItemId)
+ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId)
updateDirectChatItemView :: User -> Contact -> ChatItemId -> ACIContent -> Bool -> Bool -> Maybe CITimed -> Maybe MessageId -> CM ()
updateDirectChatItemView user ct chatItemId (ACIContent msgDir ciContent) edited live timed_ msgId_ = do
@@ -701,10 +668,10 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI
_ -> do
chatRef <- withStore $ \db -> getChatRefByFileId db user fileId
case (chatRef, grpMemberId) of
- (ChatRef CTDirect contactId _, Nothing) -> do
+ (ChatRef CTDirect contactId, Nothing) -> do
ct <- withStore $ \db -> getContact db vr user contactId
acceptFile CFCreateConnFileInvDirect $ \msg -> void $ sendDirectContactMessage user ct msg
- (ChatRef CTGroup groupId _, Just memId) -> do
+ (ChatRef CTGroup groupId, Just memId) -> do
GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user groupId memId
case activeConn of
Just conn -> do
@@ -913,7 +880,7 @@ acceptGroupJoinRequestAsync
gLinkMemRole
incognitoProfile = do
gVar <- asks random
- let initialStatus = acceptanceToStatus (memberAdmission groupProfile) gAccepted
+ let initialStatus = acceptanceToStatus gAccepted
(groupMemberId, memberId) <- withStore $ \db -> do
liftIO $ deleteContactRequestRec db user ucr
createJoiningMember db gVar user gInfo ucr gLinkMemRole initialStatus
@@ -998,7 +965,7 @@ acceptBusinessJoinRequestAsync
let chatV = vr `peerConnChatVersion` cReqChatVRange
connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV
withStore' $ \db -> createJoiningMemberConnection db user connIds chatV ucr groupMemberId subMode
- let cd = CDGroupSnd gInfo Nothing
+ let cd = CDGroupSnd gInfo
createInternalChatItem user cd (CISndGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
createGroupFeatureItems user cd CISndGroupFeature gInfo
pure gInfo
@@ -1014,41 +981,13 @@ profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$>
NewIncognito p -> p
ExistingIncognito lp -> fromLocalProfile lp
-introduceToModerators :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToModerators vr user gInfo@GroupInfo {groupId} m@GroupMember {memberRole, memberId} = do
- forM_ (memberConn m) $ \mConn -> do
- let msg =
- if (maxVersion (memberChatVRange m) >= groupKnockingVersion)
- then XGrpLinkAcpt GAPendingReview memberRole memberId
- else XMsgNew $ MCSimple $ extMsgContent (MCText pendingReviewMessage) Nothing
- void $ sendDirectMemberMessage mConn msg groupId
- modMs <- withStore' $ \db -> getGroupModerators db vr user gInfo
- let rcpModMs = filter (\mem -> memberCurrent mem && maxVersion (memberChatVRange mem) >= groupKnockingVersion) modMs
- introduceMember vr user gInfo m rcpModMs (Just $ MSMember $ memberId' m)
-
-introduceToAll :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToAll vr user gInfo m = do
+introduceToGroup :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
+introduceToGroup _ _ _ GroupMember {activeConn = Nothing} = throwChatError $ CEInternalError "member connection not active"
+introduceToGroup vr user gInfo@GroupInfo {groupId, membership} m@GroupMember {activeConn = Just conn} = do
members <- withStore' $ \db -> getGroupMembers db vr user gInfo
- let recipients = filter memberCurrent members
- introduceMember vr user gInfo m recipients Nothing
-
-introduceToRemaining :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> CM ()
-introduceToRemaining vr user gInfo m = do
- (members, introducedGMIds) <-
- withStore' $ \db -> (,) <$> getGroupMembers db vr user gInfo <*> getIntroducedGroupMemberIds db m
- let recipients = filter (introduceMemP introducedGMIds) members
- introduceMember vr user gInfo m recipients Nothing
- where
- introduceMemP introducedGMIds mem =
- memberCurrent mem
- && groupMemberId' mem `notElem` introducedGMIds
- && groupMemberId' mem /= groupMemberId' m
-
-introduceMember :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> [GroupMember] -> Maybe MsgScope -> CM ()
-introduceMember _ _ _ GroupMember {activeConn = Nothing} _ _ = throwChatError $ CEInternalError "member connection not active"
-introduceMember vr user gInfo@GroupInfo {groupId} m@GroupMember {activeConn = Just conn} introduceToMembers msgScope = do
- void . sendGroupMessage' user gInfo introduceToMembers $ XGrpMemNew (memberInfo m) msgScope
- sendIntroductions introduceToMembers
+ void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m
+ sendIntroductions members
+ when (groupFeatureAllowed SGFHistory gInfo) sendHistory
where
sendIntroductions members = do
intros <- withStore' $ \db -> createIntroductions db (maxVersion vr) members m
@@ -1064,7 +1003,7 @@ introduceMember vr user gInfo@GroupInfo {groupId} m@GroupMember {activeConn = Ju
memberIntro reMember =
let mInfo = memberInfo reMember
mRestrictions = memberRestrictions reMember
- in XGrpMemIntro mInfo mRestrictions
+ in XGrpMemIntro mInfo mRestrictions
shuffleIntros :: [GroupMemberIntro] -> IO [GroupMemberIntro]
shuffleIntros intros = do
let (admins, others) = partition isAdmin intros
@@ -1077,19 +1016,15 @@ introduceMember vr user gInfo@GroupInfo {groupId} m@GroupMember {activeConn = Ju
processIntro intro@GroupMemberIntro {introId} = do
void $ sendDirectMemberMessage conn (memberIntro $ reMember intro) groupId
withStore' $ \db -> updateIntroStatus db introId GMIntroSent
-
-sendHistory :: User -> GroupInfo -> GroupMember -> CM ()
-sendHistory _ _ GroupMember {activeConn = Nothing} = throwChatError $ CEInternalError "member connection not active"
-sendHistory user gInfo@GroupInfo {groupId, membership} m@GroupMember {activeConn = Just conn} =
- when (m `supportsVersion` batchSendVersion) $ do
- (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100)
- (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items
- let errors = map ChatErrorStore errs <> errs'
- unless (null errors) $ toView $ CEvtChatErrors errors
- let events' = maybe (concat events) (\x -> concat events <> [x]) descrEvent_
- forM_ (L.nonEmpty events') $ \events'' ->
- sendGroupMemberMessages user conn events'' groupId
- where
+ sendHistory =
+ when (m `supportsVersion` batchSendVersion) $ do
+ (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo m 100)
+ (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items
+ let errors = map ChatErrorStore errs <> errs'
+ unless (null errors) $ toView $ CEvtChatErrors errors
+ let events' = maybe (concat events) (\x -> concat events <> [x]) descrEvent_
+ forM_ (L.nonEmpty events') $ \events'' ->
+ sendGroupMemberMessages user conn events'' groupId
descrEvent_ :: Maybe (ChatMsgEvent 'Json)
descrEvent_
| m `supportsVersion` groupHistoryIncludeWelcomeVersion = do
@@ -1135,7 +1070,7 @@ sendHistory user gInfo@GroupInfo {groupId, membership} m@GroupMember {activeConn
| fileDescrComplete =
let fInvDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False}
fInv = xftpFileInvitation fileName fileSize fInvDescr
- in Just (fInv, fileDescrText)
+ in Just (fInv, fileDescrText)
| otherwise = Nothing
processContentItem :: GroupMember -> ChatItem 'CTGroup d -> MsgContent -> Maybe (FileInvitation, RcvFileDescrText) -> CM [ChatMsgEvent 'Json]
processContentItem sender ChatItem {formattedText, meta, quotedItem, mentions} mc fInvDescr_ =
@@ -1147,8 +1082,7 @@ sendHistory user gInfo@GroupInfo {groupId, membership} m@GroupMember {activeConn
fInv_ = fst <$> fInvDescr_
(mc', _, mentions') = updatedMentionNames mc formattedText mentions
mentions'' = M.map (\CIMention {memberId} -> MsgMention {memberId}) mentions'
- -- TODO [knocking] send history to other scopes too?
- (chatMsgEvent, _) <- withStore $ \db -> prepareGroupMsg db user gInfo Nothing mc' mentions'' quotedItemId_ Nothing fInv_ itemTimed False
+ (chatMsgEvent, _) <- withStore $ \db -> prepareGroupMsg db user gInfo mc' mentions'' quotedItemId_ Nothing fInv_ itemTimed False
let senderVRange = memberChatVRange' sender
xMsgNewChatMsg = ChatMessage {chatVRange = senderVRange, msgId = itemSharedMsgId, chatMsgEvent}
fileDescrEvents <- case (snd <$> fInvDescr_, itemSharedMsgId) of
@@ -1213,7 +1147,7 @@ startTimedItemThread user itemRef deleteAt = do
atomically $ writeTVar threadTVar (Just tId)
deleteTimedItem :: User -> (ChatRef, ChatItemId) -> UTCTime -> CM ()
-deleteTimedItem user (ChatRef cType chatId scope, itemId) deleteAt = do
+deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do
ts <- liftIO getCurrentTime
liftIO $ threadDelay' $ diffToMicroseconds $ diffUTCTime deleteAt ts
lift waitChatStartedAndActivated
@@ -1226,8 +1160,7 @@ deleteTimedItem user (ChatRef cType chatId scope, itemId) deleteAt = do
CTGroup -> do
(gInfo, ci) <- withStore $ \db -> (,) <$> getGroupInfo db vr user chatId <*> getGroupChatItem db user chatId itemId
deletedTs <- liftIO getCurrentTime
- chatScopeInfo <- mapM (getChatScopeInfo vr user) scope
- deletions <- deleteGroupCIs user gInfo chatScopeInfo [ci] Nothing deletedTs
+ deletions <- deleteGroupCIs user gInfo [ci] Nothing deletedTs
toView $ CEvtChatItemsDeleted user deletions True True
_ -> eToView $ ChatError $ CEInternalError "bad deleteTimedItem cType"
@@ -1343,103 +1276,6 @@ parseChatMessage conn s = do
errType = CEInvalidChatMessage conn Nothing (safeDecodeUtf8 s)
{-# INLINE parseChatMessage #-}
-getChatScopeInfo :: VersionRangeChat -> User -> GroupChatScope -> CM GroupChatScopeInfo
-getChatScopeInfo vr user = \case
- GCSMemberSupport Nothing -> pure $ GCSIMemberSupport Nothing
- GCSMemberSupport (Just gmId) -> do
- supportMem <- withFastStore $ \db -> getGroupMemberById db vr user gmId
- pure $ GCSIMemberSupport (Just supportMem)
-
--- TODO [knocking] refactor to GroupChatScope -> "a" function, "a" is some new type? Or possibly split to get scope/get recipients steps
-getGroupRecipients :: VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScope -> VersionChat -> CM (Maybe GroupChatScopeInfo, [GroupMember])
-getGroupRecipients vr user gInfo@GroupInfo {membership} scope modsCompatVersion = case scope of
- Nothing -> do
- unless (memberCurrent membership && memberActive membership) $ throwChatError $ CECommandError "not current member"
- ms <- withFastStore' $ \db -> getGroupMembers db vr user gInfo
- let recipients = filter memberCurrent ms
- pure (Nothing, recipients)
- Just (GCSMemberSupport Nothing) -> do
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
- let rcpModMs' = filter (\m -> compatible m && memberCurrent m) modMs
- when (null rcpModMs') $ throwChatError $ CECommandError "no admins support this message"
- let scopeInfo = GCSIMemberSupport Nothing
- pure (Just scopeInfo, rcpModMs')
- Just (GCSMemberSupport (Just gmId)) -> do
- unless (memberCurrent membership && memberActive membership) $ throwChatError $ CECommandError "not current member"
- supportMem <- withFastStore $ \db -> getGroupMemberById db vr user gmId
- unless (memberCurrentOrPending supportMem) $ throwChatError $ CECommandError "support member not current or pending"
- let scopeInfo = GCSIMemberSupport (Just supportMem)
- if memberStatus supportMem == GSMemPendingApproval
- then pure (Just scopeInfo, [supportMem])
- else do
- modMs <- withFastStore' $ \db -> getGroupModerators db vr user gInfo
- let rcpModMs' = filter (\m -> compatible m && memberCurrent m) modMs
- pure (Just scopeInfo, [supportMem] <> rcpModMs')
- where
- compatible GroupMember {activeConn, memberChatVRange} =
- maxVersion (maybe memberChatVRange peerChatVRange activeConn) >= modsCompatVersion
-
-mkLocalGroupChatScope :: GroupInfo -> CM (GroupInfo, Maybe GroupChatScopeInfo)
-mkLocalGroupChatScope gInfo@GroupInfo {membership}
- | memberPending membership = do
- (gInfo', scopeInfo) <- mkGroupSupportChatInfo gInfo
- pure (gInfo', Just scopeInfo)
- | otherwise =
- pure (gInfo, Nothing)
-
-mkGroupChatScope :: GroupInfo -> GroupMember -> CM (GroupInfo, GroupMember, Maybe GroupChatScopeInfo)
-mkGroupChatScope gInfo@GroupInfo {membership} m
- | memberPending membership = do
- (gInfo', scopeInfo) <- mkGroupSupportChatInfo gInfo
- pure (gInfo', m, Just scopeInfo)
- | memberPending m = do
- (m', scopeInfo) <- mkMemberSupportChatInfo m
- pure (gInfo, m', Just scopeInfo)
- | otherwise =
- pure (gInfo, m, Nothing)
-
-mkGetMessageChatScope :: VersionRangeChat -> User -> GroupInfo -> GroupMember -> Maybe MsgScope -> CM (GroupInfo, GroupMember, Maybe GroupChatScopeInfo)
-mkGetMessageChatScope vr user gInfo@GroupInfo {membership} m msgScope_ =
- mkGroupChatScope gInfo m >>= \case
- groupScope@(_gInfo', _m', Just _scopeInfo) -> pure groupScope
- (_, _, Nothing) -> case msgScope_ of
- Nothing -> pure (gInfo, m, Nothing)
- Just (MSMember mId)
- | sameMemberId mId membership -> do
- (gInfo', scopeInfo) <- mkGroupSupportChatInfo gInfo
- pure (gInfo', m, Just scopeInfo)
- | otherwise -> do
- referredMember <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo mId
- -- TODO [knocking] return patched _referredMember' too?
- (_referredMember', scopeInfo) <- mkMemberSupportChatInfo referredMember
- pure (gInfo, m, Just scopeInfo)
-
-mkGroupSupportChatInfo :: GroupInfo -> CM (GroupInfo, GroupChatScopeInfo)
-mkGroupSupportChatInfo gInfo@GroupInfo {membership} =
- case supportChat membership of
- Nothing -> do
- chatTs <- liftIO getCurrentTime
- withStore' $ \db -> setSupportChatTs db (groupMemberId' membership) chatTs
- let gInfo' = gInfo {membership = membership {supportChat = Just $ GroupSupportChat chatTs 0 0 0 Nothing}}
- scopeInfo = GCSIMemberSupport {groupMember_ = Nothing}
- pure (gInfo', scopeInfo)
- Just _supportChat ->
- let scopeInfo = GCSIMemberSupport {groupMember_ = Nothing}
- in pure (gInfo, scopeInfo)
-
-mkMemberSupportChatInfo :: GroupMember -> CM (GroupMember, GroupChatScopeInfo)
-mkMemberSupportChatInfo m@GroupMember {groupMemberId, supportChat} =
- case supportChat of
- Nothing -> do
- chatTs <- liftIO getCurrentTime
- withStore' $ \db -> setSupportChatTs db groupMemberId chatTs
- let m' = m {supportChat = Just $ GroupSupportChat chatTs 0 0 0 Nothing}
- scopeInfo = GCSIMemberSupport {groupMember_ = Just m'}
- pure (m', scopeInfo)
- Just _supportChat ->
- let scopeInfo = GCSIMemberSupport {groupMember_ = Just m}
- in pure (m, scopeInfo)
-
sendFileChunk :: User -> SndFileTransfer -> CM ()
sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentConnId acId} =
unless (fileStatus == FSComplete || fileStatus == FSCancelled) $ do
@@ -1597,20 +1433,15 @@ deleteMemberConnection' GroupMember {activeConn} waitDelivery = do
deleteAgentConnectionAsync' (aConnId conn) waitDelivery
withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
-deleteOrUpdateMemberRecord :: User -> GroupInfo -> GroupMember -> CM GroupInfo
-deleteOrUpdateMemberRecord user gInfo member =
- withStore' $ \db -> deleteOrUpdateMemberRecordIO db user gInfo member
+deleteOrUpdateMemberRecord :: User -> GroupMember -> CM ()
+deleteOrUpdateMemberRecord user member =
+ withStore' $ \db -> deleteOrUpdateMemberRecordIO db user member
-deleteOrUpdateMemberRecordIO :: DB.Connection -> User -> GroupInfo -> GroupMember -> IO GroupInfo
-deleteOrUpdateMemberRecordIO db user@User {userId} gInfo member = do
- gInfo' <-
- if gmRequiresAttention member
- then decreaseGroupMembersRequireAttention db user gInfo
- else pure gInfo
+deleteOrUpdateMemberRecordIO :: DB.Connection -> User -> GroupMember -> IO ()
+deleteOrUpdateMemberRecordIO db user@User {userId} member =
checkGroupMemberHasItems db user member >>= \case
Just _ -> updateGroupMemberStatus db userId member GSMemRemoved
Nothing -> deleteGroupMember db user member
- pure gInfo'
sendDirectContactMessages :: MsgEncodingI e => User -> Contact -> NonEmpty (ChatMsgEvent e) -> CM [Either ChatError SndMessage]
sendDirectContactMessages user ct events = do
@@ -1797,9 +1628,9 @@ deliverMessagesB msgReqs = do
where
updatePQ = updateConnPQSndEnabled db connId pqSndEnabled'
-sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> Maybe GroupChatScope -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage
-sendGroupMessage user gInfo gcScope members chatMsgEvent = do
- sendGroupMessages user gInfo gcScope members (chatMsgEvent :| []) >>= \case
+sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM SndMessage
+sendGroupMessage user gInfo members chatMsgEvent = do
+ sendGroupMessages user gInfo members (chatMsgEvent :| []) >>= \case
((Right msg) :| [], _) -> pure msg
_ -> throwChatError $ CEInternalError "sendGroupMessage: expected 1 message"
@@ -1809,9 +1640,9 @@ sendGroupMessage' user gInfo members chatMsgEvent =
((Right msg) :| [], _) -> pure msg
_ -> throwChatError $ CEInternalError "sendGroupMessage': expected 1 message"
-sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> Maybe GroupChatScope -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult)
-sendGroupMessages user gInfo scope members events = do
- -- TODO [knocking] send current profile to pending member after approval?
+sendGroupMessages :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult)
+sendGroupMessages user gInfo members events = do
+ -- TODO [knocking] when sending to all, send profile update to pending approval members too, then filter for next step?
when shouldSendProfileUpdate $
sendProfileUpdate `catchChatError` eToView
sendGroupMessages_ user gInfo members events
@@ -1819,7 +1650,6 @@ sendGroupMessages user gInfo scope members events = do
User {profile = p, userMemberProfileUpdatedAt} = user
GroupInfo {userMemberProfileSentAt} = gInfo
shouldSendProfileUpdate
- | isJust scope = False -- why not sending profile updates to scopes?
| incognitoMembership gInfo = False
| otherwise =
case (userMemberProfileSentAt, userMemberProfileUpdatedAt) of
@@ -1840,13 +1670,16 @@ data GroupSndResult = GroupSndResult
}
sendGroupMessages_ :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> NonEmpty (ChatMsgEvent e) -> CM (NonEmpty (Either ChatError SndMessage), GroupSndResult)
-sendGroupMessages_ _user gInfo@GroupInfo {groupId} recipientMembers events = do
+sendGroupMessages_ _user gInfo@GroupInfo {groupId} members events = do
let idsEvts = L.map (GroupId groupId,) events
sndMsgs_ <- lift $ createSndMessages idsEvts
- recipientMembers' <- liftIO $ shuffleMembers recipientMembers
+ -- TODO [knocking] Possibly we need to pass GroupSndScope through all functions to here to avoid ad-hoc filtering.
+ recipientMembers <- case members of
+ [m] | memberStatus m == GSMemPendingApproval -> pure [m]
+ _ -> liftIO $ shuffleMembers (filter memberCurrent members)
let msgFlags = MsgFlags {notification = any (hasNotification . toCMEventTag) events}
(toSendSeparate, toSendBatched, toPending, forwarded, _, dups) =
- foldr' (addMember recipientMembers') ([], [], [], [], S.empty, 0 :: Int) recipientMembers'
+ foldr' addMember ([], [], [], [], S.empty, 0 :: Int) recipientMembers
when (dups /= 0) $ logError $ "sendGroupMessages_: " <> tshow dups <> " duplicate members"
-- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here
-- Deliver to toSend members
@@ -1868,7 +1701,7 @@ sendGroupMessages_ _user gInfo@GroupInfo {groupId} recipientMembers events = do
liftM2 (<>) (shuffle adminMs) (shuffle otherMs)
where
isAdmin GroupMember {memberRole} = memberRole >= GRAdmin
- addMember members m acc@(toSendSeparate, toSendBatched, pending, forwarded, !mIds, !dups) =
+ addMember m acc@(toSendSeparate, toSendBatched, pending, forwarded, !mIds, !dups) =
case memberSendAction gInfo events members m of
Just a
| mId `S.member` mIds -> (toSendSeparate, toSendBatched, pending, forwarded, mIds, dups + 1)
@@ -2045,7 +1878,7 @@ saveSndChatItem user cd msg content = saveSndChatItem' user cd msg content Nothi
saveSndChatItem' :: ChatTypeI c => User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIFile 'MDSnd) -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> CM (ChatItem c 'MDSnd)
saveSndChatItem' user cd msg content ciFile quotedItem itemForwarded itemTimed live = do
let itemTexts = ciContentTexts content
- saveSndChatItems user cd [Right NewSndChatItemData {msg, content, itemTexts, itemMentions = M.empty, ciFile, quotedItem, itemForwarded}] itemTimed live >>= \case
+ saveSndChatItems user cd Nothing [Right NewSndChatItemData {msg, content, itemTexts, itemMentions = M.empty, ciFile, quotedItem, itemForwarded}] itemTimed live >>= \case
[Right ci] -> pure ci
_ -> throwChatError $ CEInternalError "saveSndChatItem': expected 1 item"
@@ -2064,43 +1897,43 @@ saveSndChatItems ::
ChatTypeI c =>
User ->
ChatDirection c 'MDSnd ->
+ Maybe NotInHistory ->
[Either ChatError (NewSndChatItemData c)] ->
Maybe CITimed ->
Bool ->
CM [Either ChatError (ChatItem c 'MDSnd)]
-saveSndChatItems user cd itemsData itemTimed live = do
+saveSndChatItems user cd notInHistory_ itemsData itemTimed live = do
createdAt <- liftIO getCurrentTime
- vr <- chatVersionRange
when (contactChatDeleted cd || any (\NewSndChatItemData {content} -> ciRequiresAttention content) (rights itemsData)) $
- void $ withStore' (\db -> updateChatTsStats db vr user cd createdAt Nothing)
+ withStore' (\db -> updateChatTs db user cd createdAt)
lift $ withStoreBatch (\db -> map (bindRight $ createItem db createdAt) itemsData)
where
createItem :: DB.Connection -> UTCTime -> NewSndChatItemData c -> IO (Either ChatError (ChatItem c 'MDSnd))
createItem db createdAt NewSndChatItemData {msg = msg@SndMessage {sharedMsgId}, content, itemTexts, itemMentions, ciFile, quotedItem, itemForwarded} = do
- ciId <- createNewSndChatItem db user cd msg content quotedItem itemForwarded itemTimed live createdAt
+ ciId <- createNewSndChatItem db user cd notInHistory_ msg content quotedItem itemForwarded itemTimed live createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
let ci = mkChatItem_ cd ciId content itemTexts ciFile quotedItem (Just sharedMsgId) itemForwarded itemTimed live False createdAt Nothing createdAt
Right <$> case cd of
- CDGroupSnd g _scope | not (null itemMentions) -> createGroupCIMentions db g ci itemMentions
+ CDGroupSnd g | not (null itemMentions) -> createGroupCIMentions db g ci itemMentions
_ -> pure ci
-saveRcvChatItemNoParse :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv, ChatInfo c)
+saveRcvChatItemNoParse :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> CIContent 'MDRcv -> CM (ChatItem c 'MDRcv)
saveRcvChatItemNoParse user cd msg brokerTs = saveRcvChatItem user cd msg brokerTs . ciContentNoParse
-saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> CM (ChatItem c 'MDRcv, ChatInfo c)
+saveRcvChatItem :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> CM (ChatItem c 'MDRcv)
saveRcvChatItem user cd msg@RcvMessage {sharedMsgId_} brokerTs content =
- saveRcvChatItem' user cd msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
+ saveRcvChatItem' user cd Nothing msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
ciContentNoParse :: CIContent 'MDRcv -> (CIContent 'MDRcv, (Text, Maybe MarkdownList))
ciContentNoParse content = (content, (ciContentToText content, Nothing))
-saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> Map MemberName MsgMention -> CM (ChatItem c 'MDRcv, ChatInfo c)
-saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, forwardedByMember} sharedMsgId_ brokerTs (content, (t, ft_)) ciFile itemTimed live mentions = do
+saveRcvChatItem' :: (ChatTypeI c, ChatTypeQuotable c) => User -> ChatDirection c 'MDRcv -> Maybe NotInHistory -> RcvMessage -> Maybe SharedMsgId -> UTCTime -> (CIContent 'MDRcv, (Text, Maybe MarkdownList)) -> Maybe (CIFile 'MDRcv) -> Maybe CITimed -> Bool -> Map MemberName MsgMention -> CM (ChatItem c 'MDRcv)
+saveRcvChatItem' user cd notInHistory_ msg@RcvMessage {chatMsgEvent, forwardedByMember} sharedMsgId_ brokerTs (content, (t, ft_)) ciFile itemTimed live mentions = do
createdAt <- liftIO getCurrentTime
- vr <- chatVersionRange
withStore' $ \db -> do
+ when (ciRequiresAttention content || contactChatDeleted cd) $ updateChatTs db user cd createdAt
(mentions' :: Map MemberName CIMention, userMention) <- case cd of
- CDGroupRcv g@GroupInfo {membership} _scope _m -> do
+ CDGroupRcv g@GroupInfo {membership} _ -> do
mentions' <- getRcvCIMentions db user g ft_ mentions
let userReply = case cmToQuotedMsg chatMsgEvent of
Just QuotedMsg {msgRef = MsgRef {memberId = Just mId}} -> sameMemberId mId membership
@@ -2108,23 +1941,12 @@ saveRcvChatItem' user cd msg@RcvMessage {chatMsgEvent, forwardedByMember} shared
userMention' = userReply || any (\CIMention {memberId} -> sameMemberId memberId membership) mentions'
in pure (mentions', userMention')
CDDirectRcv _ -> pure (M.empty, False)
- cInfo' <- if (ciRequiresAttention content || contactChatDeleted cd)
- then updateChatTsStats db vr user cd createdAt (memberChatStats userMention)
- else pure $ toChatInfo cd
- (ciId, quotedItem, itemForwarded) <- createNewRcvChatItem db user cd msg sharedMsgId_ content itemTimed live userMention brokerTs createdAt
+ (ciId, quotedItem, itemForwarded) <- createNewRcvChatItem db user cd notInHistory_ msg sharedMsgId_ content itemTimed live userMention brokerTs createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
let ci = mkChatItem_ cd ciId content (t, ft_) ciFile quotedItem sharedMsgId_ itemForwarded itemTimed live userMention brokerTs forwardedByMember createdAt
- ci' <- case cd of
- CDGroupRcv g _scope _m | not (null mentions') -> createGroupCIMentions db g ci mentions'
+ case cd of
+ CDGroupRcv g _ | not (null mentions') -> createGroupCIMentions db g ci mentions'
_ -> pure ci
- pure (ci', cInfo')
- where
- memberChatStats :: Bool -> Maybe (Int, MemberAttention, Int)
- memberChatStats userMention = case cd of
- CDGroupRcv _g (Just scope) m -> do
- let unread = fromEnum $ ciCreateStatus content == CISRcvNew
- in Just (unread, memberAttentionChange unread (Just brokerTs) m scope, fromEnum userMention)
- _ -> Nothing
-- TODO [mentions] optimize by avoiding unnecessary parsing
mkChatItem :: (ChatTypeI c, MsgDirectionI d) => ChatDirection c d -> ChatItemId -> CIContent d -> Maybe (CIFile d) -> Maybe (CIQuote c) -> Maybe SharedMsgId -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> ChatItemTs -> Maybe GroupMemberId -> UTCTime -> ChatItem c d
@@ -2340,39 +2162,19 @@ createInternalItemsForChats ::
createInternalItemsForChats user itemTs_ dirsCIContents = do
createdAt <- liftIO getCurrentTime
let itemTs = fromMaybe createdAt itemTs_
- vr <- chatVersionRange'
- void . withStoreBatch' $ \db -> map (uncurry $ updateChat db vr createdAt) dirsCIContents
+ void . withStoreBatch' $ \db -> map (uncurry $ updateChat db createdAt) dirsCIContents
withStoreBatch' $ \db -> concatMap (uncurry $ createACIs db itemTs createdAt) dirsCIContents
where
- updateChat :: DB.Connection -> VersionRangeChat -> UTCTime -> ChatDirection c d -> [CIContent d] -> IO ()
- updateChat db vr createdAt cd contents
- | any ciRequiresAttention contents || contactChatDeleted cd = void $ updateChatTsStats db vr user cd createdAt memberChatStats
+ updateChat :: DB.Connection -> UTCTime -> ChatDirection c d -> [CIContent d] -> IO ()
+ updateChat db createdAt cd contents
+ | any ciRequiresAttention contents || contactChatDeleted cd = updateChatTs db user cd createdAt
| otherwise = pure ()
- where
- memberChatStats :: Maybe (Int, MemberAttention, Int)
- memberChatStats = case cd of
- CDGroupRcv _g (Just scope) m -> do
- let unread = length $ filter ciRequiresAttention contents
- in Just (unread, memberAttentionChange unread itemTs_ m scope, 0)
- _ -> Nothing
createACIs :: DB.Connection -> UTCTime -> UTCTime -> ChatDirection c d -> [CIContent d] -> [IO AChatItem]
createACIs db itemTs createdAt cd = map $ \content -> do
ciId <- createNewChatItemNoMsg db user cd content itemTs createdAt
let ci = mkChatItem cd ciId content Nothing Nothing Nothing Nothing Nothing False False itemTs Nothing createdAt
pure $ AChatItem (chatTypeI @c) (msgDirection @d) (toChatInfo cd) ci
-memberAttentionChange :: Int -> (Maybe UTCTime) -> GroupMember -> GroupChatScopeInfo -> MemberAttention
-memberAttentionChange unread brokerTs_ rcvMem = \case
- GCSIMemberSupport (Just suppMem)
- | groupMemberId' suppMem == groupMemberId' rcvMem -> MAInc unread brokerTs_
- | msgIsNewerThanLastUnanswered -> MAReset
- | otherwise -> MAInc 0 Nothing
- where
- msgIsNewerThanLastUnanswered = case (supportChat suppMem >>= lastMsgFromMemberTs, brokerTs_) of
- (Just lastMsgTs, Just brokerTs) -> lastMsgTs < brokerTs
- _ -> False
- GCSIMemberSupport Nothing -> MAInc 0 Nothing
-
createLocalChatItems ::
User ->
ChatDirection 'CTLocal 'MDSnd ->
@@ -2380,15 +2182,14 @@ createLocalChatItems ::
UTCTime ->
CM [ChatItem 'CTLocal 'MDSnd]
createLocalChatItems user cd itemsData createdAt = do
- vr <- chatVersionRange
- void $ withStore' $ \db -> updateChatTsStats db vr user cd createdAt Nothing
+ withStore' $ \db -> updateChatTs db user cd createdAt
(errs, items) <- lift $ partitionEithers <$> withStoreBatch' (\db -> map (createItem db) $ L.toList itemsData)
unless (null errs) $ toView $ CEvtChatErrors errs
pure items
where
createItem :: DB.Connection -> (CIContent 'MDSnd, Maybe (CIFile 'MDSnd), Maybe CIForwardedFrom, (Text, Maybe MarkdownList)) -> IO (ChatItem 'CTLocal 'MDSnd)
createItem db (content, ciFile, itemForwarded, ts) = do
- ciId <- createNewChatItem_ db user cd Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False False createdAt Nothing createdAt
+ ciId <- createNewChatItem_ db user cd Nothing Nothing Nothing content (Nothing, Nothing, Nothing, Nothing, Nothing) itemForwarded Nothing False False createdAt Nothing createdAt
forM_ ciFile $ \CIFile {fileId} -> updateFileTransferChatItemId db fileId ciId createdAt
pure $ mkChatItem_ cd ciId content ts ciFile Nothing Nothing itemForwarded Nothing False False createdAt Nothing createdAt
diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs
index 80e287f414..d60faef639 100644
--- a/src/Simplex/Chat/Library/Subscriber.hs
+++ b/src/Simplex/Chat/Library/Subscriber.hs
@@ -162,9 +162,9 @@ processAgentMsgSndFile _corrId aFileId msg = do
throwChatError $ CENoSndFileUser $ AgentSndFileId aFileId
where
withEntityLock_ :: Maybe ChatRef -> CM a -> CM a
- withEntityLock_ = \case
- Just (ChatRef CTDirect contactId _) -> withContactLock "processAgentMsgSndFile" contactId
- Just (ChatRef CTGroup groupId _scope) -> withGroupLock "processAgentMsgSndFile" groupId
+ withEntityLock_ cRef_ = case cRef_ of
+ Just (ChatRef CTDirect contactId) -> withContactLock "processAgentMsgSndFile" contactId
+ Just (ChatRef CTGroup groupId) -> withGroupLock "processAgentMsgSndFile" groupId
_ -> id
process :: User -> FileTransferId -> CM ()
process user fileId = do
@@ -212,7 +212,7 @@ processAgentMsgSndFile _corrId aFileId msg = do
Left e -> eToView e
Nothing -> eToView $ ChatError $ CEInternalError "SFDONE, sendFileDescriptions: expected at least 1 result"
lift $ withAgent' (`xftpDeleteSndFileInternal` aFileId)
- (_, _, SMDSnd, GroupChat g@GroupInfo {groupId} _scope) -> do
+ (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do
ms <- withStore' $ \db -> getGroupMembers db vr user g
let rfdsMemberFTs = zipWith (\rfd (conn, sft) -> (conn, sft, fileDescrText rfd)) rfds (memberFTs ms)
extraRFDs = drop (length rfdsMemberFTs) rfds
@@ -304,9 +304,9 @@ processAgentMsgRcvFile _corrId aFileId msg = do
throwChatError $ CENoRcvFileUser $ AgentRcvFileId aFileId
where
withEntityLock_ :: Maybe ChatRef -> CM a -> CM a
- withEntityLock_ = \case
- Just (ChatRef CTDirect contactId _) -> withContactLock "processAgentMsgRcvFile" contactId
- Just (ChatRef CTGroup groupId _scope) -> withGroupLock "processAgentMsgRcvFile" groupId
+ withEntityLock_ cRef_ = case cRef_ of
+ Just (ChatRef CTDirect contactId) -> withContactLock "processAgentMsgRcvFile" contactId
+ Just (ChatRef CTGroup groupId) -> withGroupLock "processAgentMsgRcvFile" groupId
_ -> id
process :: User -> FileTransferId -> CM ()
process user fileId = do
@@ -486,7 +486,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
case event of
XMsgNew mc -> newContentMessage ct'' mc msg msgMeta
XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct'' sharedMsgId fileDescr
- XMsgUpdate sharedMsgId mContent _ ttl live _msgScope -> messageUpdate ct'' sharedMsgId mContent msg msgMeta ttl live
+ XMsgUpdate sharedMsgId mContent _ ttl live -> messageUpdate ct'' sharedMsgId mContent msg msgMeta ttl live
XMsgDel sharedMsgId _ -> messageDelete ct'' sharedMsgId msg msgMeta
XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct'' sharedMsgId reaction add msg msgMeta
-- TODO discontinue XFile
@@ -716,7 +716,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
}
(_msg, _) <- sendDirectContactMessage user ct $ XGrpInv groupInv
-- we could link chat item with sent group invitation message (_msg)
- createInternalChatItem user (CDGroupRcv gInfo Nothing m) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
+ createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
-- TODO REMOVE LEGACY ^^^
_ -> throwChatError $ CECommandError "unexpected cmdFunction"
CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
@@ -762,75 +762,54 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XOk -> pure ()
_ -> messageError "INFO from member must have x.grp.mem.info, x.info or x.ok"
pure ()
- CON _pqEnc -> unless (memberStatus m == GSMemRejected || memberStatus membership == GSMemRejected) $ do
- -- TODO [knocking] send pending messages after accepting?
- -- possible improvement: check for each pending message, requires keeping track of connection state
- unless (connDisabled conn) $ sendPendingGroupMessages user m conn
+ CON _pqEnc -> unless (memberStatus m == GSMemRejected) $ do
+ status' <- case memberStatus m of
+ GSMemPendingApproval -> pure GSMemPendingApproval
+ _ -> do
+ withStore' $ \db -> do
+ updateGroupMemberStatus db userId m GSMemConnected
+ unless (memberActive membership) $
+ updateGroupMemberStatus db userId membership GSMemConnected
+ -- possible improvement: check for each pending message, requires keeping track of connection state
+ unless (connDisabled conn) $ sendPendingGroupMessages user m conn
+ pure GSMemConnected
withAgent $ \a -> toggleConnectionNtfs a (aConnId conn) $ chatHasNtfs chatSettings
case memberCategory m of
GCHostMember -> do
- (m', gInfo') <- withStore' $ \db -> do
- updateGroupMemberStatus db userId m GSMemConnected
- gInfo' <-
- if not (memberPending membership)
- then do
- updateGroupMemberStatus db userId membership GSMemConnected
- pure gInfo {membership = membership {memberStatus = GSMemConnected}}
- else pure gInfo
- pure (m {memberStatus = GSMemConnected}, gInfo')
- toView $ CEvtUserJoinedGroup user gInfo' m'
- (gInfo'', m'', scopeInfo) <- mkGroupChatScope gInfo' m'
- let cd = CDGroupRcv gInfo'' scopeInfo m''
+ toView $ CEvtUserJoinedGroup user gInfo {membership = membership {memberStatus = status'}} m {memberStatus = status'}
+ let cd = CDGroupRcv gInfo m
createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
- createGroupFeatureItems user cd CIRcvGroupFeature gInfo''
- memberConnectedChatItem gInfo'' scopeInfo m''
- unless (memberPending membership) $ maybeCreateGroupDescrLocal gInfo'' m''
- GCInviteeMember -> do
- (gInfo', mStatus) <-
- if not (memberPending m)
- then do
- mStatus <- withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected $> GSMemConnected
- pure (gInfo, mStatus)
- else do
- gInfo' <- withStore' $ \db -> increaseGroupMembersRequireAttention db user gInfo
- pure (gInfo', memberStatus m)
- (gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo' m
- memberConnectedChatItem gInfo'' scopeInfo m'
- case scopeInfo of
- Just (GCSIMemberSupport _) -> do
- createInternalChatItem user (CDGroupRcv gInfo'' scopeInfo m') (CIRcvGroupEvent RGENewMemberPendingReview) Nothing
- _ -> pure ()
- toView $ CEvtJoinedGroupMember user gInfo'' m' {memberStatus = mStatus}
- let Connection {viaUserContactLink} = conn
- when (isJust viaUserContactLink && isNothing (memberContactId m')) $ sendXGrpLinkMem gInfo''
- when (connChatVersion < batchSend2Version) sendGroupAutoReply
- case mStatus of
- GSMemPendingApproval -> pure ()
- GSMemPendingReview -> introduceToModerators vr user gInfo'' m'
- _ -> do
- introduceToAll vr user gInfo'' m'
- when (groupFeatureAllowed SGFHistory gInfo'') $ sendHistory user gInfo'' m'
+ createGroupFeatureItems user cd CIRcvGroupFeature gInfo
+ let GroupInfo {groupProfile = GroupProfile {description}} = gInfo
+ memberConnectedChatItem gInfo m
+ unless expectHistory $ forM_ description $ groupDescriptionChatItem gInfo m
where
- sendXGrpLinkMem gInfo'' = do
- let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo''
+ expectHistory = groupFeatureAllowed SGFHistory gInfo && m `supportsVersion` groupHistoryIncludeWelcomeVersion
+ GCInviteeMember -> do
+ memberConnectedChatItem gInfo m
+ toView $ CEvtJoinedGroupMember user gInfo m {memberStatus = status'}
+ let Connection {viaUserContactLink} = conn
+ when (isJust viaUserContactLink && isNothing (memberContactId m)) sendXGrpLinkMem
+ when (connChatVersion < batchSend2Version) sendGroupAutoReply
+ unless (status' == GSMemPendingApproval) $ introduceToGroup vr user gInfo m
+ where
+ sendXGrpLinkMem = do
+ let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
profileToSend = profileToSendOnAccept user profileMode True
void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend) groupId
_ -> do
- unless (memberPending m) $ withStore' $ \db -> updateGroupMemberStatus db userId m GSMemConnected
let memCategory = memberCategory m
withStore' (\db -> getViaGroupContact db vr user m) >>= \case
Nothing -> do
notifyMemberConnected gInfo m Nothing
let connectedIncognito = memberIncognito membership
- when (memCategory == GCPreMember) $
- probeMatchingMemberContact m connectedIncognito
+ when (memCategory == GCPreMember) $ probeMatchingMemberContact m connectedIncognito
Just ct@Contact {activeConn} ->
forM_ activeConn $ \Connection {connStatus} ->
when (connStatus == ConnReady) $ do
notifyMemberConnected gInfo m $ Just ct
let connectedIncognito = contactConnIncognito ct || incognitoMembership gInfo
- when (memCategory == GCPreMember && not (memberPending membership)) $
- probeMatchingContactsAndMembers ct connectedIncognito True
+ when (memCategory == GCPreMember) $ probeMatchingContactsAndMembers ct connectedIncognito True
sendXGrpMemCon memCategory
where
GroupMember {memberId} = m
@@ -849,12 +828,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
MSG msgMeta _msgFlags msgBody -> do
tags <- newTVarIO []
withAckMessage "group msg" agentConnId msgMeta True (Just tags) $ \eInfo -> do
- -- possible improvement is to choose scope based on event (some events specify scope)
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
- checkIntegrityCreateItem (CDGroupRcv gInfo' scopeInfo m') msgMeta `catchChatError` \_ -> pure ()
+ checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure ()
forM_ aChatMsgs $ \case
Right (ACMsg _ chatMsg) ->
- processEvent gInfo' m' tags eInfo chatMsg `catchChatError` \e -> eToView e
+ processEvent tags eInfo chatMsg `catchChatError` \e -> eToView e
Left e -> do
atomically $ modifyTVar' tags ("error" :)
logInfo $ "group msg=error " <> eInfo <> " " <> tshow e
@@ -864,46 +841,43 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
where
aChatMsgs = parseChatMessages msgBody
brokerTs = metaBrokerTs msgMeta
- processEvent :: GroupInfo -> GroupMember -> TVar [Text] -> Text -> MsgEncodingI e => ChatMessage e -> CM ()
- processEvent gInfo' m' tags eInfo chatMsg@ChatMessage {chatMsgEvent} = do
+ processEvent :: TVar [Text] -> Text -> MsgEncodingI e => ChatMessage e -> CM ()
+ processEvent tags eInfo chatMsg@ChatMessage {chatMsgEvent} = do
let tag = toCMEventTag chatMsgEvent
atomically $ modifyTVar' tags (tshow tag :)
logInfo $ "group msg=" <> tshow tag <> " " <> eInfo
- (m'', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m' conn msgMeta msgBody chatMsg
+ (m', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m conn msgMeta msgBody chatMsg
case event of
- XMsgNew mc -> memberCanSend m'' scope $ newGroupContentMessage gInfo' m'' mc msg brokerTs False
- where ExtMsgContent {scope} = mcExtMsgContent mc
- -- file description is always allowed, to allow sending files to support scope
- XMsgFileDescr sharedMsgId fileDescr -> groupMessageFileDescription gInfo' m'' sharedMsgId fileDescr
- XMsgUpdate sharedMsgId mContent mentions ttl live msgScope -> memberCanSend m'' msgScope $ groupMessageUpdate gInfo' m'' sharedMsgId mContent mentions msgScope msg brokerTs ttl live
- XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo' m'' sharedMsgId memberId msg brokerTs
- XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo' m'' sharedMsgId memberId reaction add msg brokerTs
+ XMsgNew mc -> memberCanSend m' $ newGroupContentMessage gInfo m' mc msg brokerTs False
+ XMsgFileDescr sharedMsgId fileDescr -> memberCanSend m' $ groupMessageFileDescription gInfo m' sharedMsgId fileDescr
+ XMsgUpdate sharedMsgId mContent mentions ttl live -> memberCanSend m' $ groupMessageUpdate gInfo m' sharedMsgId mContent mentions msg brokerTs ttl live
+ XMsgDel sharedMsgId memberId -> groupMessageDelete gInfo m' sharedMsgId memberId msg brokerTs
+ XMsgReact sharedMsgId (Just memberId) reaction add -> groupMsgReaction gInfo m' sharedMsgId memberId reaction add msg brokerTs
-- TODO discontinue XFile
- XFile fInv -> processGroupFileInvitation' gInfo' m'' fInv msg brokerTs
- XFileCancel sharedMsgId -> xFileCancelGroup gInfo' m'' sharedMsgId
- XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo' m'' sharedMsgId fileConnReq_ fName
- XInfo p -> xInfoMember gInfo' m'' p brokerTs
- XGrpLinkMem p -> xGrpLinkMem gInfo' m'' conn' p
- XGrpLinkAcpt acceptance role memberId -> xGrpLinkAcpt gInfo' m'' acceptance role memberId msg brokerTs
- XGrpMemNew memInfo msgScope -> xGrpMemNew gInfo' m'' memInfo msgScope msg brokerTs
- XGrpMemIntro memInfo memRestrictions_ -> xGrpMemIntro gInfo' m'' memInfo memRestrictions_
- XGrpMemInv memId introInv -> xGrpMemInv gInfo' m'' memId introInv
- XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo' m'' memInfo introInv
- XGrpMemRole memId memRole -> xGrpMemRole gInfo' m'' memId memRole msg brokerTs
- XGrpMemRestrict memId memRestrictions -> xGrpMemRestrict gInfo' m'' memId memRestrictions msg brokerTs
- XGrpMemCon memId -> xGrpMemCon gInfo' m'' memId
- XGrpMemDel memId withMessages -> xGrpMemDel gInfo' m'' memId withMessages msg brokerTs
- XGrpLeave -> xGrpLeave gInfo' m'' msg brokerTs
- XGrpDel -> xGrpDel gInfo' m'' msg brokerTs
- XGrpInfo p' -> xGrpInfo gInfo' m'' p' msg brokerTs
- XGrpPrefs ps' -> xGrpPrefs gInfo' m'' ps'
- -- TODO [knocking] why don't we forward these messages?
- XGrpDirectInv connReq mContent_ msgScope -> memberCanSend m'' msgScope $ xGrpDirectInv gInfo' m'' conn' connReq mContent_ msg brokerTs
- XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo' m'' memberId msg' msgTs
- XInfoProbe probe -> xInfoProbe (COMGroupMember m'') probe
- XInfoProbeCheck probeHash -> xInfoProbeCheck (COMGroupMember m'') probeHash
- XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m'') probe
- BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo' sharedMsgId chunk msgMeta
+ XFile fInv -> processGroupFileInvitation' gInfo m' fInv msg brokerTs
+ XFileCancel sharedMsgId -> xFileCancelGroup gInfo m' sharedMsgId
+ XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInvGroup gInfo m' sharedMsgId fileConnReq_ fName
+ XInfo p -> xInfoMember gInfo m' p brokerTs
+ XGrpLinkMem p -> xGrpLinkMem gInfo m' conn' p
+ XGrpLinkAcpt role -> xGrpLinkAcpt gInfo m' role
+ XGrpMemNew memInfo -> xGrpMemNew gInfo m' memInfo msg brokerTs
+ XGrpMemIntro memInfo memRestrictions_ -> xGrpMemIntro gInfo m' memInfo memRestrictions_
+ XGrpMemInv memId introInv -> xGrpMemInv gInfo m' memId introInv
+ XGrpMemFwd memInfo introInv -> xGrpMemFwd gInfo m' memInfo introInv
+ XGrpMemRole memId memRole -> xGrpMemRole gInfo m' memId memRole msg brokerTs
+ XGrpMemRestrict memId memRestrictions -> xGrpMemRestrict gInfo m' memId memRestrictions msg brokerTs
+ XGrpMemCon memId -> xGrpMemCon gInfo m' memId
+ XGrpMemDel memId withMessages -> xGrpMemDel gInfo m' memId withMessages msg brokerTs
+ XGrpLeave -> xGrpLeave gInfo m' msg brokerTs
+ XGrpDel -> xGrpDel gInfo m' msg brokerTs
+ XGrpInfo p' -> xGrpInfo gInfo m' p' msg brokerTs
+ XGrpPrefs ps' -> xGrpPrefs gInfo m' ps'
+ XGrpDirectInv connReq mContent_ -> memberCanSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg brokerTs
+ XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo m' memberId msg' msgTs
+ XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe
+ XInfoProbeCheck probeHash -> xInfoProbeCheck (COMGroupMember m') probeHash
+ XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m') probe
+ BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta
_ -> messageError $ "unsupported message: " <> tshow event
checkSendRcpt :: [AChatMessage] -> CM Bool
checkSendRcpt aMsgs = do
@@ -918,7 +892,6 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
hasDeliveryReceipt (toCMEventTag chatMsgEvent)
forwardMsgs :: [AChatMessage] -> CM ()
forwardMsgs aMsgs = do
- -- TODO [knocking] forward to/from GSMemPendingReview members
let GroupMember {memberRole = membershipMemRole} = membership
when (membershipMemRole >= GRAdmin && not (blockedByAdmin m)) $ do
let forwardedMsgs = mapMaybe (\(ACMsg _ chatMsg) -> forwardedGroupMsg chatMsg) aMsgs
@@ -934,7 +907,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let GroupMember {memberId} = m
ms = forwardedToGroupMembers (introducedMembers <> invitedMembers) forwardedMsgs'
events = L.map (\cm -> XGrpMsgForward memberId cm brokerTs) forwardedMsgs'
- unless (null ms) $ void $ sendGroupMessages_ user gInfo ms events
+ unless (null ms) $ void $ sendGroupMessages user gInfo ms events
RCVD msgMeta msgRcpt ->
withAckMessage' "group rcvd" agentConnId msgMeta $
groupMsgReceived gInfo m conn msgMeta msgRcpt
@@ -946,38 +919,36 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
when continued $ sendPendingGroupMessages user m conn
SWITCH qd phase cStats -> do
toView $ CEvtGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats)
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
when (phase == SPStarted || phase == SPCompleted) $ case qd of
- QDRcv -> createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndConnEvent . SCESwitchQueue phase . Just $ groupMemberRef m') Nothing
- QDSnd -> createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvConnEvent $ RCESwitchQueue phase) Nothing
- RSYNC rss cryptoErr_ cStats -> do
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
+ QDRcv -> createInternalChatItem user (CDGroupSnd gInfo) (CISndConnEvent . SCESwitchQueue phase . Just $ groupMemberRef m) Nothing
+ QDSnd -> createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvConnEvent $ RCESwitchQueue phase) Nothing
+ RSYNC rss cryptoErr_ cStats ->
case (rss, connectionCode, cryptoErr_) of
- (RSRequired, _, Just cryptoErr) -> processErr gInfo' scopeInfo m' cryptoErr
- (RSAllowed, _, Just cryptoErr) -> processErr gInfo' scopeInfo m' cryptoErr
+ (RSRequired, _, Just cryptoErr) -> processErr cryptoErr
+ (RSAllowed, _, Just cryptoErr) -> processErr cryptoErr
(RSAgreed, Just _, _) -> do
withStore' $ \db -> setConnectionVerified db user connId Nothing
- let m'' = m' {activeConn = Just (conn {connectionCode = Nothing} :: Connection)} :: GroupMember
- ratchetSyncEventItem gInfo' scopeInfo m''
- toViewTE $ TEGroupMemberVerificationReset user gInfo' m''
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m'') (CIRcvConnEvent RCEVerificationCodeReset) Nothing
- _ -> ratchetSyncEventItem gInfo' scopeInfo m'
+ let m' = m {activeConn = Just (conn {connectionCode = Nothing} :: Connection)} :: GroupMember
+ ratchetSyncEventItem m'
+ toViewTE $ TEGroupMemberVerificationReset user gInfo m'
+ createInternalChatItem user (CDGroupRcv gInfo m') (CIRcvConnEvent RCEVerificationCodeReset) Nothing
+ _ -> ratchetSyncEventItem m
where
- processErr gInfo' scopeInfo m' cryptoErr = do
+ processErr cryptoErr = do
let e@(mde, n) = agentMsgDecryptError cryptoErr
ci_ <- withStore $ \db ->
- getGroupMemberChatItemLast db user groupId (groupMemberId' m')
+ getGroupMemberChatItemLast db user groupId (groupMemberId' m)
>>= liftIO
. mapM (\(ci, content') -> updateGroupChatItem db user groupId ci content' False False Nothing)
. mdeUpdatedCI e
case ci_ of
- Just ci -> toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo' scopeInfo) ci)
+ Just ci -> toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci)
_ -> do
- toView $ CEvtGroupMemberRatchetSync user gInfo' m' (RatchetSyncProgress rss cStats)
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvDecryptionError mde n) Nothing
- ratchetSyncEventItem gInfo' scopeInfo m' = do
- toView $ CEvtGroupMemberRatchetSync user gInfo' m' (RatchetSyncProgress rss cStats)
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m') (CIRcvConnEvent $ RCERatchetSync rss) Nothing
+ toView $ CEvtGroupMemberRatchetSync user gInfo m (RatchetSyncProgress rss cStats)
+ createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvDecryptionError mde n) Nothing
+ ratchetSyncEventItem m' = do
+ toView $ CEvtGroupMemberRatchetSync user gInfo m' (RatchetSyncProgress rss cStats)
+ createInternalChatItem user (CDGroupRcv gInfo m') (CIRcvConnEvent $ RCERatchetSync rss) Nothing
OK ->
-- [async agent commands] continuation on receiving OK
when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure ()
@@ -1027,9 +998,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
_ -> pure Nothing
send mc = do
msg <- sendGroupMessage' user gInfo [m] (XMsgNew $ MCSimple (extMsgContent mc Nothing))
- ci <- saveSndChatItem user (CDGroupSnd gInfo Nothing) msg (CISndMsgContent mc)
+ ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndMsgContent mc)
withStore' $ \db -> createGroupSndStatus db (chatItemId' ci) (groupMemberId' m) GSSNew
- toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo Nothing) ci]
+ toView $ CEvtNewChatItems user [AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci]
agentMsgDecryptError :: AgentCryptoError -> (MsgDecryptError, Word32)
agentMsgDecryptError = \case
@@ -1085,7 +1056,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
SMP _ SMP.AUTH -> unless (fileStatus == FSCancelled) $ do
ci <- withStore $ \db -> do
liftIO (lookupChatRefByFileId db user fileId) >>= \case
- Just (ChatRef CTDirect _ _) -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled
+ Just (ChatRef CTDirect _) -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled
_ -> pure ()
lookupChatItemByFileId db vr user fileId
toView $ CEvtSndFileRcvCancelled user ci ft
@@ -1244,9 +1215,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise -> do
let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo
mem <- acceptGroupJoinRequestAsync user gInfo cReq acceptance useRole profileMode
- (gInfo', mem', scopeInfo) <- mkGroupChatScope gInfo mem
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo mem') (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
- toView $ CEvtAcceptingGroupJoinRequestMember user gInfo' mem'
+ createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing
+ toView $ CEvtAcceptingGroupJoinRequestMember user gInfo mem
Left rjctReason
| v < groupJoinRejectVersion ->
messageWarning $ "processUserContactRequest (group " <> groupName' gInfo <> "): joining of " <> displayName <> " is blocked"
@@ -1255,12 +1225,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
_ -> toView $ CEvtReceivedContactRequest user cReq
- memberCanSend :: GroupMember -> Maybe MsgScope -> CM () -> CM ()
- memberCanSend m@GroupMember {memberRole} msgScope a = case msgScope of
- Just MSMember {} -> a
- Nothing
- | memberRole > GRObserver || memberPending m -> a
- | otherwise -> messageError "member is not allowed to send messages"
+ -- TODO [knocking] review
+ memberCanSend :: GroupMember -> CM () -> CM ()
+ memberCanSend GroupMember {memberRole, memberStatus} a
+ | memberRole > GRObserver || memberStatus == GSMemPendingApproval = a
+ | otherwise = messageError "member is not allowed to send messages"
processConnMERR :: ConnectionEntity -> Connection -> AgentErrorType -> CM ()
processConnMERR connEntity conn err = do
@@ -1383,17 +1352,20 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
cancelRcvFileTransfer user ft >>= mapM_ deleteAgentConnectionAsync
throwChatError $ CEFileRcvChunk err
- memberConnectedChatItem :: GroupInfo -> Maybe GroupChatScopeInfo -> GroupMember -> CM ()
- memberConnectedChatItem gInfo scopeInfo m =
+ memberConnectedChatItem :: GroupInfo -> GroupMember -> CM ()
+ memberConnectedChatItem gInfo m =
-- ts should be broker ts but we don't have it for CON
- createInternalChatItem user (CDGroupRcv gInfo scopeInfo m) (CIRcvGroupEvent RGEMemberConnected) Nothing
+ createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupEvent RGEMemberConnected) Nothing
+
+ groupDescriptionChatItem :: GroupInfo -> GroupMember -> Text -> CM ()
+ groupDescriptionChatItem gInfo m descr =
+ createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvMsgContent $ MCText descr) Nothing
notifyMemberConnected :: GroupInfo -> GroupMember -> Maybe Contact -> CM ()
notifyMemberConnected gInfo m ct_ = do
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
- memberConnectedChatItem gInfo' scopeInfo m'
+ memberConnectedChatItem gInfo m
lift $ mapM_ (`setContactNetworkStatus` NSConnected) ct_
- toView $ CEvtConnectedToGroupMember user gInfo' m' ct_
+ toView $ CEvtConnectedToGroupMember user gInfo m ct_
probeMatchingContactsAndMembers :: Contact -> IncognitoEnabled -> Bool -> CM ()
probeMatchingContactsAndMembers ct connectedIncognito doProbeContacts = do
@@ -1457,7 +1429,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
newContentMessage :: Contact -> MsgContainer -> RcvMessage -> MsgMeta -> CM ()
newContentMessage ct mc msg@RcvMessage {sharedMsgId_} msgMeta = do
- let ExtMsgContent content _ fInv_ _ _ _ = mcExtMsgContent mc
+ let ExtMsgContent content _ fInv_ _ _ = mcExtMsgContent mc
-- Uncomment to test stuck delivery on errors - see test testDirectMessageDelete
-- case content of
-- MCText "hello 111" ->
@@ -1468,7 +1440,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
then do
void $ newChatItem (ciContentNoParse $ CIRcvChatFeatureRejected CFVoice) Nothing Nothing False
else do
- let ExtMsgContent _ _ _ itemTTL live_ _ = mcExtMsgContent mc
+ let ExtMsgContent _ _ _ itemTTL live_ = mcExtMsgContent mc
timed_ = rcvContactCITimed ct itemTTL
live = fromMaybe False live_
file_ <- processFileInvitation fInv_ content $ \db -> createRcvFileTransfer db userId ct
@@ -1477,9 +1449,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
where
brokerTs = metaBrokerTs msgMeta
newChatItem content ciFile_ timed_ live = do
- (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile_ timed_ live M.empty
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content ciFile_ timed_ live M.empty
reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getDirectCIReactions db ct sharedMsgId) sharedMsgId_
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci {reactions}]
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci {reactions}]
autoAcceptFile :: Maybe (RcvFileTransfer, CIFile 'MDRcv) -> CM ()
autoAcceptFile = mapM_ $ \(ft, CIFile {fileSize}) -> do
@@ -1495,8 +1467,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
groupMessageFileDescription :: GroupInfo -> GroupMember -> SharedMsgId -> FileDescr -> CM ()
groupMessageFileDescription g@GroupInfo {groupId} m sharedMsgId fileDescr = do
fileId <- withStore $ \db -> getGroupFileIdBySharedMsgId db userId groupId sharedMsgId
- -- here scope we pass only affects how chat item is searched in getAChatItemBySharedMsgId, and it ignores scope
- processFDMessage (CDGroupRcv g Nothing m) sharedMsgId fileId fileDescr
+ processFDMessage (CDGroupRcv g m) sharedMsgId fileId fileDescr
processFDMessage :: ChatTypeQuotable c => ChatDirection c 'MDRcv -> SharedMsgId -> FileTransferId -> FileDescr -> CM ()
processFDMessage cd sharedMsgId fileId fileDescr = do
@@ -1545,11 +1516,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvContactCITimed ct ttl
ts = ciContentTexts content
- (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live M.empty
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
updateDirectChatItem' db user contactId ci content True live Nothing Nothing
- toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv cInfo ci')
+ toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci')
where
brokerTs = metaBrokerTs msgMeta
content = CIRcvMsgContent mc
@@ -1569,7 +1540,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let edited = itemLive /= Just True
updateDirectChatItem' db user contactId ci {reactions} content edited live Nothing $ Just msgId
toView $ CEvtChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci')
- startUpdatedTimedItemThread user (ChatRef CTDirect contactId Nothing) ci ci'
+ startUpdatedTimedItemThread user (ChatRef CTDirect contactId) ci ci'
else toView $ CEvtChatItemNotChanged user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci)
_ -> messageError "x.msg.update: contact attempted invalid message update"
@@ -1627,13 +1598,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
updateChatItemReaction = do
cEvt_ <- withStore $ \db -> do
CChatItem md ci <- getGroupMemberCIBySharedMsgId db user g itemMemberId sharedMsgId
- scopeInfo <- getGroupChatScopeInfoForItem db vr user g (chatItemId' ci)
if ciReactionAllowed ci
then liftIO $ do
setGroupReaction db g m itemMemberId sharedMsgId False reaction add msgId brokerTs
reactions <- getGroupCIReactions db g itemMemberId sharedMsgId
let ci' = CChatItem md ci {reactions}
- r = ACIReaction SCTGroup SMDRcv (GroupChat g scopeInfo) $ CIReaction (CIGroupRcv m) ci' brokerTs reaction
+ r = ACIReaction SCTGroup SMDRcv (GroupChat g) $ CIReaction (CIGroupRcv m) ci' brokerTs reaction
pure $ Just $ CEvtChatItemReaction user add r
else pure Nothing
mapM_ toView cEvt_
@@ -1648,66 +1618,63 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
e -> throwError e
newGroupContentMessage :: GroupInfo -> GroupMember -> MsgContainer -> RcvMessage -> UTCTime -> Bool -> CM ()
- newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded = do
- (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
- if blockedByAdmin m'
- then createBlockedByAdmin gInfo' m' scopeInfo
- else
- case prohibitedGroupContent gInfo' m' scopeInfo content ft_ fInv_ False of
- Just f -> rejected gInfo' m' scopeInfo f
- Nothing ->
- withStore' (\db -> getCIModeration db vr user gInfo' memberId sharedMsgId_) >>= \case
- Just ciModeration -> do
- applyModeration gInfo' m' scopeInfo ciModeration
- withStore' $ \db -> deleteCIModeration db gInfo' memberId sharedMsgId_
- Nothing -> createContentItem gInfo' m' scopeInfo
+ newGroupContentMessage gInfo m@GroupMember {memberId, memberRole} mc msg@RcvMessage {sharedMsgId_} brokerTs forwarded
+ | blockedByAdmin m = createBlockedByAdmin
+ | otherwise = case prohibitedGroupContent gInfo m content ft_ fInv_ False of
+ Just f -> rejected f
+ Nothing ->
+ withStore' (\db -> getCIModeration db vr user gInfo memberId sharedMsgId_) >>= \case
+ Just ciModeration -> do
+ applyModeration ciModeration
+ withStore' $ \db -> deleteCIModeration db gInfo memberId sharedMsgId_
+ Nothing -> createContentItem
where
- rejected gInfo' m' scopeInfo f = newChatItem gInfo' m' scopeInfo (ciContentNoParse $ CIRcvGroupFeatureRejected f) Nothing Nothing False
- timed' gInfo' = if forwarded then rcvCITimed_ (Just Nothing) itemTTL else rcvGroupCITimed gInfo' itemTTL
+ rejected f = newChatItem (ciContentNoParse $ CIRcvGroupFeatureRejected f) Nothing Nothing False
+ timed' = if forwarded then rcvCITimed_ (Just Nothing) itemTTL else rcvGroupCITimed gInfo itemTTL
live' = fromMaybe False live_
- ExtMsgContent content mentions fInv_ itemTTL live_ msgScope_ = mcExtMsgContent mc
+ ExtMsgContent content mentions fInv_ itemTTL live_ = mcExtMsgContent mc
ts@(_, ft_) = msgContentTexts content
- saveRcvCI gInfo' m' scopeInfo = saveRcvChatItem' user (CDGroupRcv gInfo' scopeInfo m') msg sharedMsgId_ brokerTs
- createBlockedByAdmin gInfo' m' scopeInfo
- | groupFeatureAllowed SGFFullDelete gInfo' = do
+ saveRcvCI = saveRcvChatItem' user (CDGroupRcv gInfo m) (memberNotInHistory m) msg sharedMsgId_ brokerTs
+ createBlockedByAdmin
+ | groupFeatureAllowed SGFFullDelete gInfo = do
-- ignores member role when blocked by admin
- (ci, cInfo) <- saveRcvCI gInfo' m' scopeInfo (ciContentNoParse CIRcvBlocked) Nothing (timed' gInfo') False M.empty
- ci' <- withStore' $ \db -> updateGroupCIBlockedByAdmin db user gInfo' ci brokerTs
- groupMsgToView cInfo ci'
+ ci <- saveRcvCI (ciContentNoParse CIRcvBlocked) Nothing timed' False M.empty
+ ci' <- withStore' $ \db -> updateGroupCIBlockedByAdmin db user gInfo ci brokerTs
+ groupMsgToView gInfo ci'
| otherwise = do
- file_ <- processFileInv m'
- (ci, cInfo) <- createNonLive gInfo' m' scopeInfo file_
- ci' <- withStore' $ \db -> markGroupCIBlockedByAdmin db user gInfo' ci
- groupMsgToView cInfo ci'
- applyModeration gInfo' m' scopeInfo CIModeration {moderatorMember = moderator@GroupMember {memberRole = moderatorRole}, moderatedAt}
+ file_ <- processFileInv
+ ci <- createNonLive file_
+ ci' <- withStore' $ \db -> markGroupCIBlockedByAdmin db user gInfo ci
+ groupMsgToView gInfo ci'
+ applyModeration CIModeration {moderatorMember = moderator@GroupMember {memberRole = moderatorRole}, moderatedAt}
| moderatorRole < GRModerator || moderatorRole < memberRole =
- createContentItem gInfo' m' scopeInfo
- | groupFeatureMemberAllowed SGFFullDelete moderator gInfo' = do
- (ci, cInfo) <- saveRcvCI gInfo' m' scopeInfo (ciContentNoParse CIRcvModerated) Nothing (timed' gInfo') False M.empty
- ci' <- withStore' $ \db -> updateGroupChatItemModerated db user gInfo' ci moderator moderatedAt
- groupMsgToView cInfo ci'
+ createContentItem
+ | groupFeatureMemberAllowed SGFFullDelete moderator gInfo = do
+ ci <- saveRcvCI (ciContentNoParse CIRcvModerated) Nothing timed' False M.empty
+ ci' <- withStore' $ \db -> updateGroupChatItemModerated db user gInfo ci moderator moderatedAt
+ groupMsgToView gInfo ci'
| otherwise = do
- file_ <- processFileInv m'
- (ci, _cInfo) <- createNonLive gInfo' m' scopeInfo file_
- deletions <- markGroupCIsDeleted user gInfo' scopeInfo [CChatItem SMDRcv ci] (Just moderator) moderatedAt
+ file_ <- processFileInv
+ ci <- createNonLive file_
+ deletions <- markGroupCIsDeleted user gInfo [CChatItem SMDRcv ci] (Just moderator) moderatedAt
toView $ CEvtChatItemsDeleted user deletions False False
- createNonLive gInfo' m' scopeInfo file_ = do
- saveRcvCI gInfo' m' scopeInfo (CIRcvMsgContent content, ts) (snd <$> file_) (timed' gInfo') False mentions
- createContentItem gInfo' m' scopeInfo = do
- file_ <- processFileInv m'
- newChatItem gInfo' m' scopeInfo (CIRcvMsgContent content, ts) (snd <$> file_) (timed' gInfo') live'
- when (showMessages $ memberSettings m') $ autoAcceptFile file_
- processFileInv m' =
- processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m'
- newChatItem gInfo' m' scopeInfo ciContent ciFile_ timed_ live = do
- let mentions' = if showMessages (memberSettings m') then mentions else []
- (ci, cInfo) <- saveRcvCI gInfo' m' scopeInfo ciContent ciFile_ timed_ live mentions'
- ci' <- blockedMember m' ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo' ci
- reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getGroupCIReactions db gInfo' memberId sharedMsgId) sharedMsgId_
- groupMsgToView cInfo ci' {reactions}
+ createNonLive file_ =
+ saveRcvCI (CIRcvMsgContent content, ts) (snd <$> file_) timed' False mentions
+ createContentItem = do
+ file_ <- processFileInv
+ newChatItem (CIRcvMsgContent content, ts) (snd <$> file_) timed' live'
+ when (showMessages $ memberSettings m) $ autoAcceptFile file_
+ processFileInv =
+ processFileInvitation fInv_ content $ \db -> createRcvGroupFileTransfer db userId m
+ newChatItem ciContent ciFile_ timed_ live = do
+ let mentions' = if showMessages (memberSettings m) then mentions else []
+ ci <- saveRcvCI ciContent ciFile_ timed_ live mentions'
+ ci' <- blockedMember m ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo ci
+ reactions <- maybe (pure []) (\sharedMsgId -> withStore' $ \db -> getGroupCIReactions db gInfo memberId sharedMsgId) sharedMsgId_
+ groupMsgToView gInfo ci' {reactions}
- groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> Map MemberName MsgMention -> Maybe MsgScope -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM ()
- groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc mentions msgScope_ msg@RcvMessage {msgId} brokerTs ttl_ live_
+ groupMessageUpdate :: GroupInfo -> GroupMember -> SharedMsgId -> MsgContent -> Map MemberName MsgMention -> RcvMessage -> UTCTime -> Maybe Int -> Maybe Bool -> CM ()
+ groupMessageUpdate gInfo@GroupInfo {groupId} m@GroupMember {groupMemberId, memberId} sharedMsgId mc mentions msg@RcvMessage {msgId} brokerTs ttl_ live_
| prohibitedSimplexLinks gInfo m ft_ =
messageWarning $ "x.msg.update ignored: feature not allowed " <> groupFeatureNameText GFSimplexLinks
| otherwise = do
@@ -1717,20 +1684,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- Chat item and update message which created it will have different sharedMsgId in this case...
let timed_ = rcvGroupCITimed gInfo ttl_
mentions' = if showMessages (memberSettings m) then mentions else []
- (gInfo', m', scopeInfo) <- mkGetMessageChatScope vr user gInfo m msgScope_
- (ci, cInfo) <- saveRcvChatItem' user (CDGroupRcv gInfo' scopeInfo m') msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) (memberNotInHistory m) msg (Just sharedMsgId) brokerTs (content, ts) Nothing timed_ live mentions'
ci' <- withStore' $ \db -> do
createChatItemVersion db (chatItemId' ci) brokerTs mc
ci' <- updateGroupChatItem db user groupId ci content True live Nothing
- blockedMember m' ci' $ markGroupChatItemBlocked db user gInfo' ci'
- toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv cInfo ci')
+ blockedMember m ci' $ markGroupChatItemBlocked db user gInfo ci'
+ toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
where
content = CIRcvMsgContent mc
ts@(_, ft_) = msgContentTexts mc
live = fromMaybe False live_
updateRcvChatItem = do
cci <- withStore $ \db -> getGroupChatItemBySharedMsgId db user gInfo groupMemberId sharedMsgId
- scopeInfo <- withStore $ \db -> getGroupChatScopeInfoForItem db vr user gInfo (cChatItemId cci)
case cci of
CChatItem SMDRcv ci@ChatItem {chatDir = CIGroupRcv m', meta = CIMeta {itemLive}, content = CIRcvMsgContent oldMC} ->
if sameMemberId memberId m'
@@ -1746,12 +1711,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
ciMentions <- getRcvCIMentions db user gInfo ft_ mentions
ci' <- updateGroupChatItem db user groupId ci {reactions} content edited live $ Just msgId
updateGroupCIMentions db gInfo ci' ciMentions
- toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo scopeInfo) ci')
- startUpdatedTimedItemThread user (ChatRef CTGroup groupId $ toChatScope <$> scopeInfo) ci ci'
- else toView $ CEvtChatItemNotChanged user (AChatItem SCTGroup SMDRcv (GroupChat gInfo scopeInfo) ci)
+ toView $ CEvtChatItemUpdated user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci')
+ startUpdatedTimedItemThread user (ChatRef CTGroup groupId) ci ci'
+ else toView $ CEvtChatItemNotChanged user (AChatItem SCTGroup SMDRcv (GroupChat gInfo) ci)
else messageError "x.msg.update: group member attempted to update a message of another member"
_ -> messageError "x.msg.update: group member attempted invalid message update"
+ memberNotInHistory :: GroupMember -> Maybe NotInHistory
+ memberNotInHistory = \case
+ GroupMember {memberStatus = GSMemPendingApproval} -> Just NotInHistory
+ _ -> Nothing
+
groupMessageDelete :: GroupInfo -> GroupMember -> SharedMsgId -> Maybe MemberId -> RcvMessage -> UTCTime -> CM ()
groupMessageDelete gInfo@GroupInfo {membership} m@GroupMember {memberId, memberRole = senderRole} sharedMsgId sndMemberId_ RcvMessage {msgId} brokerTs = do
let msgMemberId = fromMaybe memberId sndMemberId_
@@ -1790,10 +1760,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise = a
delete :: CChatItem 'CTGroup -> Maybe GroupMember -> CM ()
delete cci byGroupMember = do
- scopeInfo <- withStore $ \db -> getGroupChatScopeInfoForItem db vr user gInfo (cChatItemId cci)
deletions <- if groupFeatureMemberAllowed SGFFullDelete m gInfo
- then deleteGroupCIs user gInfo scopeInfo [cci] byGroupMember brokerTs
- else markGroupCIsDeleted user gInfo scopeInfo [cci] byGroupMember brokerTs
+ then deleteGroupCIs user gInfo [cci] byGroupMember brokerTs
+ else markGroupCIsDeleted user gInfo [cci] byGroupMember brokerTs
toView $ CEvtChatItemsDeleted user deletions False False
archiveMessageReports :: CChatItem 'CTGroup -> GroupMember -> CM ()
archiveMessageReports (CChatItem _ ci) byMember = do
@@ -1810,8 +1779,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
content = ciContentNoParse $ CIRcvMsgContent $ MCFile ""
- (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
where
brokerTs = metaBrokerTs msgMeta
@@ -1824,9 +1793,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let fileProtocol = if isJust xftpRcvFile then FPXFTP else FPSMP
ciFile = Just $ CIFile {fileId, fileName, fileSize, fileSource = Nothing, fileStatus = CIFSRcvInvitation, fileProtocol}
content = ciContentNoParse $ CIRcvMsgContent $ MCFile ""
- (ci, cInfo) <- saveRcvChatItem' user (CDGroupRcv gInfo Nothing m) msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
+ ci <- saveRcvChatItem' user (CDGroupRcv gInfo m) Nothing msg sharedMsgId_ brokerTs content ciFile Nothing False M.empty
ci' <- blockedMember m ci $ withStore' $ \db -> markGroupChatItemBlocked db user gInfo ci
- groupMsgToView cInfo ci'
+ groupMsgToView gInfo ci'
blockedMember :: Monad m' => GroupMember -> ChatItem c d -> m' (ChatItem c d) -> m' (ChatItem c d)
blockedMember m ci blockedCI
@@ -1977,9 +1946,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
_ -> messageError "x.file.acpt.inv: member connection is not active"
else messageError "x.file.acpt.inv: fileName is different from expected"
- groupMsgToView :: forall d. MsgDirectionI d => ChatInfo 'CTGroup -> ChatItem 'CTGroup d -> CM ()
- groupMsgToView cInfo ci = do
- toView $ CEvtNewChatItems user [AChatItem SCTGroup (msgDirection @d) cInfo ci]
+ groupMsgToView :: forall d. MsgDirectionI d => GroupInfo -> ChatItem 'CTGroup d -> CM ()
+ groupMsgToView gInfo ci =
+ toView $ CEvtNewChatItems user [AChatItem SCTGroup (msgDirection @d) (GroupChat gInfo) ci]
processGroupInvitation :: Contact -> GroupInvitation -> RcvMessage -> MsgMeta -> CM ()
processGroupInvitation ct inv msg msgMeta = do
@@ -2004,9 +1973,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
toView $ CEvtUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct)
else do
let content = CIRcvGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs content
+ ci <- saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs content
withStore' $ \db -> setGroupInvitationChatItemId db user groupId (chatItemId' ci)
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
toView $ CEvtReceivedGroupInvitation {user, groupInfo = gInfo, contact = ct, fromMemberRole = fromRole, memberRole = memRole}
where
brokerTs = metaBrokerTs msgMeta
@@ -2032,8 +2001,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted
activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted}
let ct'' = ct' {activeConn = activeConn'} :: Contact
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted)
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ ci <- saveRcvChatItemNoParse user (CDDirectRcv ct'') msg brokerTs (CIRcvDirectEvent RDEContactDeleted)
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct'') ci]
toView $ CEvtContactDeletedByContact user ct''
else do
contactConns <- withStore' $ \db -> getContactConnections db vr userId c
@@ -2087,71 +2056,26 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xInfoMember gInfo m p' brokerTs = void $ processMemberProfileUpdate gInfo m p' True (Just brokerTs)
xGrpLinkMem :: GroupInfo -> GroupMember -> Connection -> Profile -> CM ()
- xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory} Connection {viaGroupLink} p' = do
+ xGrpLinkMem gInfo@GroupInfo {membership, businessChat} m@GroupMember {groupMemberId, memberCategory, memberStatus} Connection {viaGroupLink} p' = do
xGrpLinkMemReceived <- withStore $ \db -> getXGrpLinkMemReceived db groupMemberId
if (viaGroupLink || isJust businessChat) && isNothing (memberContactId m) && memberCategory == GCHostMember && not xGrpLinkMemReceived
then do
m' <- processMemberProfileUpdate gInfo m p' False Nothing
withStore' $ \db -> setXGrpLinkMemReceived db groupMemberId True
- let connectedIncognito = memberIncognito membership
- probeMatchingMemberContact m' connectedIncognito
+ unless (memberStatus == GSMemPendingApproval) $ do
+ let connectedIncognito = memberIncognito membership
+ probeMatchingMemberContact m' connectedIncognito
else messageError "x.grp.link.mem error: invalid group link host profile update"
- xGrpLinkAcpt :: GroupInfo -> GroupMember -> GroupAcceptance -> GroupMemberRole -> MemberId -> RcvMessage -> UTCTime -> CM ()
- xGrpLinkAcpt gInfo@GroupInfo {membership} m acceptance role memberId msg brokerTs
- | sameMemberId memberId membership = processUserAccepted
- | otherwise =
- withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case
- Left _ -> messageError "x.grp.link.acpt error: referenced member does not exist"
- Right referencedMember -> do
- (referencedMember', gInfo') <- withFastStore' $ \db -> do
- referencedMember' <- updateGroupMemberAccepted db user referencedMember (newMemberStatus referencedMember) role
- gInfo' <- updateGroupMembersRequireAttention db user gInfo referencedMember referencedMember'
- pure (referencedMember', gInfo')
- when (memberCategory referencedMember == GCInviteeMember) $ introduceToRemainingMembers referencedMember'
- -- create item in both scopes
- memberConnectedChatItem gInfo' Nothing referencedMember'
- let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Just referencedMember'}
- gEvent = RGEMemberAccepted (groupMemberId' referencedMember') (fromLocalProfile $ memberProfile referencedMember')
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
- groupMsgToView cInfo ci
- toView $ CEvtMemberAcceptedByOther user gInfo' m referencedMember'
- where
- newMemberStatus refMem = case memberConn refMem of
- Just c | connReady c -> GSMemConnected
- _ -> GSMemAnnounced
- where
- processUserAccepted = case acceptance of
- GAAccepted -> do
- membership' <- withStore' $ \db -> updateGroupMemberAccepted db user membership GSMemConnected role
- -- create item in both scopes
- let gInfo' = gInfo {membership = membership'}
- cd = CDGroupRcv gInfo' Nothing m
- createInternalChatItem user cd (CIRcvGroupE2EEInfo E2EInfo {pqEnabled = PQEncOff}) Nothing
- createGroupFeatureItems user cd CIRcvGroupFeature gInfo'
- maybeCreateGroupDescrLocal gInfo' m
- createInternalChatItem user cd (CIRcvGroupEvent RGEUserAccepted) Nothing
- let scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m) (CIRcvGroupEvent RGEUserAccepted) Nothing
- toView $ CEvtUserJoinedGroup user gInfo' m
- GAPendingReview -> do
- membership' <- withStore' $ \db -> updateGroupMemberAccepted db user membership GSMemPendingReview role
- let gInfo' = gInfo {membership = membership'}
- scopeInfo = Just $ GCSIMemberSupport {groupMember_ = Nothing}
- createInternalChatItem user (CDGroupSnd gInfo' scopeInfo) (CISndGroupEvent SGEUserPendingReview) Nothing
- toView $ CEvtMemberAcceptedByOther user gInfo' m membership'
- GAPendingApproval ->
- messageWarning "x.grp.link.acpt: unexpected group acceptance - pending approval"
- introduceToRemainingMembers acceptedMember = do
- introduceToRemaining vr user gInfo acceptedMember
- when (groupFeatureAllowed SGFHistory gInfo) $ sendHistory user gInfo acceptedMember
-
- maybeCreateGroupDescrLocal :: GroupInfo -> GroupMember -> CM ()
- maybeCreateGroupDescrLocal gInfo@GroupInfo {groupProfile = GroupProfile {description}} m =
- unless expectHistory $ forM_ description $ \descr ->
- createInternalChatItem user (CDGroupRcv gInfo Nothing m) (CIRcvMsgContent $ MCText descr) Nothing
- where
- expectHistory = groupFeatureAllowed SGFHistory gInfo && m `supportsVersion` groupHistoryIncludeWelcomeVersion
+ xGrpLinkAcpt :: GroupInfo -> GroupMember -> GroupMemberRole -> CM ()
+ xGrpLinkAcpt gInfo@GroupInfo {membership} m role = do
+ membership' <- withStore' $ \db -> do
+ updateGroupMemberStatus db userId m GSMemConnected
+ updateGroupMemberAccepted db user membership role
+ let m' = m {memberStatus = GSMemConnected}
+ toView $ CEvtUserJoinedGroup user gInfo {membership = membership'} m'
+ let connectedIncognito = memberIncognito membership
+ probeMatchingMemberContact m' connectedIncognito
processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember
processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_
@@ -2192,9 +2116,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
BCCustomer -> customerId == memberId
createProfileUpdatedItem m' =
when createItems $ do
- (gInfo', m'', scopeInfo) <- mkGroupChatScope gInfo m'
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m'') ciContent itemTs_
+ createInternalChatItem user (CDGroupRcv gInfo m') ciContent itemTs_
createFeatureEnabledItems :: Contact -> CM ()
createFeatureEnabledItems ct@Contact {mergedPreferences} =
@@ -2281,7 +2204,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
then do
g <- asks random
dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing
- (ci, cInfo) <- saveCallItem CISCallPending
+ ci <- saveCallItem CISCallPending
callUUID <- UUID.toText <$> liftIO V4.nextRandom
let sharedKey = C.Key . C.dhBytes' <$> (C.dh' <$> callDhPubKey <*> (snd <$> dhKeyPair))
callState = CallInvitationReceived {peerCallType = callType, localDhPubKey = fst <$> dhKeyPair, sharedKey}
@@ -2294,15 +2217,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
call_ <- atomically (TM.lookupInsert contactId call' calls)
forM_ call_ $ \call -> updateCallItemStatus user ct call WCSDisconnected Nothing
toView $ CEvtCallInvitation RcvCallInvitation {user, contact = ct, callType, sharedKey, callUUID, callTs = chatItemTs' ci}
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
else featureRejected CFCalls
where
brokerTs = metaBrokerTs msgMeta
saveCallItem status = saveRcvChatItemNoParse user (CDDirectRcv ct) msg brokerTs (CIRcvCall status 0)
featureRejected f = do
let content = ciContentNoParse $ CIRcvChatFeatureRejected f
- (ci, cInfo) <- saveRcvChatItem' user (CDDirectRcv ct) msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ ci <- saveRcvChatItem' user (CDDirectRcv ct) Nothing msg sharedMsgId_ brokerTs content Nothing Nothing False M.empty
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat ct) ci]
-- to party initiating call
xCallOffer :: Contact -> CallId -> CallOffer -> RcvMessage -> CM ()
@@ -2378,7 +2301,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
timed_ <- callTimed ct aciContent
updateDirectChatItemView user ct chatItemId aciContent False False timed_ $ Just msgId
forM_ (timed_ >>= timedDeleteAt') $
- startProximateTimedItemThread user (ChatRef CTDirect ctId' Nothing, chatItemId)
+ startProximateTimedItemThread user (ChatRef CTDirect ctId', chatItemId)
msgCallStateError :: Text -> Call -> CM ()
msgCallStateError eventName Call {callState} =
@@ -2467,48 +2390,25 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- TODO show/log error, other events in SMP confirmation
_ -> pure (conn', False)
- xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MsgScope -> RcvMessage -> UTCTime -> CM ()
- xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msgScope_ msg brokerTs = do
+ xGrpMemNew :: GroupInfo -> GroupMember -> MemberInfo -> RcvMessage -> UTCTime -> CM ()
+ xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do
checkHostRole m memRole
unless (sameMemberId memId $ membership gInfo) $
withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case
Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do
- (updatedMember, gInfo') <- withStore $ \db -> do
- updatedMember <- updateUnknownMemberAnnounced db vr user m unknownMember memInfo initialStatus
- gInfo' <- if memberPending updatedMember
- then liftIO $ increaseGroupMembersRequireAttention db user gInfo
- else pure gInfo
- pure (updatedMember, gInfo')
- toView $ CEvtUnknownMemberAnnounced user gInfo' m unknownMember updatedMember
- memberAnnouncedToView updatedMember gInfo'
+ updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db vr user m unknownMember memInfo
+ toView $ CEvtUnknownMemberAnnounced user gInfo m unknownMember updatedMember
+ memberAnnouncedToView updatedMember
Right _ -> messageError "x.grp.mem.new error: member already exists"
Left _ -> do
- (newMember, gInfo') <- withStore $ \db -> do
- newMember <- createNewGroupMember db user gInfo m memInfo GCPostMember initialStatus
- gInfo' <- if memberPending newMember
- then liftIO $ increaseGroupMembersRequireAttention db user gInfo
- else pure gInfo
- pure (newMember, gInfo')
- memberAnnouncedToView newMember gInfo'
+ newMember <- withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
+ memberAnnouncedToView newMember
where
- initialStatus = case msgScope_ of
- Just (MSMember _) -> GSMemPendingReview
- _ -> GSMemAnnounced
- memberAnnouncedToView announcedMember@GroupMember {groupMemberId, memberProfile} gInfo' = do
- (announcedMember', scopeInfo) <- getMemNewChatScope announcedMember
+ memberAnnouncedToView announcedMember@GroupMember {groupMemberId, memberProfile} = do
let event = RGEMemberAdded groupMemberId (fromLocalProfile memberProfile)
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m) msg brokerTs (CIRcvGroupEvent event)
- groupMsgToView cInfo ci
- case scopeInfo of
- Just (GCSIMemberSupport _) -> do
- createInternalChatItem user (CDGroupRcv gInfo' scopeInfo m) (CIRcvGroupEvent RGENewMemberPendingReview) (Just brokerTs)
- _ -> pure ()
- toView $ CEvtJoinedGroupMemberConnecting user gInfo' m announcedMember'
- getMemNewChatScope announcedMember = case msgScope_ of
- Nothing -> pure (announcedMember, Nothing)
- Just (MSMember _) -> do
- (announcedMember', scopeInfo) <- mkMemberSupportChatInfo announcedMember
- pure (announcedMember', Just scopeInfo)
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent event)
+ groupMsgToView gInfo ci
+ toView $ CEvtJoinedGroupMemberConnecting user gInfo m announcedMember
xGrpMemIntro :: GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> CM ()
xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do
@@ -2564,11 +2464,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- For now, this branch compensates for the lack of delayed message delivery.
Left _ -> withStore $ \db -> createNewGroupMember db user gInfo m memInfo GCPostMember GSMemAnnounced
Right m' -> pure m'
- -- TODO [knocking] separate pending statuses from GroupMemberStatus?
- -- TODO add GSMemIntroInvitedPending, GSMemConnectedPending, etc.?
- -- TODO keep as is? (GSMemIntroInvited has no purpose)
- let newMemberStatus = if memberPending toMember then memberStatus toMember else GSMemIntroInvited
- withStore' $ \db -> saveMemberInvitation db toMember introInv newMemberStatus
+ withStore' $ \db -> saveMemberInvitation db toMember introInv
subMode <- chatReadVar subscriptionMode
-- [incognito] send membership incognito profile, create direct connection as incognito
let membershipProfile = redactedMemberProfile $ fromLocalProfile $ memberProfile membership
@@ -2596,10 +2492,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| senderRole < GRAdmin || senderRole < fromRole = messageError "x.grp.mem.role with insufficient member permissions"
| otherwise = do
withStore' $ \db -> updateGroupMemberRole db user member memRole
- (gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo' m
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
- groupMsgToView cInfo ci
- toView CEvtMemberRole {user, groupInfo = gInfo'', byMember = m', member = member {memberRole = memRole}, fromRole, toRole = memRole}
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
+ groupMsgToView gInfo ci
+ toView CEvtMemberRole {user, groupInfo = gInfo', byMember = m, member = member {memberRole = memRole}, fromRole, toRole = memRole}
checkHostRole :: GroupMember -> GroupMemberRole -> CM ()
checkHostRole GroupMember {memberRole, localDisplayName} memRole =
@@ -2625,10 +2520,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
bm' <- setMemberBlocked bm
toggleNtf bm' (not blocked)
let ciContent = CIRcvGroupEvent $ RGEMemberBlocked bmId (fromLocalProfile bmp) blocked
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs ciContent
- groupMsgToView cInfo ci
- toView CEvtMemberBlockedForAll {user, groupInfo = gInfo', byMember = m', member = bm, blocked}
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs ciContent
+ groupMsgToView gInfo ci
+ toView CEvtMemberBlockedForAll {user, groupInfo = gInfo, byMember = m, member = bm, blocked}
Left (SEGroupMemberNotFoundByMemberId _) -> do
bm <- createUnknownMember gInfo memId
bm' <- setMemberBlocked bm
@@ -2700,19 +2594,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- ? prohibit deleting member if it's the sender - sender should use x.grp.leave
deleteMemberConnection member
-- undeleted "member connected" chat item will prevent deletion of member record
- gInfo' <- deleteOrUpdateMemberRecord user gInfo member
+ deleteOrUpdateMemberRecord user member
when withMessages $ deleteMessages member SMDRcv
deleteMemberItem $ RGEMemberDeleted groupMemberId (fromLocalProfile memberProfile)
- toView $ CEvtDeletedMember user gInfo' m member {memberStatus = GSMemRemoved} withMessages
+ toView $ CEvtDeletedMember user gInfo m member {memberStatus = GSMemRemoved} withMessages
where
checkRole GroupMember {memberRole} a
| senderRole < GRAdmin || senderRole < memberRole =
messageError "x.grp.mem.del with insufficient member permissions"
| otherwise = a
deleteMemberItem gEvent = do
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo' scopeInfo m') msg brokerTs (CIRcvGroupEvent gEvent)
- groupMsgToView cInfo ci
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent gEvent)
+ groupMsgToView gInfo ci
deleteMessages :: MsgDirectionI d => GroupMember -> SMsgDirection d -> CM ()
deleteMessages delMem msgDir
| groupFeatureMemberAllowed SGFFullDelete m gInfo = deleteGroupMemberCIs user gInfo delMem m msgDir
@@ -2722,15 +2615,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
xGrpLeave gInfo m msg brokerTs = do
deleteMemberConnection m
-- member record is not deleted to allow creation of "member left" chat item
- gInfo' <- withStore' $ \db -> do
- updateGroupMemberStatus db userId m GSMemLeft
- if gmRequiresAttention m
- then decreaseGroupMembersRequireAttention db user gInfo
- else pure gInfo
- (gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo' m
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEMemberLeft)
- groupMsgToView cInfo ci
- toView $ CEvtLeftMember user gInfo'' m' {memberStatus = GSMemLeft}
+ withStore' $ \db -> updateGroupMemberStatus db userId m GSMemLeft
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEMemberLeft)
+ groupMsgToView gInfo ci
+ toView $ CEvtLeftMember user gInfo m {memberStatus = GSMemLeft}
xGrpDel :: GroupInfo -> GroupMember -> RcvMessage -> UTCTime -> CM ()
xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg brokerTs = do
@@ -2741,10 +2629,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
pure members
-- member records are not deleted to keep history
deleteMembersConnections user ms
- (gInfo'', m', scopeInfo) <- mkGroupChatScope gInfo m
- (ci, cInfo) <- saveRcvChatItemNoParse user (CDGroupRcv gInfo'' scopeInfo m') msg brokerTs (CIRcvGroupEvent RGEGroupDeleted)
- groupMsgToView cInfo ci
- toView $ CEvtGroupDeleted user gInfo'' {membership = membership {memberStatus = GSMemGroupDeleted}} m'
+ ci <- saveRcvChatItemNoParse user (CDGroupRcv gInfo m) msg brokerTs (CIRcvGroupEvent RGEGroupDeleted)
+ groupMsgToView gInfo ci
+ toView $ CEvtGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m
xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM ()
xGrpInfo g@GroupInfo {groupProfile = p, businessChat} m@GroupMember {memberRole} p' msg brokerTs
@@ -2752,13 +2639,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
| otherwise = case businessChat of
Nothing -> unless (p == p') $ do
g' <- withStore $ \db -> updateGroupProfile db user g p'
- (g'', m', scopeInfo) <- mkGroupChatScope g' m
- toView $ CEvtGroupUpdated user g g'' (Just m')
- let cd = CDGroupRcv g'' scopeInfo m'
+ toView $ CEvtGroupUpdated user g g' (Just m)
+ let cd = CDGroupRcv g' m
unless (sameGroupProfileInfo p p') $ do
- (ci, cInfo) <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
- groupMsgToView cInfo ci
- createGroupFeatureChangedItems user cd CIRcvGroupFeature g g''
+ ci <- saveRcvChatItemNoParse user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
+ groupMsgToView g' ci
+ createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
Just _ -> updateGroupPrefs_ g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
xGrpPrefs :: GroupInfo -> GroupMember -> GroupPreferences -> CM ()
@@ -2771,9 +2657,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
unless (groupPreferences p == Just ps') $ do
g' <- withStore' $ \db -> updateGroupPreferences db user g ps'
toView $ CEvtGroupUpdated user g g' (Just m)
- (g'', m', scopeInfo) <- mkGroupChatScope g' m
- let cd = CDGroupRcv g'' scopeInfo m'
- createGroupFeatureChangedItems user cd CIRcvGroupFeature g g''
+ let cd = CDGroupRcv g' m
+ createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM ()
xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do
@@ -2813,12 +2698,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
dm <- encodeConnInfo $ XInfo p
joinAgentConnectionAsync user True connReq dm subMode
createItems mCt' m' = do
- (g', m'', scopeInfo) <- mkGroupChatScope g m'
- createInternalChatItem user (CDGroupRcv g' scopeInfo m'') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing
- toView $ CEvtNewMemberContactReceivedInv user mCt' g' m''
+ createInternalChatItem user (CDGroupRcv g m') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing
+ toView $ CEvtNewMemberContactReceivedInv user mCt' g m'
forM_ mContent_ $ \mc -> do
- (ci, cInfo) <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc, msgContentTexts mc)
- toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv cInfo ci]
+ ci <- saveRcvChatItem user (CDDirectRcv mCt') msg brokerTs (CIRcvMsgContent mc, msgContentTexts mc)
+ toView $ CEvtNewChatItems user [AChatItem SCTDirect SMDRcv (DirectChat mCt') ci]
securityCodeChanged :: Contact -> CM ()
securityCodeChanged ct = do
@@ -2842,16 +2726,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let body = LB.toStrict $ J.encode msg
rcvMsg@RcvMessage {chatMsgEvent = ACME _ event} <- saveGroupFwdRcvMsg user groupId m author body chatMsg
case event of
- XMsgNew mc -> memberCanSend author scope $ newGroupContentMessage gInfo author mc rcvMsg msgTs True
- where ExtMsgContent {scope} = mcExtMsgContent mc
- -- file description is always allowed, to allow sending files to support scope
- XMsgFileDescr sharedMsgId fileDescr -> groupMessageFileDescription gInfo author sharedMsgId fileDescr
- XMsgUpdate sharedMsgId mContent mentions ttl live msgScope -> memberCanSend author msgScope $ groupMessageUpdate gInfo author sharedMsgId mContent mentions msgScope rcvMsg msgTs ttl live
+ XMsgNew mc -> memberCanSend author $ newGroupContentMessage gInfo author mc rcvMsg msgTs True
+ XMsgFileDescr sharedMsgId fileDescr -> memberCanSend author $ groupMessageFileDescription gInfo author sharedMsgId fileDescr
+ XMsgUpdate sharedMsgId mContent mentions ttl live -> memberCanSend author $ groupMessageUpdate gInfo author sharedMsgId mContent mentions rcvMsg msgTs ttl live
XMsgDel sharedMsgId memId -> groupMessageDelete gInfo author sharedMsgId memId rcvMsg msgTs
XMsgReact sharedMsgId (Just memId) reaction add -> groupMsgReaction gInfo author sharedMsgId memId reaction add rcvMsg msgTs
XFileCancel sharedMsgId -> xFileCancelGroup gInfo author sharedMsgId
XInfo p -> xInfoMember gInfo author p msgTs
- XGrpMemNew memInfo msgScope -> xGrpMemNew gInfo author memInfo msgScope rcvMsg msgTs
+ XGrpMemNew memInfo -> xGrpMemNew gInfo author memInfo rcvMsg msgTs
XGrpMemRole memId memRole -> xGrpMemRole gInfo author memId memRole rcvMsg msgTs
XGrpMemDel memId withMessages -> xGrpMemDel gInfo author memId withMessages rcvMsg msgTs
XGrpLeave -> xGrpLeave gInfo author rcvMsg msgTs
@@ -2874,11 +2756,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> CM ()
groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do
- (gInfo', m', scopeInfo) <- mkGroupChatScope gInfo m
- checkIntegrityCreateItem (CDGroupRcv gInfo' scopeInfo m') msgMeta `catchChatError` \_ -> pure ()
+ checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure ()
forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do
withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus
- updateGroupItemsStatus gInfo' m' conn agentMsgId (GSSRcvd msgRcptStatus) Nothing
+ updateGroupItemsStatus gInfo m conn agentMsgId (GSSRcvd msgRcptStatus) Nothing
-- Searches chat items for many agent message IDs and updates their status
updateDirectItemsStatusMsgs :: Contact -> Connection -> [AgentMsgId] -> CIStatus 'MDSnd -> CM ()
@@ -2923,15 +2804,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
updateGroupItemsStatus gInfo@GroupInfo {groupId} GroupMember {groupMemberId} Connection {connId} msgId newMemStatus viaProxy_ = do
items <- withStore' (\db -> getGroupChatItemsByAgentMsgId db user groupId connId msgId)
cis <- catMaybes <$> withStore (\db -> mapM (updateItem db) items)
- -- SENT and RCVD events are received for messages that may be batched in single scope,
- -- so we can look up scope of first item
- scopeInfo <- case cis of
- (ci : _) -> withStore $ \db -> getGroupChatScopeInfoForItem db vr user gInfo (chatItemId' ci)
- _ -> pure Nothing
- let acis = map (gItem scopeInfo) cis
+ let acis = map gItem cis
unless (null acis) $ toView $ CEvtChatItemsStatusesUpdated user acis
where
- gItem scopeInfo ci = AChatItem SCTGroup SMDSnd (GroupChat gInfo scopeInfo) ci
+ gItem = AChatItem SCTGroup SMDSnd (GroupChat gInfo)
updateItem :: DB.Connection -> CChatItem 'CTGroup -> ExceptT StoreError IO (Maybe (ChatItem 'CTGroup 'MDSnd))
updateItem db = \case
(CChatItem SMDSnd ChatItem {meta = CIMeta {itemStatus = CISSndRcvd _ SSPComplete}}) -> pure Nothing
diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs
index dfd37527d1..79d416dee5 100644
--- a/src/Simplex/Chat/Messages.hs
+++ b/src/Simplex/Chat/Messages.hs
@@ -61,38 +61,9 @@ import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
deriving (Eq, Show, Ord)
-data GroupChatScope
- = GCSMemberSupport {groupMemberId_ :: Maybe GroupMemberId} -- Nothing means own conversation with support
- deriving (Eq, Show, Ord)
-
-data GroupChatScopeTag
- = GCSTMemberSupport_
- deriving (Eq, Show)
-
-instance FromField GroupChatScopeTag where fromField = fromTextField_ textDecode
-
-instance ToField GroupChatScopeTag where toField = toField . textEncode
-
-instance TextEncoding GroupChatScopeTag where
- textDecode = \case
- "member_support" -> Just GCSTMemberSupport_
- _ -> Nothing
- textEncode = \case
- GCSTMemberSupport_ -> "member_support"
-
data ChatName = ChatName {chatType :: ChatType, chatName :: Text}
deriving (Show)
-data SendName
- = SNDirect ContactName
- | SNGroup GroupName (Maybe GroupScopeName)
- | SNLocal
- deriving (Show)
-
-data GroupScopeName
- = GSNMemberSupport (Maybe ContactName)
- deriving (Show)
-
chatTypeStr :: ChatType -> Text
chatTypeStr = \case
CTDirect -> "@"
@@ -104,52 +75,44 @@ chatTypeStr = \case
chatNameStr :: ChatName -> String
chatNameStr (ChatName cType name) = T.unpack $ chatTypeStr cType <> if T.any isSpace name then "'" <> name <> "'" else name
-data ChatRef = ChatRef ChatType Int64 (Maybe GroupChatScope)
+data ChatRef = ChatRef ChatType Int64
deriving (Eq, Show, Ord)
data ChatInfo (c :: ChatType) where
DirectChat :: Contact -> ChatInfo 'CTDirect
- GroupChat :: GroupInfo -> Maybe GroupChatScopeInfo -> ChatInfo 'CTGroup
+ GroupChat :: GroupInfo -> ChatInfo 'CTGroup
LocalChat :: NoteFolder -> ChatInfo 'CTLocal
ContactRequest :: UserContactRequest -> ChatInfo 'CTContactRequest
ContactConnection :: PendingContactConnection -> ChatInfo 'CTContactConnection
deriving instance Show (ChatInfo c)
-data GroupChatScopeInfo
- = GCSIMemberSupport {groupMember_ :: Maybe GroupMember}
- deriving (Show)
-
-toChatScope :: GroupChatScopeInfo -> GroupChatScope
-toChatScope = \case
- GCSIMemberSupport {groupMember_} -> GCSMemberSupport $ groupMemberId' <$> groupMember_
-
-toMsgScope :: GroupInfo -> GroupChatScopeInfo -> MsgScope
-toMsgScope GroupInfo {membership} = \case
- GCSIMemberSupport {groupMember_} -> MSMember $ memberId' $ fromMaybe membership groupMember_
+chatInfoChatTs :: ChatInfo c -> Maybe UTCTime
+chatInfoChatTs = \case
+ DirectChat Contact {chatTs} -> chatTs
+ GroupChat GroupInfo {chatTs} -> chatTs
+ _ -> Nothing
chatInfoToRef :: ChatInfo c -> ChatRef
chatInfoToRef = \case
- DirectChat Contact {contactId} -> ChatRef CTDirect contactId Nothing
- GroupChat GroupInfo {groupId} scopeInfo -> ChatRef CTGroup groupId (toChatScope <$> scopeInfo)
- LocalChat NoteFolder {noteFolderId} -> ChatRef CTLocal noteFolderId Nothing
- ContactRequest UserContactRequest {contactRequestId} -> ChatRef CTContactRequest contactRequestId Nothing
- ContactConnection PendingContactConnection {pccConnId} -> ChatRef CTContactConnection pccConnId Nothing
+ DirectChat Contact {contactId} -> ChatRef CTDirect contactId
+ GroupChat GroupInfo {groupId} -> ChatRef CTGroup groupId
+ LocalChat NoteFolder {noteFolderId} -> ChatRef CTLocal noteFolderId
+ ContactRequest UserContactRequest {contactRequestId} -> ChatRef CTContactRequest contactRequestId
+ ContactConnection PendingContactConnection {pccConnId} -> ChatRef CTContactConnection pccConnId
chatInfoMembership :: ChatInfo c -> Maybe GroupMember
chatInfoMembership = \case
- GroupChat GroupInfo {membership} _scopeInfo -> Just membership
+ GroupChat GroupInfo {membership} -> Just membership
_ -> Nothing
data JSONChatInfo
= JCInfoDirect {contact :: Contact}
- | JCInfoGroup {groupInfo :: GroupInfo, groupChatScope :: Maybe GroupChatScopeInfo}
+ | JCInfoGroup {groupInfo :: GroupInfo}
| JCInfoLocal {noteFolder :: NoteFolder}
| JCInfoContactRequest {contactRequest :: UserContactRequest}
| JCInfoContactConnection {contactConnection :: PendingContactConnection}
-$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GCSI") ''GroupChatScopeInfo)
-
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCInfo") ''JSONChatInfo)
instance ChatTypeI c => FromJSON (ChatInfo c) where
@@ -162,7 +125,7 @@ instance ToJSON (ChatInfo c) where
jsonChatInfo :: ChatInfo c -> JSONChatInfo
jsonChatInfo = \case
DirectChat c -> JCInfoDirect c
- GroupChat g s -> JCInfoGroup g s
+ GroupChat g -> JCInfoGroup g
LocalChat l -> JCInfoLocal l
ContactRequest g -> JCInfoContactRequest g
ContactConnection c -> JCInfoContactConnection c
@@ -174,7 +137,7 @@ deriving instance Show AChatInfo
jsonAChatInfo :: JSONChatInfo -> AChatInfo
jsonAChatInfo = \case
JCInfoDirect c -> AChatInfo SCTDirect $ DirectChat c
- JCInfoGroup g s -> AChatInfo SCTGroup $ GroupChat g s
+ JCInfoGroup g -> AChatInfo SCTGroup $ GroupChat g
JCInfoLocal l -> AChatInfo SCTLocal $ LocalChat l
JCInfoContactRequest g -> AChatInfo SCTContactRequest $ ContactRequest g
JCInfoContactConnection c -> AChatInfo SCTContactConnection $ ContactConnection c
@@ -200,6 +163,8 @@ data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem
}
deriving (Show)
+data NotInHistory = NotInHistory
+
data CIMention = CIMention
{ memberId :: MemberId,
-- member record can be created later than the mention is received
@@ -215,9 +180,6 @@ data CIMentionMember = CIMentionMember
}
deriving (Eq, Show)
-isACIUserMention :: AChatItem -> Bool
-isACIUserMention (AChatItem _ _ _ ci) = isUserMention ci
-
isUserMention :: ChatItem c d -> Bool
isUserMention ChatItem {meta = CIMeta {userMention}} = userMention
@@ -298,16 +260,6 @@ chatItemMember GroupInfo {membership} ChatItem {chatDir} = case chatDir of
CIGroupSnd -> membership
CIGroupRcv m -> m
-chatItemRcvFromMember :: ChatItem c d -> Maybe GroupMember
-chatItemRcvFromMember ChatItem {chatDir} = case chatDir of
- CIGroupRcv m -> Just m
- _ -> Nothing
-
-chatItemIsRcvNew :: ChatItem c d -> Bool
-chatItemIsRcvNew ChatItem {meta = CIMeta {itemStatus}} = case itemStatus of
- CISRcvNew -> True
- _ -> False
-
ciReactionAllowed :: ChatItem c d -> Bool
ciReactionAllowed ChatItem {meta = CIMeta {itemDeleted = Just _}} = False
ciReactionAllowed ChatItem {content} = isJust $ ciMsgContent content
@@ -315,8 +267,8 @@ ciReactionAllowed ChatItem {content} = isJust $ ciMsgContent content
data ChatDirection (c :: ChatType) (d :: MsgDirection) where
CDDirectSnd :: Contact -> ChatDirection 'CTDirect 'MDSnd
CDDirectRcv :: Contact -> ChatDirection 'CTDirect 'MDRcv
- CDGroupSnd :: GroupInfo -> Maybe GroupChatScopeInfo -> ChatDirection 'CTGroup 'MDSnd
- CDGroupRcv :: GroupInfo -> Maybe GroupChatScopeInfo -> GroupMember -> ChatDirection 'CTGroup 'MDRcv
+ CDGroupSnd :: GroupInfo -> ChatDirection 'CTGroup 'MDSnd
+ CDGroupRcv :: GroupInfo -> GroupMember -> ChatDirection 'CTGroup 'MDRcv
CDLocalSnd :: NoteFolder -> ChatDirection 'CTLocal 'MDSnd
CDLocalRcv :: NoteFolder -> ChatDirection 'CTLocal 'MDRcv
@@ -324,8 +276,8 @@ toCIDirection :: ChatDirection c d -> CIDirection c d
toCIDirection = \case
CDDirectSnd _ -> CIDirectSnd
CDDirectRcv _ -> CIDirectRcv
- CDGroupSnd _ _ -> CIGroupSnd
- CDGroupRcv _ _ m -> CIGroupRcv m
+ CDGroupSnd _ -> CIGroupSnd
+ CDGroupRcv _ m -> CIGroupRcv m
CDLocalSnd _ -> CILocalSnd
CDLocalRcv _ -> CILocalRcv
@@ -333,8 +285,8 @@ toChatInfo :: ChatDirection c d -> ChatInfo c
toChatInfo = \case
CDDirectSnd c -> DirectChat c
CDDirectRcv c -> DirectChat c
- CDGroupSnd g s -> GroupChat g s
- CDGroupRcv g s _ -> GroupChat g s
+ CDGroupSnd g -> GroupChat g
+ CDGroupRcv g _ -> GroupChat g
CDLocalSnd l -> LocalChat l
CDLocalRcv l -> LocalChat l
@@ -408,12 +360,6 @@ aChatItemTs (AChatItem _ _ _ ci) = chatItemTs' ci
aChatItemDir :: AChatItem -> MsgDirection
aChatItemDir (AChatItem _ sMsgDir _ _) = toMsgDirection sMsgDir
-aChatItemRcvFromMember :: AChatItem -> Maybe GroupMember
-aChatItemRcvFromMember (AChatItem _ _ _ ci) = chatItemRcvFromMember ci
-
-aChatItemIsRcvNew :: AChatItem -> Bool
-aChatItemIsRcvNew (AChatItem _ _ _ ci) = chatItemIsRcvNew ci
-
updateFileStatus :: forall c d. ChatItem c d -> CIFileStatus d -> ChatItem c d
updateFileStatus ci@ChatItem {file} status = case file of
Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}}
@@ -985,10 +931,7 @@ ciStatusNew = case msgDirection @d of
ciCreateStatus :: forall d. MsgDirectionI d => CIContent d -> CIStatus d
ciCreateStatus content = case msgDirection @d of
SMDSnd -> ciStatusNew
- SMDRcv
- | isCIReport content -> CISRcvRead
- | ciRequiresAttention content -> ciStatusNew
- | otherwise -> CISRcvRead
+ SMDRcv -> if ciRequiresAttention content then ciStatusNew else CISRcvRead
membersGroupItemStatus :: [(GroupSndStatus, Int)] -> CIStatus 'MDSnd
membersGroupItemStatus memStatusCounts
@@ -1431,8 +1374,6 @@ instance MsgDirectionI d => ToJSON (CIFile d) where
toJSON = $(JQ.mkToJSON defaultJSON ''CIFile)
toEncoding = $(JQ.mkToEncoding defaultJSON ''CIFile)
-$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GCS") ''GroupChatScope)
-
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCI") ''JSONCIDirection)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIDirection c d) where
diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs
index cc6529831c..60d5464b79 100644
--- a/src/Simplex/Chat/Messages/CIContent.hs
+++ b/src/Simplex/Chat/Messages/CIContent.hs
@@ -182,9 +182,6 @@ ciMsgContent = \case
CIRcvMsgContent mc -> Just mc
_ -> Nothing
-isCIReport :: CIContent d -> Bool
-isCIReport = maybe False isReport . ciMsgContent
-
data MsgDecryptError
= MDERatchetHeader
| MDETooManySkipped
@@ -209,8 +206,6 @@ ciRequiresAttention content = case msgDirection @d of
CIRcvGroupEvent rge -> case rge of
RGEMemberAdded {} -> False
RGEMemberConnected -> False
- RGEMemberAccepted {} -> False
- RGEUserAccepted -> False
RGEMemberLeft -> False
RGEMemberRole {} -> False
RGEMemberBlocked {} -> False
@@ -222,7 +217,6 @@ ciRequiresAttention content = case msgDirection @d of
RGEInvitedViaGroupLink -> False
RGEMemberCreatedContact -> False
RGEMemberProfileUpdated {} -> False
- RGENewMemberPendingReview -> True
CIRcvConnEvent _ -> True
CIRcvChatFeature {} -> False
CIRcvChatPreference {} -> False
@@ -323,8 +317,6 @@ rcvGroupEventToText :: RcvGroupEvent -> Text
rcvGroupEventToText = \case
RGEMemberAdded _ p -> "added " <> profileToText p
RGEMemberConnected -> "connected"
- RGEMemberAccepted _ p -> "accepted " <> profileToText p
- RGEUserAccepted -> "accepted you"
RGEMemberLeft -> "left"
RGEMemberRole _ p r -> "changed role of " <> profileToText p <> " to " <> safeDecodeUtf8 (strEncode r)
RGEMemberBlocked _ p blocked -> (if blocked then "blocked" else "unblocked") <> " " <> profileToText p
@@ -336,7 +328,6 @@ rcvGroupEventToText = \case
RGEInvitedViaGroupLink -> "invited via your group link"
RGEMemberCreatedContact -> "started direct connection with you"
RGEMemberProfileUpdated {} -> "updated profile"
- RGENewMemberPendingReview -> "new member wants to join the group"
sndGroupEventToText :: SndGroupEvent -> Text
sndGroupEventToText = \case
@@ -346,18 +337,6 @@ sndGroupEventToText = \case
SGEMemberDeleted _ p -> "removed " <> profileToText p
SGEUserLeft -> "left"
SGEGroupUpdated _ -> "group profile updated"
- SGEMemberAccepted _ _p -> "you accepted this member"
- SGEUserPendingReview -> "please wait for group moderators to review your request to join the group"
-
--- used to send to members with old version
-pendingReviewMessage :: Text
-pendingReviewMessage =
- "Please wait for group moderators to review your request to join the group."
-
--- used to send to members with old version
-acceptedToGroupMessage :: Text
-acceptedToGroupMessage =
- "You are accepted to the group."
rcvConnEventToText :: RcvConnEvent -> Text
rcvConnEventToText = \case
diff --git a/src/Simplex/Chat/Messages/CIContent/Events.hs b/src/Simplex/Chat/Messages/CIContent/Events.hs
index 539c1f524c..054530e06f 100644
--- a/src/Simplex/Chat/Messages/CIContent/Events.hs
+++ b/src/Simplex/Chat/Messages/CIContent/Events.hs
@@ -14,9 +14,7 @@ import Simplex.Messaging.Crypto.Ratchet (PQEncryption)
data RcvGroupEvent
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
- | RGEMemberConnected -- CEvtUserJoinedGroup, CRJoinedGroupMember, CEvtConnectedToGroupMember
- | RGEMemberAccepted {groupMemberId :: GroupMemberId, profile :: Profile}
- | RGEUserAccepted
+ | RGEMemberConnected -- CRUserJoinedGroup, CRJoinedGroupMember, CRConnectedToGroupMember
| RGEMemberLeft -- CRLeftMember
| RGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole}
| RGEMemberBlocked {groupMemberId :: GroupMemberId, profile :: Profile, blocked :: Bool} -- CRMemberBlockedForAll
@@ -31,7 +29,6 @@ data RcvGroupEvent
| RGEInvitedViaGroupLink -- CRSentGroupInvitationViaLink
| RGEMemberCreatedContact -- CRNewMemberContactReceivedInv
| RGEMemberProfileUpdated {fromProfile :: Profile, toProfile :: Profile} -- CRGroupMemberUpdated
- | RGENewMemberPendingReview
deriving (Show)
data SndGroupEvent
@@ -41,8 +38,6 @@ data SndGroupEvent
| SGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRUserDeletedMembers
| SGEUserLeft -- CRLeftMemberUser
| SGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
- | SGEMemberAccepted {groupMemberId :: GroupMemberId, profile :: Profile}
- | SGEUserPendingReview
deriving (Show)
data RcvConnEvent
diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs
index 151b1b0d27..2281c1aefa 100644
--- a/src/Simplex/Chat/Protocol.hs
+++ b/src/Simplex/Chat/Protocol.hs
@@ -77,13 +77,12 @@ import Simplex.Messaging.Version hiding (version)
-- 11 - fix profile update in business chats (2024-12-05)
-- 12 - support sending and receiving content reports (2025-01-03)
-- 14 - support sending and receiving group join rejection (2025-02-24)
--- 15 - support specifying message scopes for group messages (2025-03-12)
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
-- This indirection is needed for backward/forward compatibility testing.
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
currentChatVersion :: VersionChat
-currentChatVersion = VersionChat 15
+currentChatVersion = VersionChat 14
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
supportedChatVRange :: VersionRangeChat
@@ -138,10 +137,6 @@ contentReportsVersion = VersionChat 12
groupJoinRejectVersion :: VersionChat
groupJoinRejectVersion = VersionChat 14
--- support group knocking (MsgScope)
-groupKnockingVersion :: VersionChat
-groupKnockingVersion = VersionChat 15
-
agentToChatVersion :: VersionSMPA -> VersionChat
agentToChatVersion v
| v < pqdrSMPAgentVersion = initialChatVersion
@@ -248,12 +243,6 @@ instance ToJSON SharedMsgId where
toJSON = strToJSON
toEncoding = strToJEncoding
-data MsgScope
- = MSMember {memberId :: MemberId} -- Admins can use any member id; members can use only their own id
- deriving (Eq, Show)
-
-$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "MS") ''MsgScope)
-
$(JQ.deriveJSON defaultJSON ''AppMessageJson)
data MsgRef = MsgRef
@@ -330,7 +319,7 @@ data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMess
data ChatMsgEvent (e :: MsgEncoding) where
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json
- XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool, scope :: Maybe MsgScope} -> ChatMsgEvent 'Json
+ XMsgUpdate :: {msgId :: SharedMsgId, content :: MsgContent, mentions :: Map MemberName MsgMention, ttl :: Maybe Int, live :: Maybe Bool} -> ChatMsgEvent 'Json
XMsgDel :: SharedMsgId -> Maybe MemberId -> ChatMsgEvent 'Json
XMsgDeleted :: ChatMsgEvent 'Json
XMsgReact :: {msgId :: SharedMsgId, memberId :: Maybe MemberId, reaction :: MsgReaction, add :: Bool} -> ChatMsgEvent 'Json
@@ -346,8 +335,8 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpLinkInv :: GroupLinkInvitation -> ChatMsgEvent 'Json
XGrpLinkReject :: GroupLinkRejection -> ChatMsgEvent 'Json
XGrpLinkMem :: Profile -> ChatMsgEvent 'Json
- XGrpLinkAcpt :: GroupAcceptance -> GroupMemberRole -> MemberId -> ChatMsgEvent 'Json
- XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
+ XGrpLinkAcpt :: GroupMemberRole -> ChatMsgEvent 'Json
+ XGrpMemNew :: MemberInfo -> ChatMsgEvent 'Json
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json
XGrpMemFwd :: MemberInfo -> IntroInvitation -> ChatMsgEvent 'Json
@@ -361,7 +350,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpDel :: ChatMsgEvent 'Json
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json
- XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> Maybe MsgScope -> ChatMsgEvent 'Json
+ XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> ChatMsgEvent 'Json
XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
XInfoProbe :: Probe -> ChatMsgEvent 'Json
XInfoProbeCheck :: ProbeHash -> ChatMsgEvent 'Json
@@ -394,7 +383,7 @@ isForwardedGroupMsg ev = case ev of
XMsgReact {} -> True
XFileCancel _ -> True
XInfo _ -> True
- XGrpMemNew {} -> True
+ XGrpMemNew _ -> True
XGrpMemRole {} -> True
XGrpMemRestrict {} -> True
XGrpMemDel {} -> True -- TODO there should be a special logic when deleting host member (e.g., host forwards it before deleting connections)
@@ -651,8 +640,7 @@ data ExtMsgContent = ExtMsgContent
mentions :: Map MemberName MsgMention,
file :: Maybe FileInvitation,
ttl :: Maybe Int,
- live :: Maybe Bool,
- scope :: Maybe MsgScope
+ live :: Maybe Bool
}
deriving (Eq, Show)
@@ -732,11 +720,10 @@ parseMsgContainer v =
ttl <- v .:? "ttl"
live <- v .:? "live"
mentions <- fromMaybe M.empty <$> (v .:? "mentions")
- scope <- v .:? "scope"
- pure ExtMsgContent {content, mentions, file, ttl, live, scope}
+ pure ExtMsgContent {content, mentions, file, ttl, live}
extMsgContent :: MsgContent -> Maybe FileInvitation -> ExtMsgContent
-extMsgContent mc file = ExtMsgContent mc M.empty file Nothing Nothing Nothing
+extMsgContent mc file = ExtMsgContent mc M.empty file Nothing Nothing
justTrue :: Bool -> Maybe Bool
justTrue True = Just True
@@ -785,8 +772,8 @@ msgContainerJSON = \case
MCSimple mc -> o $ msgContent mc
where
o = JM.fromList
- msgContent ExtMsgContent {content, mentions, file, ttl, live, scope} =
- ("file" .=? file) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("mentions" .=? nonEmptyMap mentions) $ ("scope" .=? scope) ["content" .= content]
+ msgContent ExtMsgContent {content, mentions, file, ttl, live} =
+ ("file" .=? file) $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("mentions" .=? nonEmptyMap mentions) ["content" .= content]
nonEmptyMap :: Map k v -> Maybe (Map k v)
nonEmptyMap m = if M.null m then Nothing else Just m
@@ -996,8 +983,8 @@ toCMEventTag msg = case msg of
XGrpLinkInv _ -> XGrpLinkInv_
XGrpLinkReject _ -> XGrpLinkReject_
XGrpLinkMem _ -> XGrpLinkMem_
- XGrpLinkAcpt {} -> XGrpLinkAcpt_
- XGrpMemNew {} -> XGrpMemNew_
+ XGrpLinkAcpt _ -> XGrpLinkAcpt_
+ XGrpMemNew _ -> XGrpMemNew_
XGrpMemIntro _ _ -> XGrpMemIntro_
XGrpMemInv _ _ -> XGrpMemInv_
XGrpMemFwd _ _ -> XGrpMemFwd_
@@ -1011,7 +998,7 @@ toCMEventTag msg = case msg of
XGrpDel -> XGrpDel_
XGrpInfo _ -> XGrpInfo_
XGrpPrefs _ -> XGrpPrefs_
- XGrpDirectInv {} -> XGrpDirectInv_
+ XGrpDirectInv _ _ -> XGrpDirectInv_
XGrpMsgForward {} -> XGrpMsgForward_
XInfoProbe _ -> XInfoProbe_
XInfoProbeCheck _ -> XInfoProbeCheck_
@@ -1083,14 +1070,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
msg = \case
XMsgNew_ -> XMsgNew <$> JT.parseEither parseMsgContainer params
XMsgFileDescr_ -> XMsgFileDescr <$> p "msgId" <*> p "fileDescr"
- XMsgUpdate_ -> do
- msgId' <- p "msgId"
- content <- p "content"
- mentions <- fromMaybe M.empty <$> opt "mentions"
- ttl <- opt "ttl"
- live <- opt "live"
- scope <- opt "scope"
- pure XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope}
+ XMsgUpdate_ -> XMsgUpdate <$> p "msgId" <*> p "content" <*> (fromMaybe M.empty <$> opt "mentions") <*> opt "ttl" <*> opt "live"
XMsgDel_ -> XMsgDel <$> p "msgId" <*> opt "memberId"
XMsgDeleted_ -> pure XMsgDeleted
XMsgReact_ -> XMsgReact <$> p "msgId" <*> opt "memberId" <*> p "reaction" <*> p "add"
@@ -1106,8 +1086,8 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
XGrpLinkInv_ -> XGrpLinkInv <$> p "groupLinkInvitation"
XGrpLinkReject_ -> XGrpLinkReject <$> p "groupLinkRejection"
XGrpLinkMem_ -> XGrpLinkMem <$> p "profile"
- XGrpLinkAcpt_ -> XGrpLinkAcpt <$> p "acceptance" <*> p "role" <*> p "memberId"
- XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope"
+ XGrpLinkAcpt_ -> XGrpLinkAcpt <$> p "role"
+ XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo"
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions"
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
XGrpMemFwd_ -> XGrpMemFwd <$> p "memberInfo" <*> p "memberIntro"
@@ -1121,7 +1101,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
XGrpDel_ -> pure XGrpDel
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences"
- XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content" <*> opt "scope"
+ XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content"
XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
XInfoProbe_ -> XInfoProbe <$> p "probe"
XInfoProbeCheck_ -> XInfoProbeCheck <$> p "probeHash"
@@ -1154,7 +1134,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
params = \case
XMsgNew container -> msgContainerJSON container
XMsgFileDescr msgId' fileDescr -> o ["msgId" .= msgId', "fileDescr" .= fileDescr]
- XMsgUpdate {msgId = msgId', content, mentions, ttl, live, scope} -> o $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("scope" .=? scope) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
+ XMsgUpdate msgId' content mentions ttl live -> o $ ("ttl" .=? ttl) $ ("live" .=? live) $ ("mentions" .=? nonEmptyMap mentions) ["msgId" .= msgId', "content" .= content]
XMsgDel msgId' memberId -> o $ ("memberId" .=? memberId) ["msgId" .= msgId']
XMsgDeleted -> JM.empty
XMsgReact msgId' memberId reaction add -> o $ ("memberId" .=? memberId) ["msgId" .= msgId', "reaction" .= reaction, "add" .= add]
@@ -1170,8 +1150,8 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
XGrpLinkInv groupLinkInv -> o ["groupLinkInvitation" .= groupLinkInv]
XGrpLinkReject groupLinkRjct -> o ["groupLinkRejection" .= groupLinkRjct]
XGrpLinkMem profile -> o ["profile" .= profile]
- XGrpLinkAcpt acceptance role memberId -> o ["acceptance" .= acceptance, "role" .= role, "memberId" .= memberId]
- XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo]
+ XGrpLinkAcpt role -> o ["role" .= role]
+ XGrpMemNew memInfo -> o ["memberInfo" .= memInfo]
XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo]
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
XGrpMemFwd memInfo memIntro -> o ["memberInfo" .= memInfo, "memberIntro" .= memIntro]
@@ -1185,7 +1165,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
XGrpDel -> JM.empty
XGrpInfo p -> o ["groupProfile" .= p]
XGrpPrefs p -> o ["groupPreferences" .= p]
- XGrpDirectInv connReq content scope -> o $ ("content" .=? content) $ ("scope" .=? scope) ["connReq" .= connReq]
+ XGrpDirectInv connReq content -> o $ ("content" .=? content) ["connReq" .= connReq]
XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
XInfoProbe probe -> o ["probe" .= probe]
XInfoProbeCheck probeHash -> o ["probeHash" .= probeHash]
diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs
index f6c94badfe..d41957bbac 100644
--- a/src/Simplex/Chat/Remote.hs
+++ b/src/Simplex/Chat/Remote.hs
@@ -75,11 +75,11 @@ remoteFilesFolder = "simplex_v1_files"
-- when acting as host
minRemoteCtrlVersion :: AppVersion
-minRemoteCtrlVersion = AppVersion [6, 4, 0, 2]
+minRemoteCtrlVersion = AppVersion [6, 3, 4, 1]
-- when acting as controller
minRemoteHostVersion :: AppVersion
-minRemoteHostVersion = AppVersion [6, 4, 0, 2]
+minRemoteHostVersion = AppVersion [6, 3, 4, 1]
currentAppVersion :: AppVersion
currentAppVersion = AppVersion SC.version
diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs
index b69f6f646c..5c177969b9 100644
--- a/src/Simplex/Chat/Store/Connections.hs
+++ b/src/Simplex/Chat/Store/Connections.hs
@@ -137,20 +137,17 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
-- from GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
- m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
+ m.created_at, m.updated_at
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN groups g ON g.group_id = m.group_id
diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs
index 4de832a8b1..9318f62f76 100644
--- a/src/Simplex/Chat/Store/Direct.hs
+++ b/src/Simplex/Chat/Store/Direct.hs
@@ -66,7 +66,6 @@ module Simplex.Chat.Store.Direct
updateContactAccepted,
getUserByContactRequestId,
getPendingContactConnections,
- updatePCCUser,
getContactConnections,
getConnectionById,
getConnectionsContacts,
@@ -440,19 +439,6 @@ updatePCCIncognito db User {userId} conn customUserProfileId = do
(customUserProfileId, updatedAt, userId, pccConnId conn)
pure (conn :: PendingContactConnection) {customUserProfileId, updatedAt}
-updatePCCUser :: DB.Connection -> UserId -> PendingContactConnection -> UserId -> IO PendingContactConnection
-updatePCCUser db userId conn newUserId = do
- updatedAt <- getCurrentTime
- DB.execute
- db
- [sql|
- UPDATE connections
- SET user_id = ?, custom_user_profile_id = NULL, updated_at = ?
- WHERE user_id = ? AND connection_id = ?
- |]
- (newUserId, updatedAt, userId, pccConnId conn)
- pure (conn :: PendingContactConnection) {customUserProfileId = Nothing, updatedAt}
-
deletePCCIncognitoProfile :: DB.Connection -> User -> ProfileId -> IO ()
deletePCCIncognitoProfile db User {userId} profileId =
DB.execute
diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs
index 81dbb4ca54..95e169e400 100644
--- a/src/Simplex/Chat/Store/Files.hs
+++ b/src/Simplex/Chat/Store/Files.hs
@@ -371,9 +371,9 @@ getXFTPRcvFileDBIds db aRcvFileId =
toFileRef :: (FileTransferId, Maybe Int64, Maybe Int64, Maybe Int64) -> Either StoreError (Maybe ChatRef, FileTransferId)
toFileRef = \case
- (fileId, Just contactId, Nothing, Nothing) -> Right (Just $ ChatRef CTDirect contactId Nothing, fileId)
- (fileId, Nothing, Just groupId, Nothing) -> Right (Just $ ChatRef CTGroup groupId Nothing, fileId)
- (fileId, Nothing, Nothing, Just folderId) -> Right (Just $ ChatRef CTLocal folderId Nothing, fileId)
+ (fileId, Just contactId, Nothing, Nothing) -> Right (Just $ ChatRef CTDirect contactId, fileId)
+ (fileId, Nothing, Just groupId, Nothing) -> Right (Just $ ChatRef CTGroup groupId, fileId)
+ (fileId, Nothing, Nothing, Just folderId) -> Right (Just $ ChatRef CTLocal folderId, fileId)
(fileId, _, _, _) -> Right (Nothing, fileId)
updateFileCancelled :: MsgDirectionI d => DB.Connection -> User -> Int64 -> CIFileStatus d -> IO ()
@@ -444,8 +444,8 @@ getChatRefByFileId db user fileId = liftIO (lookupChatRefByFileId db user fileId
lookupChatRefByFileId :: DB.Connection -> User -> Int64 -> IO (Maybe ChatRef)
lookupChatRefByFileId db User {userId} fileId =
getChatRef <&> \case
- [(Just contactId, Nothing)] -> Just $ ChatRef CTDirect contactId Nothing
- [(Nothing, Just groupId)] -> Just $ ChatRef CTGroup groupId Nothing
+ [(Just contactId, Nothing)] -> Just $ ChatRef CTDirect contactId
+ [(Nothing, Just groupId)] -> Just $ ChatRef CTGroup groupId
_ -> Nothing
where
getChatRef =
diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs
index 6c66ea0e64..fc23c9ef44 100644
--- a/src/Simplex/Chat/Store/Groups.hs
+++ b/src/Simplex/Chat/Store/Groups.hs
@@ -80,10 +80,6 @@ module Simplex.Chat.Store.Groups
updateGroupMemberStatus,
updateGroupMemberStatusById,
updateGroupMemberAccepted,
- deleteGroupMemberSupportChat,
- updateGroupMembersRequireAttention,
- decreaseGroupMembersRequireAttention,
- increaseGroupMembersRequireAttention,
createNewGroupMember,
checkGroupMemberHasItems,
deleteGroupMember,
@@ -93,7 +89,6 @@ module Simplex.Chat.Store.Groups
updateIntroStatus,
saveIntroInvitation,
getIntroduction,
- getIntroducedGroupMemberIds,
getForwardIntroducedMembers,
getForwardInvitedMembers,
createIntroReMember,
@@ -181,11 +176,11 @@ import Database.SQLite.Simple (Only (..), Query, (:.) (..))
import Database.SQLite.Simple.QQ (sql)
#endif
-type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime) :. (Maybe UTCTime, Maybe Int64, Maybe Int64, Maybe Int64, Maybe UTCTime)
+type MaybeGroupMemberRow = (Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnLinkContact, Maybe LocalAlias, Maybe Preferences) :. (Maybe UTCTime, Maybe UTCTime)
toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember
-toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt) :. (supportChatTs, Just supportChatUnread, Just supportChatUnanswered, Just supportChatMentions, supportChatLastMsgFromMemberTs)) =
- Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt) :. (supportChatTs, supportChatUnread, supportChatUnanswered, supportChatMentions, supportChatLastMsgFromMemberTs))
+toMaybeGroupMember userContactId ((Just groupMemberId, Just groupId, Just memberId, Just minVer, Just maxVer, Just memberRole, Just memberCategory, Just memberStatus, Just showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, Just localDisplayName, memberContactId, Just memberContactProfileId, Just profileId, Just displayName, Just fullName, image, contactLink, Just localAlias, contactPreferences) :. (Just createdAt, Just updatedAt)) =
+ Just $ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, showMessages, memberBlocked) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, contactPreferences) :. (createdAt, updatedAt))
toMaybeGroupMember _ _ = Nothing
createGroupLink :: DB.Connection -> User -> GroupInfo -> ConnId -> CreatedLinkContact -> GroupLinkId -> GroupMemberRole -> SubscriptionMode -> ExceptT StoreError IO ()
@@ -283,20 +278,17 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
-- from GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -367,8 +359,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
- customData = Nothing,
- membersRequireAttention = 0
+ customData = Nothing
}
-- | creates a new group record for the group the current user was invited to, or returns an existing one
@@ -381,9 +372,9 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
gInfo@GroupInfo {membership, groupProfile = p'} <- getGroupInfo db vr user gId
hostId <- getHostMemberId_ db user gId
let GroupMember {groupMemberId, memberId, memberRole} = membership
- MemberIdRole {memberId = invMemberId, memberRole = memberRole'} = invitedMember
- liftIO . when (memberId /= invMemberId || memberRole /= memberRole') $
- DB.execute db "UPDATE group_members SET member_id = ?, member_role = ? WHERE group_member_id = ?" (invMemberId, memberRole', groupMemberId)
+ MemberIdRole {memberId = memberId', memberRole = memberRole'} = invitedMember
+ liftIO . when (memberId /= memberId' || memberRole /= memberRole') $
+ DB.execute db "UPDATE group_members SET member_id = ?, member_role = ? WHERE group_member_id = ?" (memberId', memberRole', groupMemberId)
gInfo' <-
if p' == groupProfile
then pure gInfo
@@ -438,8 +429,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
chatTags = [],
chatItemTTL = Nothing,
uiThemes = Nothing,
- customData = Nothing,
- membersRequireAttention = 0
+ customData = Nothing
},
groupMemberId
)
@@ -486,8 +476,7 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe
activeConn = Nothing,
memberChatVRange,
createdAt,
- updatedAt = createdAt,
- supportChat = Nothing
+ updatedAt = createdAt
}
where
memberChatVRange@(VersionRange minV maxV) = vr
@@ -535,7 +524,7 @@ deleteContactCardKeepConn db connId Contact {contactId, profile = LocalProfile {
createGroupInvitedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember)
createGroupInvitedViaLink db vr user conn GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, accepted, business} = do
let fromMemberProfile = profileFromName fromMemberName
- initialStatus = maybe GSMemAccepted (acceptanceToStatus $ memberAdmission groupProfile) accepted
+ initialStatus = maybe GSMemAccepted acceptanceToStatus accepted
createGroupViaLink' db vr user conn fromMember fromMemberProfile invitedMember groupProfile business initialStatus
createGroupRejectedViaLink :: DB.Connection -> VersionRangeChat -> User -> Connection -> GroupLinkRejection -> ExceptT StoreError IO (GroupInfo, GroupMember)
@@ -554,13 +543,13 @@ createGroupViaLink'
invitedMember
groupProfile
business
- membershipStatus = do
+ memStatus = do
currentTs <- liftIO getCurrentTime
groupId <- insertGroup_ currentTs
hostMemberId <- insertHost_ currentTs groupId
liftIO $ DB.execute db "UPDATE connections SET conn_type = ?, group_member_id = ?, updated_at = ? WHERE connection_id = ?" (ConnMember, hostMemberId, currentTs, connId)
-- using IBUnknown since host is created without contact
- void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember membershipStatus IBUnknown customUserProfileId currentTs vr
+ void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember memStatus IBUnknown customUserProfileId currentTs vr
liftIO $ setViaGroupLinkHash db groupId connId
(,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId
where
@@ -595,7 +584,7 @@ createGroupViaLink'
user_id, local_display_name, contact_id, contact_profile_id, created_at, updated_at)
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ( (groupId, memberId, memberRole, GCHostMember, GSMemAccepted, fromInvitedBy userContactId IBUnknown)
+ ( (groupId, memberId, memberRole, GCHostMember, memStatus, fromInvitedBy userContactId IBUnknown)
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
)
insertedRowId db
@@ -775,12 +764,10 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = do
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
- mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
+ mu.created_at, mu.updated_at
FROM groups g
JOIN group_profiles gp USING (group_profile_id)
JOIN group_members mu USING (group_id)
@@ -845,7 +832,6 @@ groupMemberQuery =
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -1015,8 +1001,7 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId,
activeConn = Nothing,
memberChatVRange = peerChatVRange,
createdAt,
- updatedAt = createdAt,
- supportChat = Nothing
+ updatedAt = createdAt
}
where
insertMember_ =
@@ -1219,8 +1204,8 @@ updateGroupMemberStatusById db userId groupMemberId memStatus = do
|]
(memStatus, currentTs, userId, groupMemberId)
-updateGroupMemberAccepted :: DB.Connection -> User -> GroupMember -> GroupMemberStatus -> GroupMemberRole -> IO GroupMember
-updateGroupMemberAccepted db User {userId} m@GroupMember {groupMemberId} status role = do
+updateGroupMemberAccepted :: DB.Connection -> User -> GroupMember -> GroupMemberRole -> IO GroupMember
+updateGroupMemberAccepted db User {userId} m@GroupMember {groupMemberId} role = do
currentTs <- getCurrentTime
DB.execute
db
@@ -1229,73 +1214,8 @@ updateGroupMemberAccepted db User {userId} m@GroupMember {groupMemberId} status
SET member_status = ?, member_role = ?, updated_at = ?
WHERE user_id = ? AND group_member_id = ?
|]
- (status, role, currentTs, userId, groupMemberId)
- pure m {memberStatus = status, memberRole = role, updatedAt = currentTs}
-
-deleteGroupMemberSupportChat :: DB.Connection -> User -> GroupInfo -> GroupMember -> IO (GroupInfo, GroupMember)
-deleteGroupMemberSupportChat db user g m@GroupMember {groupMemberId} = do
- let requiredAttention = gmRequiresAttention m
- currentTs <- getCurrentTime
- DB.execute
- db
- [sql|
- DELETE FROM chat_items
- WHERE group_scope_group_member_id = ?
- |]
- (Only groupMemberId)
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_ts = NULL,
- support_chat_items_unread = 0,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = 0,
- support_chat_last_msg_from_member_ts = NULL,
- updated_at = ?
- WHERE group_member_id = ?
- |]
- (currentTs, groupMemberId)
- let m' = m {supportChat = Nothing, updatedAt = currentTs}
- g' <- if requiredAttention
- then decreaseGroupMembersRequireAttention db user g
- else pure g
- pure (g', m')
-
-updateGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> IO GroupInfo
-updateGroupMembersRequireAttention db user g member member'
- | nowRequires && not didRequire =
- increaseGroupMembersRequireAttention db user g
- | not nowRequires && didRequire =
- decreaseGroupMembersRequireAttention db user g
- | otherwise = pure g
- where
- didRequire = gmRequiresAttention member
- nowRequires = gmRequiresAttention member'
-
-decreaseGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> IO GroupInfo
-decreaseGroupMembersRequireAttention db User {userId} g@GroupInfo {groupId, membersRequireAttention} = do
- DB.execute
- db
- [sql|
- UPDATE groups
- SET members_require_attention = members_require_attention - 1
- WHERE user_id = ? AND group_id = ?
- |]
- (userId, groupId)
- pure g {membersRequireAttention = membersRequireAttention - 1}
-
-increaseGroupMembersRequireAttention :: DB.Connection -> User -> GroupInfo -> IO GroupInfo
-increaseGroupMembersRequireAttention db User {userId} g@GroupInfo {groupId, membersRequireAttention} = do
- DB.execute
- db
- [sql|
- UPDATE groups
- SET members_require_attention = members_require_attention + 1
- WHERE user_id = ? AND group_id = ?
- |]
- (userId, groupId)
- pure g {membersRequireAttention = membersRequireAttention + 1}
+ (GSMemConnected, role, currentTs, userId, groupMemberId)
+ pure m {memberStatus = GSMemConnected, memberRole = role, updatedAt = currentTs}
-- | add new member with profile
createNewGroupMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> GroupMemberCategory -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
@@ -1379,8 +1299,7 @@ createNewMember_
activeConn,
memberChatVRange,
createdAt,
- updatedAt = createdAt,
- supportChat = Nothing
+ updatedAt = createdAt
}
checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId)
@@ -1478,8 +1397,8 @@ saveIntroInvitation db reMember toMember introInv@IntroInvitation {groupConnReq}
(GMIntroInvReceived, groupConnReq, directConnReq introInv, currentTs, introId intro)
pure intro {introInvitation = Just introInv, introStatus = GMIntroInvReceived}
-saveMemberInvitation :: DB.Connection -> GroupMember -> IntroInvitation -> GroupMemberStatus -> IO ()
-saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnReq, directConnReq} newMemberStatus = do
+saveMemberInvitation :: DB.Connection -> GroupMember -> IntroInvitation -> IO ()
+saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnReq, directConnReq} = do
currentTs <- getCurrentTime
DB.execute
db
@@ -1491,7 +1410,7 @@ saveMemberInvitation db GroupMember {groupMemberId} IntroInvitation {groupConnRe
updated_at = ?
WHERE group_member_id = ?
|]
- (newMemberStatus, groupConnReq, directConnReq, currentTs, groupMemberId)
+ (GSMemIntroInvited, groupConnReq, directConnReq, currentTs, groupMemberId)
getIntroduction :: DB.Connection -> GroupMember -> GroupMember -> ExceptT StoreError IO GroupMemberIntro
getIntroduction db reMember toMember = ExceptT $ do
@@ -1511,14 +1430,6 @@ getIntroduction db reMember toMember = ExceptT $ do
in Right GroupMemberIntro {introId, reMember, toMember, introStatus, introInvitation}
toIntro _ = Left SEIntroNotFound
-getIntroducedGroupMemberIds :: DB.Connection -> GroupMember -> IO [GroupMemberId]
-getIntroducedGroupMemberIds db invitee =
- map fromOnly <$>
- DB.query
- db
- "SELECT re_group_member_id FROM group_member_intros WHERE to_group_member_id = ?"
- (Only $ groupMemberId' invitee)
-
getForwardIntroducedMembers :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> Bool -> IO [GroupMember]
getForwardIntroducedMembers db vr user invitee highlyAvailable = do
memberIds <- map fromOnly <$> query
@@ -1634,20 +1545,17 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = do
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
-- via GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -2516,8 +2424,8 @@ createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {g
where
VersionRange minV maxV = vr
-updateUnknownMemberAnnounced :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> GroupMember -> MemberInfo -> GroupMemberStatus -> ExceptT StoreError IO GroupMember
-updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} status = do
+updateUnknownMemberAnnounced :: DB.Connection -> VersionRangeChat -> User -> GroupMember -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember
+updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do
_ <- updateMemberProfile db user unknownMember profile
currentTs <- liftIO getCurrentTime
liftIO $
@@ -2534,7 +2442,7 @@ updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMemb
updated_at = ?
WHERE user_id = ? AND group_member_id = ?
|]
- ( (memberRole, GCPostMember, status, groupMemberId' invitingMember)
+ ( (memberRole, GCPostMember, GSMemAnnounced, groupMemberId' invitingMember)
:. (minV, maxV, currentTs, userId, groupMemberId)
)
getGroupMemberById db vr user groupMemberId
diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs
index a9bd03ed0e..752a4a2c6d 100644
--- a/src/Simplex/Chat/Store/Messages.hs
+++ b/src/Simplex/Chat/Store/Messages.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
@@ -11,7 +10,6 @@
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
@@ -35,9 +33,7 @@ module Simplex.Chat.Store.Messages
getPendingGroupMessages,
deletePendingGroupMessage,
deleteOldMessages,
- MemberAttention (..),
- updateChatTsStats,
- setSupportChatTs,
+ updateChatTs,
createNewSndChatItem,
createNewRcvChatItem,
createNewChatItemNoMsg,
@@ -45,7 +41,6 @@ module Simplex.Chat.Store.Messages
getChatPreviews,
getDirectChat,
getGroupChat,
- getGroupChatScopeInfoForItem,
getLocalChat,
getDirectChatItemLast,
getAllChatItems,
@@ -80,7 +75,6 @@ module Simplex.Chat.Store.Messages
updateGroupChatItemsRead,
getGroupUnreadTimedItems,
updateGroupChatItemsReadList,
- updateGroupScopeUnreadStats,
setGroupChatItemsDeleteAt,
updateLocalChatItemsRead,
getChatRefViaItemId,
@@ -146,7 +140,7 @@ import Data.Bifunctor (first)
import Data.ByteString.Char8 (ByteString)
import Data.Either (fromRight, rights)
import Data.Int (Int64)
-import Data.List (foldl', sortBy)
+import Data.List (sortBy)
import Data.List.NonEmpty (NonEmpty)
import qualified Data.List.NonEmpty as L
import Data.Map.Strict (Map)
@@ -177,7 +171,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..))
import Simplex.Messaging.Util (eitherToMaybe)
import UnliftIO.STM
#if defined(dbPostgres)
-import Database.PostgreSQL.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
+import Database.PostgreSQL.Simple (FromRow, In (..), Only (..), Query, ToRow, (:.) (..))
import Database.PostgreSQL.Simple.SqlQQ (sql)
#else
import Database.SQLite.Simple (FromRow, Only (..), Query, ToRow, (:.) (..))
@@ -367,136 +361,28 @@ deleteOldMessages db createdAtCutoff = do
type NewQuoteRow = (Maybe SharedMsgId, Maybe UTCTime, Maybe MsgContent, Maybe Bool, Maybe MemberId)
--- For support chats with members we track unanswered count - number of messages from the member
--- that weren't followed up by a message from any of moderators.
-data MemberAttention
- -- Message was received from member, increase unanswered counter and set support_chat_last_msg_from_member_ts.
- -- `MAInc 0 Nothing` is used in two cases:
- -- - when message from moderator is older than the last message from member (support_chat_last_msg_from_member_ts);
- -- - for user's chat with moderators, where unanswered count is not tracked.
- = MAInc Int (Maybe UTCTime)
- -- Message was received from moderator, reset unanswered counter.
- | MAReset
- deriving (Show)
-
-updateChatTsStats :: DB.Connection -> VersionRangeChat -> User -> ChatDirection c d -> UTCTime -> Maybe (Int, MemberAttention, Int) -> IO (ChatInfo c)
-updateChatTsStats db vr user@User {userId} chatDirection chatTs chatStats_ = case toChatInfo chatDirection of
- DirectChat ct@Contact {contactId} -> do
+updateChatTs :: DB.Connection -> User -> ChatDirection c d -> UTCTime -> IO ()
+updateChatTs db User {userId} chatDirection chatTs = case toChatInfo chatDirection of
+ DirectChat Contact {contactId} ->
DB.execute
db
"UPDATE contacts SET chat_ts = ?, chat_deleted = 0 WHERE user_id = ? AND contact_id = ?"
(chatTs, userId, contactId)
- pure $ DirectChat ct {chatTs = Just chatTs}
- GroupChat g@GroupInfo {groupId} Nothing -> do
+ GroupChat GroupInfo {groupId} ->
DB.execute
db
"UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
(chatTs, userId, groupId)
- pure $ GroupChat g {chatTs = Just chatTs} Nothing
- GroupChat g@GroupInfo {groupId, membership, membersRequireAttention} (Just GCSIMemberSupport {groupMember_}) ->
- case groupMember_ of
- Nothing -> do
- membership' <- updateGMStats membership
- DB.execute
- db
- "UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
- (chatTs, userId, groupId)
- pure $ GroupChat g {membership = membership', chatTs = Just chatTs} (Just $ GCSIMemberSupport Nothing)
- Just member -> do
- member' <- updateGMStats member
- let didRequire = gmRequiresAttention member
- nowRequires = gmRequiresAttention member'
- if
- | nowRequires && not didRequire -> do
- DB.execute
- db
- [sql|
- UPDATE groups
- SET chat_ts = ?,
- members_require_attention = members_require_attention + 1
- WHERE user_id = ? AND group_id = ?
- |]
- (chatTs, userId, groupId)
- pure $ GroupChat g {membersRequireAttention = membersRequireAttention + 1, chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
- | not nowRequires && didRequire -> do
- DB.execute
- db
- [sql|
- UPDATE groups
- SET chat_ts = ?,
- members_require_attention = members_require_attention - 1
- WHERE user_id = ? AND group_id = ?
- |]
- (chatTs, userId, groupId)
- pure $ GroupChat g {membersRequireAttention = membersRequireAttention - 1, chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
- | otherwise -> do
- DB.execute
- db
- "UPDATE groups SET chat_ts = ? WHERE user_id = ? AND group_id = ?"
- (chatTs, userId, groupId)
- pure $ GroupChat g {chatTs = Just chatTs} (Just $ GCSIMemberSupport (Just member'))
- where
- updateGMStats m@GroupMember {groupMemberId} = do
- case chatStats_ of
- Nothing ->
- DB.execute
- db
- "UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?"
- (chatTs, groupMemberId)
- Just (unread, MAInc unanswered Nothing, mentions) ->
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = support_chat_items_member_attention + ?,
- support_chat_items_mentions = support_chat_items_mentions + ?
- WHERE group_member_id = ?
- |]
- (chatTs, unread, unanswered, mentions, groupMemberId)
- Just (unread, MAInc unanswered (Just lastMsgFromMemberTs), mentions) ->
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = support_chat_items_member_attention + ?,
- support_chat_items_mentions = support_chat_items_mentions + ?,
- support_chat_last_msg_from_member_ts = ?
- WHERE group_member_id = ?
- |]
- (chatTs, unread, unanswered, mentions, lastMsgFromMemberTs, groupMemberId)
- Just (unread, MAReset, mentions) ->
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = support_chat_items_mentions + ?
- WHERE group_member_id = ?
- |]
- (chatTs, unread, mentions, groupMemberId)
- m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
- pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
- LocalChat nf@NoteFolder {noteFolderId} -> do
+ LocalChat NoteFolder {noteFolderId} ->
DB.execute
db
"UPDATE note_folders SET chat_ts = ? WHERE user_id = ? AND note_folder_id = ?"
(chatTs, userId, noteFolderId)
- pure $ LocalChat nf {chatTs = chatTs}
- cInfo -> pure cInfo
+ _ -> pure ()
-setSupportChatTs :: DB.Connection -> GroupMemberId -> UTCTime -> IO ()
-setSupportChatTs db groupMemberId chatTs =
- DB.execute db "UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?" (chatTs, groupMemberId)
-
-createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId
-createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciContent quotedItem itemForwarded timed live createdAt =
- createNewChatItem_ db user chatDirection createdByMsgId (Just sharedMsgId) ciContent quoteRow itemForwarded timed live False createdAt Nothing createdAt
+createNewSndChatItem :: DB.Connection -> User -> ChatDirection c 'MDSnd -> Maybe NotInHistory -> SndMessage -> CIContent 'MDSnd -> Maybe (CIQuote c) -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> UTCTime -> IO ChatItemId
+createNewSndChatItem db user chatDirection notInHistory_ SndMessage {msgId, sharedMsgId} ciContent quotedItem itemForwarded timed live createdAt =
+ createNewChatItem_ db user chatDirection notInHistory_ createdByMsgId (Just sharedMsgId) ciContent quoteRow itemForwarded timed live False createdAt Nothing createdAt
where
createdByMsgId = if msgId == 0 then Nothing else Just msgId
quoteRow :: NewQuoteRow
@@ -510,9 +396,9 @@ createNewSndChatItem db user chatDirection SndMessage {msgId, sharedMsgId} ciCon
CIQGroupRcv (Just GroupMember {memberId}) -> (Just False, Just memberId)
CIQGroupRcv Nothing -> (Just False, Nothing)
-createNewRcvChatItem :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c), Maybe CIForwardedFrom)
-createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, forwardedByMember} sharedMsgId_ ciContent timed live userMention itemTs createdAt = do
- ciId <- createNewChatItem_ db user chatDirection (Just msgId) sharedMsgId_ ciContent quoteRow itemForwarded timed live userMention itemTs forwardedByMember createdAt
+createNewRcvChatItem :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirection c 'MDRcv -> Maybe NotInHistory -> RcvMessage -> Maybe SharedMsgId -> CIContent 'MDRcv -> Maybe CITimed -> Bool -> Bool -> UTCTime -> UTCTime -> IO (ChatItemId, Maybe (CIQuote c), Maybe CIForwardedFrom)
+createNewRcvChatItem db user chatDirection notInHistory_ RcvMessage {msgId, chatMsgEvent, forwardedByMember} sharedMsgId_ ciContent timed live userMention itemTs createdAt = do
+ ciId <- createNewChatItem_ db user chatDirection notInHistory_ (Just msgId) sharedMsgId_ ciContent quoteRow itemForwarded timed live userMention itemTs forwardedByMember createdAt
quotedItem <- mapM (getChatItemQuote_ db user chatDirection) quotedMsg
pure (ciId, quotedItem, itemForwarded)
where
@@ -524,24 +410,24 @@ createNewRcvChatItem db user chatDirection RcvMessage {msgId, chatMsgEvent, forw
Just QuotedMsg {msgRef = MsgRef {msgId = sharedMsgId, sentAt, sent, memberId}, content} ->
uncurry (sharedMsgId,Just sentAt,Just content,,) $ case chatDirection of
CDDirectRcv _ -> (Just $ not sent, Nothing)
- CDGroupRcv GroupInfo {membership = GroupMember {memberId = userMemberId}} _ _ ->
+ CDGroupRcv GroupInfo {membership = GroupMember {memberId = userMemberId}} _ ->
(Just $ Just userMemberId == memberId, memberId)
createNewChatItemNoMsg :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> CIContent d -> UTCTime -> UTCTime -> IO ChatItemId
createNewChatItemNoMsg db user chatDirection ciContent itemTs =
- createNewChatItem_ db user chatDirection Nothing Nothing ciContent quoteRow Nothing Nothing False False itemTs Nothing
+ createNewChatItem_ db user chatDirection Nothing Nothing Nothing ciContent quoteRow Nothing Nothing False False itemTs Nothing
where
quoteRow :: NewQuoteRow
quoteRow = (Nothing, Nothing, Nothing, Nothing, Nothing)
-createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> UTCTime -> Maybe GroupMemberId -> UTCTime -> IO ChatItemId
-createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent quoteRow itemForwarded timed live userMention itemTs forwardedByMember createdAt = do
+createNewChatItem_ :: forall c d. MsgDirectionI d => DB.Connection -> User -> ChatDirection c d -> Maybe NotInHistory -> Maybe MessageId -> Maybe SharedMsgId -> CIContent d -> NewQuoteRow -> Maybe CIForwardedFrom -> Maybe CITimed -> Bool -> Bool -> UTCTime -> Maybe GroupMemberId -> UTCTime -> IO ChatItemId
+createNewChatItem_ db User {userId} chatDirection notInHistory_ msgId_ sharedMsgId ciContent quoteRow itemForwarded timed live userMention itemTs forwardedByMember createdAt = do
DB.execute
db
[sql|
INSERT INTO chat_items (
-- user and IDs
- user_id, created_by_msg_id, contact_id, group_id, group_member_id, note_folder_id, group_scope_tag, group_scope_group_member_id,
+ user_id, created_by_msg_id, contact_id, group_id, group_member_id, note_folder_id,
-- meta
item_sent, item_ts, item_content, item_content_tag, item_text, item_status, msg_content_tag, shared_msg_id,
forwarded_by_group_member_id, include_in_history, created_at, updated_at, item_live, user_mention, timed_ttl, timed_delete_at,
@@ -549,9 +435,9 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id,
-- forwarded from
fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|]
- ((userId, msgId_) :. idsRow :. groupScopeRow :. itemRow :. quoteRow' :. forwardedFromRow)
+ ((userId, msgId_) :. idsRow :. itemRow :. quoteRow' :. forwardedFromRow)
ciId <- insertedRowId db
forM_ msgId_ $ \msgId -> insertChatItemMessage_ db ciId msgId createdAt
pure ciId
@@ -559,27 +445,18 @@ createNewChatItem_ db User {userId} chatDirection msgId_ sharedMsgId ciContent q
itemRow :: (SMsgDirection d, UTCTime, CIContent d, Text, Text, CIStatus d, Maybe MsgContentTag, Maybe SharedMsgId, Maybe GroupMemberId, BoolInt) :. (UTCTime, UTCTime, Maybe BoolInt, BoolInt) :. (Maybe Int, Maybe UTCTime)
itemRow = (msgDirection @d, itemTs, ciContent, toCIContentTag ciContent, ciContentToText ciContent, ciCreateStatus ciContent, msgContentTag <$> ciMsgContent ciContent, sharedMsgId, forwardedByMember, BI includeInHistory) :. (createdAt, createdAt, BI <$> (justTrue live), BI userMention) :. ciTimedRow timed
quoteRow' = let (a, b, c, d, e) = quoteRow in (a, b, c, BI <$> d, e)
- idsRow :: (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId, Maybe NoteFolderId)
+ idsRow :: (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64)
idsRow = case chatDirection of
CDDirectRcv Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing)
CDDirectSnd Contact {contactId} -> (Just contactId, Nothing, Nothing, Nothing)
- CDGroupRcv GroupInfo {groupId} _ GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId, Nothing)
- CDGroupSnd GroupInfo {groupId} _ -> (Nothing, Just groupId, Nothing, Nothing)
+ CDGroupRcv GroupInfo {groupId} GroupMember {groupMemberId} -> (Nothing, Just groupId, Just groupMemberId, Nothing)
+ CDGroupSnd GroupInfo {groupId} -> (Nothing, Just groupId, Nothing, Nothing)
CDLocalRcv NoteFolder {noteFolderId} -> (Nothing, Nothing, Nothing, Just noteFolderId)
CDLocalSnd NoteFolder {noteFolderId} -> (Nothing, Nothing, Nothing, Just noteFolderId)
- groupScope :: Maybe (Maybe GroupChatScopeInfo)
- groupScope = case chatDirection of
- CDGroupRcv _ scope _ -> Just scope
- CDGroupSnd _ scope -> Just scope
- _ -> Nothing
- groupScopeRow :: (Maybe GroupChatScopeTag, Maybe GroupMemberId)
- groupScopeRow = case groupScope of
- Just (Just GCSIMemberSupport {groupMember_}) -> (Just GCSTMemberSupport_, groupMemberId' <$> groupMember_)
- _ -> (Nothing, Nothing)
includeInHistory :: Bool
- includeInHistory = case groupScope of
- Just Nothing -> isJust (ciMsgContent ciContent) && ((msgContentTag <$> ciMsgContent ciContent) /= Just MCReport_)
- _ -> False
+ includeInHistory =
+ let (_, groupId_, _, _) = idsRow
+ in isJust groupId_ && isNothing notInHistory_ && isJust (ciMsgContent ciContent) && ((msgContentTag <$> ciMsgContent ciContent) /= Just MCReport_)
forwardedFromRow :: (Maybe CIForwardedFromTag, Maybe Text, Maybe MsgDirection, Maybe Int64, Maybe Int64, Maybe Int64)
forwardedFromRow = case itemForwarded of
Nothing ->
@@ -602,7 +479,7 @@ getChatItemQuote_ :: ChatTypeQuotable c => DB.Connection -> User -> ChatDirectio
getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRef = MsgRef {msgId, sentAt, sent, memberId}, content} =
case chatDirection of
CDDirectRcv Contact {contactId} -> getDirectChatItemQuote_ contactId (not sent)
- CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} _s sender@GroupMember {groupMemberId = senderGMId, memberId = senderMemberId} ->
+ CDGroupRcv GroupInfo {groupId, membership = GroupMember {memberId = userMemberId}} sender@GroupMember {groupMemberId = senderGMId, memberId = senderMemberId} ->
case memberId of
Just mId
| mId == userMemberId -> (`ciQuote` CIQGroupSnd) <$> getUserGroupChatItemId_ groupId
@@ -647,8 +524,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
- m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
+ m.created_at, m.updated_at
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN contacts c ON m.contact_id = c.contact_id
@@ -702,17 +578,15 @@ data ChatPreviewData (c :: ChatType) where
data AChatPreviewData = forall c. ChatTypeI c => ACPD (SChatType c) (ChatPreviewData c)
-type ChatStatsRow = (Int, ChatItemId, BoolInt)
+type ChatStatsRow = (Int, Int, ChatItemId, BoolInt)
toChatStats :: ChatStatsRow -> ChatStats
-toChatStats (unreadCount, minUnreadItemId, BI unreadChat) =
- ChatStats {unreadCount, unreadMentions = 0, reportsCount = 0, minUnreadItemId, unreadChat}
+toChatStats (unreadCount, reportsCount, minUnreadItemId, BI unreadChat) = ChatStats {unreadCount, unreadMentions = 0, reportsCount, minUnreadItemId, unreadChat}
type GroupStatsRow = (Int, Int, Int, ChatItemId, BoolInt)
toGroupStats :: GroupStatsRow -> ChatStats
-toGroupStats (unreadCount, unreadMentions, reportsCount, minUnreadItemId, BI unreadChat) =
- ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat}
+toGroupStats (unreadCount, unreadMentions, reportsCount, minUnreadItemId, BI unreadChat) = ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat}
findDirectChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
findDirectChatPreviews_ db User {userId} pagination clq =
@@ -734,6 +608,7 @@ findDirectChatPreviews_ db User {userId} pagination clq =
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -826,7 +701,7 @@ findGroupChatPreviews_ db User {userId} pagination clq =
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -839,7 +714,7 @@ findGroupChatPreviews_ db User {userId} pagination clq =
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -915,7 +790,7 @@ getGroupChatPreview_ db vr user (GroupChatPD _ groupId lastItemId_ stats) = do
lastItem <- case lastItemId_ of
Just lastItemId -> (: []) <$> getGroupCIWithReactions db user groupInfo lastItemId
Nothing -> pure []
- pure $ AChat SCTGroup (Chat (GroupChat groupInfo Nothing) lastItem stats)
+ pure $ AChat SCTGroup (Chat (GroupChat groupInfo) lastItem stats)
findLocalChatPreviews_ :: DB.Connection -> User -> PaginationByTime -> ChatListQuery -> IO [AChatPreviewData]
findLocalChatPreviews_ db User {userId} pagination clq =
@@ -937,6 +812,7 @@ findLocalChatPreviews_ db User {userId} pagination clq =
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -1349,101 +1225,32 @@ getContactNavInfo_ db User {userId} Contact {contactId} afterCI = do
:. (userId, contactId, ciCreatedAt afterCI, cChatItemId afterCI)
)
-getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Maybe GroupChatScope -> Maybe MsgContentTag -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
-getGroupChat db vr user groupId scope_ contentFilter pagination search_ = do
+getGroupChat :: DB.Connection -> VersionRangeChat -> User -> Int64 -> Maybe MsgContentTag -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
+getGroupChat db vr user groupId contentFilter pagination search_ = do
let search = fromMaybe "" search_
g <- getGroupInfo db vr user groupId
- scopeInfo <- mapM (getCreateGroupChatScopeInfo db vr user g) scope_
case pagination of
- CPLast count -> (,Nothing) <$> getGroupChatLast_ db user g scopeInfo contentFilter count search emptyChatStats
- CPAfter afterId count -> (,Nothing) <$> getGroupChatAfter_ db user g scopeInfo contentFilter afterId count search
- CPBefore beforeId count -> (,Nothing) <$> getGroupChatBefore_ db user g scopeInfo contentFilter beforeId count search
- CPAround aroundId count -> getGroupChatAround_ db user g scopeInfo contentFilter aroundId count search
+ CPLast count -> liftIO $ (,Nothing) <$> getGroupChatLast_ db user g contentFilter count search emptyChatStats
+ CPAfter afterId count -> (,Nothing) <$> getGroupChatAfter_ db user g contentFilter afterId count search
+ CPBefore beforeId count -> (,Nothing) <$> getGroupChatBefore_ db user g contentFilter beforeId count search
+ CPAround aroundId count -> getGroupChatAround_ db user g contentFilter aroundId count search
CPInitial count -> do
unless (null search) $ throwError $ SEInternalError "initial chat pagination doesn't support search"
- getGroupChatInitial_ db user g scopeInfo contentFilter count
+ getGroupChatInitial_ db user g contentFilter count
-getCreateGroupChatScopeInfo :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
-getCreateGroupChatScopeInfo db vr user GroupInfo {membership} = \case
- GCSMemberSupport Nothing -> do
- when (isNothing $ supportChat membership) $ do
- ts <- liftIO getCurrentTime
- liftIO $ setSupportChatTs db (groupMemberId' membership) ts
- pure $ GCSIMemberSupport {groupMember_ = Nothing}
- GCSMemberSupport (Just gmId) -> do
- m <- getGroupMemberById db vr user gmId
- when (isNothing $ supportChat m) $ do
- ts <- liftIO getCurrentTime
- liftIO $ setSupportChatTs db gmId ts
- pure GCSIMemberSupport {groupMember_ = Just m}
-
-getGroupChatScopeInfoForItem :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> ChatItemId -> ExceptT StoreError IO (Maybe GroupChatScopeInfo)
-getGroupChatScopeInfoForItem db vr user g itemId =
- getGroupChatScopeForItem_ db itemId >>= mapM (getGroupChatScopeInfo db vr user g)
-
-getGroupChatScopeInfo :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScope -> ExceptT StoreError IO GroupChatScopeInfo
-getGroupChatScopeInfo db vr user GroupInfo {membership} = \case
- GCSMemberSupport Nothing -> case supportChat membership of
- Nothing -> throwError $ SEInternalError "no moderators support chat"
- Just _supportChat -> pure $ GCSIMemberSupport {groupMember_ = Nothing}
- GCSMemberSupport (Just gmId) -> do
- m <- getGroupMemberById db vr user gmId
- case supportChat m of
- Nothing -> throwError $ SEInternalError "no support chat"
- Just _supportChat -> pure GCSIMemberSupport {groupMember_ = Just m}
-
-getGroupChatScopeForItem_ :: DB.Connection -> ChatItemId -> ExceptT StoreError IO (Maybe GroupChatScope)
-getGroupChatScopeForItem_ db itemId =
- ExceptT . firstRow toScope (SEChatItemNotFound itemId) $
- DB.query
- db
- [sql|
- SELECT group_scope_tag, group_scope_group_member_id
- FROM chat_items
- WHERE chat_item_id = ?
- |]
- (Only itemId)
- where
- toScope (scopeTag, scopeMemberId) =
- case (scopeTag, scopeMemberId) of
- (Just GCSTMemberSupport_, Just gmId) -> Just $ GCSMemberSupport gmId
- (Just GCSTMemberSupport_, Nothing) -> Just $ GCSMemberSupport Nothing
- (Nothing, Nothing) -> Nothing
- (Nothing, Just _) -> Nothing -- shouldn't happen
-
-getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTGroup)
-getGroupChatLast_ db user g scopeInfo_ contentFilter count search stats = do
- ciIds <- getGroupChatItemIDs db user g scopeInfo_ contentFilter GRLast count search
- ts <- liftIO getCurrentTime
- cis <- mapM (liftIO . safeGetGroupItem db user g ts) ciIds
- pure $ Chat (GroupChat g scopeInfo_) (reverse cis) stats
+getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> Int -> String -> ChatStats -> IO (Chat 'CTGroup)
+getGroupChatLast_ db user g contentFilter count search stats = do
+ ciIds <- getGroupChatItemIDs db user g contentFilter GRLast count search
+ ts <- getCurrentTime
+ cis <- mapM (safeGetGroupItem db user g ts) ciIds
+ pure $ Chat (GroupChat g) (reverse cis) stats
data GroupItemIDsRange = GRLast | GRAfter UTCTime ChatItemId | GRBefore UTCTime ChatItemId
-getGroupChatItemIDs :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> GroupItemIDsRange -> Int -> String -> ExceptT StoreError IO [ChatItemId]
-getGroupChatItemIDs db User {userId} GroupInfo {groupId} scopeInfo_ contentFilter range count search = case (scopeInfo_, contentFilter) of
- (Nothing, Nothing) ->
- liftIO $
- idsQuery
- (baseCond <> " AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL ")
- (userId, groupId)
- (Nothing, Just mcTag) ->
- liftIO $
- idsQuery
- (baseCond <> " AND msg_content_tag = ? ")
- (userId, groupId, mcTag)
- (Just GCSIMemberSupport {groupMember_ = Just m}, Nothing) ->
- liftIO $
- idsQuery
- (baseCond <> " AND group_scope_tag = ? AND group_scope_group_member_id = ? ")
- (userId, groupId, GCSTMemberSupport_, groupMemberId' m)
- (Just GCSIMemberSupport {groupMember_ = Nothing}, Nothing) ->
- liftIO $
- idsQuery
- (baseCond <> " AND group_scope_tag = ? AND group_scope_group_member_id IS NULL ")
- (userId, groupId, GCSTMemberSupport_)
- (Just _scope, Just _mcTag) ->
- throwError $ SEInternalError "group scope and content filter are not supported together"
+getGroupChatItemIDs :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> GroupItemIDsRange -> Int -> String -> IO [ChatItemId]
+getGroupChatItemIDs db User {userId} GroupInfo {groupId} contentFilter range count search = case contentFilter of
+ Just mcTag -> idsQuery (baseCond <> " AND msg_content_tag = ? ") (userId, groupId, mcTag)
+ Nothing -> idsQuery baseCond (userId, groupId)
where
baseQuery = " SELECT chat_item_id FROM chat_items WHERE "
baseCond = " user_id = ? AND group_id = ? "
@@ -1515,82 +1322,81 @@ getGroupMemberChatItemLast db user@User {userId} groupId groupMemberId = do
(userId, groupId, groupMemberId)
getGroupChatItem db user groupId chatItemId
-getGroupChatAfter_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
-getGroupChatAfter_ db user g@GroupInfo {groupId} scopeInfo contentFilter afterId count search = do
+getGroupChatAfter_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
+getGroupChatAfter_ db user g@GroupInfo {groupId} contentFilter afterId count search = do
afterCI <- getGroupChatItem db user groupId afterId
let range = GRAfter (chatItemTs afterCI) (cChatItemId afterCI)
- ciIds <- getGroupChatItemIDs db user g scopeInfo contentFilter range count search
+ ciIds <- liftIO $ getGroupChatItemIDs db user g contentFilter range count search
ts <- liftIO getCurrentTime
cis <- liftIO $ mapM (safeGetGroupItem db user g ts) ciIds
- pure $ Chat (GroupChat g scopeInfo) cis emptyChatStats
+ pure $ Chat (GroupChat g) cis emptyChatStats
-getGroupChatBefore_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
-getGroupChatBefore_ db user g@GroupInfo {groupId} scopeInfo contentFilter beforeId count search = do
+getGroupChatBefore_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup)
+getGroupChatBefore_ db user g@GroupInfo {groupId} contentFilter beforeId count search = do
beforeCI <- getGroupChatItem db user groupId beforeId
let range = GRBefore (chatItemTs beforeCI) (cChatItemId beforeCI)
- ciIds <- getGroupChatItemIDs db user g scopeInfo contentFilter range count search
+ ciIds <- liftIO $ getGroupChatItemIDs db user g contentFilter range count search
ts <- liftIO getCurrentTime
cis <- liftIO $ mapM (safeGetGroupItem db user g ts) ciIds
- pure $ Chat (GroupChat g scopeInfo) (reverse cis) emptyChatStats
+ pure $ Chat (GroupChat g) (reverse cis) emptyChatStats
-getGroupChatAround_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
-getGroupChatAround_ db user g scopeInfo contentFilter aroundId count search = do
- stats <- getGroupStats_ db user g scopeInfo
- getGroupChatAround' db user g scopeInfo contentFilter aroundId count search stats
+getGroupChatAround_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
+getGroupChatAround_ db user g contentFilter aroundId count search = do
+ stats <- liftIO $ getGroupStats_ db user g
+ getGroupChatAround' db user g contentFilter aroundId count search stats
-getGroupChatAround' :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
-getGroupChatAround' db user g scopeInfo contentFilter aroundId count search stats = do
+getGroupChatAround' :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> ChatItemId -> Int -> String -> ChatStats -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
+getGroupChatAround' db user g contentFilter aroundId count search stats = do
aroundCI <- getGroupCIWithReactions db user g aroundId
let beforeRange = GRBefore (chatItemTs aroundCI) (cChatItemId aroundCI)
afterRange = GRAfter (chatItemTs aroundCI) (cChatItemId aroundCI)
- beforeIds <- getGroupChatItemIDs db user g scopeInfo contentFilter beforeRange count search
- afterIds <- getGroupChatItemIDs db user g scopeInfo contentFilter afterRange count search
+ beforeIds <- liftIO $ getGroupChatItemIDs db user g contentFilter beforeRange count search
+ afterIds <- liftIO $ getGroupChatItemIDs db user g contentFilter afterRange count search
ts <- liftIO getCurrentTime
beforeCIs <- liftIO $ mapM (safeGetGroupItem db user g ts) beforeIds
afterCIs <- liftIO $ mapM (safeGetGroupItem db user g ts) afterIds
let cis = reverse beforeCIs <> [aroundCI] <> afterCIs
navInfo <- liftIO $ getNavInfo cis
- pure (Chat (GroupChat g scopeInfo) cis stats, Just navInfo)
+ pure (Chat (GroupChat g) cis stats, Just navInfo)
where
getNavInfo cis_ = case cis_ of
[] -> pure $ NavigationInfo 0 0
cis -> getGroupNavInfo_ db user g (last cis)
-getGroupChatInitial_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Int -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
-getGroupChatInitial_ db user g scopeInfo_ contentFilter count = do
- getGroupMinUnreadId_ db user g scopeInfo_ contentFilter >>= \case
+getGroupChatInitial_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> Int -> ExceptT StoreError IO (Chat 'CTGroup, Maybe NavigationInfo)
+getGroupChatInitial_ db user g contentFilter count = do
+ liftIO (getGroupMinUnreadId_ db user g contentFilter) >>= \case
Just minUnreadItemId -> do
- unreadCounts <- getGroupUnreadCount_ db user g scopeInfo_ Nothing
- stats <- liftIO $ getStats minUnreadItemId unreadCounts
- getGroupChatAround' db user g scopeInfo_ contentFilter minUnreadItemId count "" stats
- Nothing -> do
- stats <- liftIO $ getStats 0 (0, 0)
- (,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g scopeInfo_ contentFilter count "" stats
+ stats <- liftIO $ getStats minUnreadItemId =<< getGroupUnreadCount_ db user g Nothing
+ getGroupChatAround' db user g contentFilter minUnreadItemId count "" stats
+ Nothing -> liftIO $ do
+ stats <- getStats 0 (0, 0)
+ (,Just $ NavigationInfo 0 0) <$> getGroupChatLast_ db user g contentFilter count "" stats
where
getStats minUnreadItemId (unreadCount, unreadMentions) = do
reportsCount <- getGroupReportsCount_ db user g False
pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False}
-getGroupStats_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> ExceptT StoreError IO ChatStats
-getGroupStats_ db user g scopeInfo_ = do
- minUnreadItemId <- fromMaybe 0 <$> getGroupMinUnreadId_ db user g scopeInfo_ Nothing
- (unreadCount, unreadMentions) <- getGroupUnreadCount_ db user g scopeInfo_ Nothing
- reportsCount <- liftIO $ getGroupReportsCount_ db user g False
+getGroupStats_ :: DB.Connection -> User -> GroupInfo -> IO ChatStats
+getGroupStats_ db user g = do
+ minUnreadItemId <- fromMaybe 0 <$> getGroupMinUnreadId_ db user g Nothing
+ (unreadCount, unreadMentions) <- getGroupUnreadCount_ db user g Nothing
+ reportsCount <- getGroupReportsCount_ db user g False
pure ChatStats {unreadCount, unreadMentions, reportsCount, minUnreadItemId, unreadChat = False}
-getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Maybe ChatItemId)
-getGroupMinUnreadId_ db user g scopeInfo_ contentFilter =
+getGroupMinUnreadId_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> IO (Maybe ChatItemId)
+getGroupMinUnreadId_ db user g contentFilter =
fmap join . maybeFirstRow fromOnly $
- queryUnreadGroupItems db user g scopeInfo_ contentFilter baseQuery orderLimit
+ queryUnreadGroupItems db user g contentFilter baseQuery orderLimit
where
baseQuery = "SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? "
orderLimit = " ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1"
-getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> ExceptT StoreError IO (Int, Int)
-getGroupUnreadCount_ db user g scopeInfo_ contentFilter =
- head <$> queryUnreadGroupItems db user g scopeInfo_ contentFilter baseQuery ""
+getGroupUnreadCount_ :: DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> IO (Int, Int)
+getGroupUnreadCount_ db user g contentFilter =
+ head <$> queryUnreadGroupItems db user g contentFilter baseQuery ""
where
- baseQuery = "SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL "
+ baseQuery = "SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? "
getGroupReportsCount_ :: DB.Connection -> User -> GroupInfo -> Bool -> IO Int
getGroupReportsCount_ db User {userId} GroupInfo {groupId} archived =
@@ -1600,35 +1406,19 @@ getGroupReportsCount_ db User {userId} GroupInfo {groupId} archived =
"SELECT COUNT(1) FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? AND item_deleted = ? AND item_sent = 0"
(userId, groupId, MCReport_, BI archived)
-queryUnreadGroupItems :: FromRow r => DB.Connection -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> Maybe MsgContentTag -> Query -> Query -> ExceptT StoreError IO [r]
-queryUnreadGroupItems db User {userId} GroupInfo {groupId} scopeInfo_ contentFilter baseQuery orderLimit =
- case (scopeInfo_, contentFilter) of
- (Nothing, Nothing) ->
- liftIO $
- DB.query
- db
- (baseQuery <> " AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ? " <> orderLimit)
- (userId, groupId, CISRcvNew)
- (Nothing, Just mcTag) ->
- liftIO $
- DB.query
- db
- (baseQuery <> " AND msg_content_tag = ? AND item_status = ? " <> orderLimit)
- (userId, groupId, mcTag, CISRcvNew)
- (Just GCSIMemberSupport {groupMember_ = Just m}, Nothing) ->
- liftIO $
- DB.query
- db
- (baseQuery <> " AND group_scope_tag = ? AND group_scope_group_member_id = ? AND item_status = ? " <> orderLimit)
- (userId, groupId, GCSTMemberSupport_, groupMemberId' m, CISRcvNew)
- (Just GCSIMemberSupport {groupMember_ = Nothing}, Nothing) ->
- liftIO $
- DB.query
- db
- (baseQuery <> " AND group_scope_tag = ? AND group_scope_group_member_id IS NULL AND item_status = ? " <> orderLimit)
- (userId, groupId, GCSTMemberSupport_, CISRcvNew)
- (Just _scope, Just _mcTag) ->
- throwError $ SEInternalError "group scope and content filter are not supported together"
+queryUnreadGroupItems :: FromRow r => DB.Connection -> User -> GroupInfo -> Maybe MsgContentTag -> Query -> Query -> IO [r]
+queryUnreadGroupItems db User {userId} GroupInfo {groupId} contentFilter baseQuery orderLimit =
+ case contentFilter of
+ Just mcTag ->
+ DB.query
+ db
+ (baseQuery <> " AND msg_content_tag = ? AND item_status = ? " <> orderLimit)
+ (userId, groupId, mcTag, CISRcvNew)
+ Nothing ->
+ DB.query
+ db
+ (baseQuery <> " AND item_status = ? " <> orderLimit)
+ (userId, groupId, CISRcvNew)
getGroupNavInfo_ :: DB.Connection -> User -> GroupInfo -> CChatItem 'CTGroup -> IO NavigationInfo
getGroupNavInfo_ db User {userId} GroupInfo {groupId} afterCI = do
@@ -1901,22 +1691,12 @@ getLocalNavInfo_ db User {userId} NoteFolder {noteFolderId} afterCI = do
:. (userId, noteFolderId, ciCreatedAt afterCI, cChatItemId afterCI)
)
-toChatItemRef ::
- (ChatItemId, Maybe ContactId, Maybe GroupId, Maybe GroupChatScopeTag, Maybe GroupMemberId, Maybe NoteFolderId) ->
- Either StoreError (ChatRef, ChatItemId)
+toChatItemRef :: (ChatItemId, Maybe Int64, Maybe Int64, Maybe Int64) -> Either StoreError (ChatRef, ChatItemId)
toChatItemRef = \case
- (itemId, Just contactId, Nothing, Nothing, Nothing, Nothing) ->
- Right (ChatRef CTDirect contactId Nothing, itemId)
- (itemId, Nothing, Just groupId, Nothing, Nothing, Nothing) ->
- Right (ChatRef CTGroup groupId Nothing, itemId)
- (itemId, Nothing, Just groupId, Just GCSTMemberSupport_, Nothing, Nothing) ->
- Right (ChatRef CTGroup groupId (Just (GCSMemberSupport Nothing)), itemId)
- (itemId, Nothing, Just groupId, Just GCSTMemberSupport_, Just scopeGMId, Nothing) ->
- Right (ChatRef CTGroup groupId (Just (GCSMemberSupport $ Just scopeGMId)), itemId)
- (itemId, Nothing, Nothing, Nothing, Nothing, Just folderId) ->
- Right (ChatRef CTLocal folderId Nothing, itemId)
- (itemId, _, _, _, _, _) ->
- Left $ SEBadChatItem itemId Nothing
+ (itemId, Just contactId, Nothing, Nothing) -> Right (ChatRef CTDirect contactId, itemId)
+ (itemId, Nothing, Just groupId, Nothing) -> Right (ChatRef CTGroup groupId, itemId)
+ (itemId, Nothing, Nothing, Just folderId) -> Right (ChatRef CTLocal folderId, itemId)
+ (itemId, _, _, _) -> Left $ SEBadChatItem itemId Nothing
updateDirectChatItemsRead :: DB.Connection -> User -> ContactId -> IO ()
updateDirectChatItemsRead db User {userId} contactId = do
@@ -1983,8 +1763,8 @@ setDirectChatItemsDeleteAt db User {userId} contactId itemIds currentTs = forM i
(deleteAt, userId, contactId, chatItemId)
pure (chatItemId, deleteAt)
-updateGroupChatItemsRead :: DB.Connection -> User -> GroupInfo -> Maybe GroupChatScope -> IO ()
-updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope = do
+updateGroupChatItemsRead :: DB.Connection -> User -> GroupId -> IO ()
+updateGroupChatItemsRead db User {userId} groupId = do
currentTs <- getCurrentTime
DB.execute
db
@@ -1993,20 +1773,6 @@ updateGroupChatItemsRead db User {userId} GroupInfo {groupId, membership} scope
WHERE user_id = ? AND group_id = ? AND item_status = ?
|]
(CISRcvRead, currentTs, userId, groupId, CISRcvNew)
- case scope of
- Nothing -> pure ()
- Just GCSMemberSupport {groupMemberId_} -> do
- let gmId = fromMaybe (groupMemberId' membership) groupMemberId_
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_items_unread = 0,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = 0
- WHERE group_member_id = ?
- |]
- (Only gmId)
getGroupUnreadTimedItems :: DB.Connection -> User -> GroupId -> IO [(ChatItemId, Int)]
getGroupUnreadTimedItems db User {userId} groupId =
@@ -2019,83 +1785,33 @@ getGroupUnreadTimedItems db User {userId} groupId =
|]
(userId, groupId, CISRcvNew)
-updateGroupChatItemsReadList :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> Maybe GroupChatScopeInfo -> NonEmpty ChatItemId -> ExceptT StoreError IO ([(ChatItemId, Int)], GroupInfo)
-updateGroupChatItemsReadList db vr user@User {userId} g@GroupInfo {groupId} scopeInfo_ itemIds = do
- currentTs <- liftIO getCurrentTime
- -- Possible improvement is to differentiate retrieval queries for each scope,
- -- but we rely on UI to not pass item IDs from incorrect scope.
- readItemsData <- liftIO $ catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds
- g' <- case scopeInfo_ of
- Nothing -> pure g
- Just scopeInfo@GCSIMemberSupport {groupMember_} -> do
- let decStats = countReadItems groupMember_ readItemsData
- liftIO $ updateGroupScopeUnreadStats db vr user g scopeInfo decStats
- pure (timedItems readItemsData, g')
+updateGroupChatItemsReadList :: DB.Connection -> User -> GroupId -> NonEmpty ChatItemId -> IO [(ChatItemId, Int)]
+updateGroupChatItemsReadList db User {userId} groupId itemIds = do
+ currentTs <- getCurrentTime
+ catMaybes . L.toList <$> mapM (getUpdateGroupItem currentTs) itemIds
where
- getUpdateGroupItem :: UTCTime -> ChatItemId -> IO (Maybe (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt))
- getUpdateGroupItem currentTs itemId =
- maybeFirstRow id $
- DB.query
- db
- [sql|
- UPDATE chat_items SET item_status = ?, updated_at = ?
- WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ?
- RETURNING chat_item_id, timed_ttl, timed_delete_at, group_member_id, user_mention
- |]
- (CISRcvRead, currentTs, userId, groupId, CISRcvNew, itemId)
- countReadItems :: Maybe GroupMember -> [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> (Int, Int, Int)
- countReadItems scopeMember_ readItemsData =
- let unread = length readItemsData
- (unanswered, mentions) = foldl' countItem (0, 0) readItemsData
- in (unread, unanswered, mentions)
+ getUpdateGroupItem currentTs itemId = do
+ ttl_ <- maybeFirstRow fromOnly getUnreadTimedItem
+ setItemRead
+ pure $ (itemId,) <$> ttl_
where
- countItem :: (Int, Int) -> (ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt) -> (Int, Int)
- countItem (!unanswered, !mentions) (_, _, _, itemGMId_, userMention_) =
- let unanswered' = case (scopeMember_, itemGMId_) of
- (Just scopeMember, Just itemGMId) | itemGMId == groupMemberId' scopeMember -> unanswered + 1
- _ -> unanswered
- mentions' = case userMention_ of
- Just (BI True) -> mentions + 1
- _ -> mentions
- in (unanswered', mentions')
- timedItems :: [(ChatItemId, Maybe Int, Maybe UTCTime, Maybe GroupMemberId, Maybe BoolInt)] -> [(ChatItemId, Int)]
- timedItems = foldl' addTimedItem []
- where
- addTimedItem acc (itemId, Just ttl, Nothing, _, _) = (itemId, ttl) : acc
- addTimedItem acc _ = acc
-
-updateGroupScopeUnreadStats :: DB.Connection -> VersionRangeChat -> User -> GroupInfo -> GroupChatScopeInfo -> (Int, Int, Int) -> IO GroupInfo
-updateGroupScopeUnreadStats db vr user g@GroupInfo {membership} scopeInfo (unread, unanswered, mentions) =
- case scopeInfo of
- GCSIMemberSupport {groupMember_} -> case groupMember_ of
- Nothing -> do
- membership' <- updateGMStats membership
- pure g {membership = membership'}
- Just member -> do
- member' <- updateGMStats member
- let didRequire = gmRequiresAttention member
- nowRequires = gmRequiresAttention member'
- if (not nowRequires && didRequire)
- then decreaseGroupMembersRequireAttention db user g
- else pure g
- where
- updateGMStats m@GroupMember {groupMemberId} = do
- currentTs <- getCurrentTime
- DB.execute
- db
- [sql|
- UPDATE group_members
- SET support_chat_items_unread = support_chat_items_unread - ?,
- support_chat_items_member_attention = support_chat_items_member_attention - ?,
- support_chat_items_mentions = support_chat_items_mentions - ?,
- updated_at = ?
- WHERE group_member_id = ?
- |]
- (unread, unanswered, mentions, currentTs, groupMemberId)
- m_ <- runExceptT $ getGroupMemberById db vr user groupMemberId
- pure $ either (const m) id m_ -- Left shouldn't happen, but types require it
-
-deriving instance Show BoolInt
+ getUnreadTimedItem =
+ DB.query
+ db
+ [sql|
+ SELECT timed_ttl
+ FROM chat_items
+ WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
+ |]
+ (userId, groupId, CISRcvNew, itemId)
+ setItemRead =
+ DB.execute
+ db
+ [sql|
+ UPDATE chat_items SET item_status = ?, updated_at = ?
+ WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ?
+ |]
+ (CISRcvRead, currentTs, userId, groupId, CISRcvNew, itemId)
setGroupChatItemsDeleteAt :: DB.Connection -> User -> GroupId -> [(ChatItemId, Int)] -> UTCTime -> IO [(ChatItemId, UTCTime)]
setGroupChatItemsDeleteAt db User {userId} groupId itemIds currentTs = forM itemIds $ \(chatItemId, ttl) -> do
@@ -2199,69 +1915,48 @@ toGroupQuote qr@(_, _, _, _, quotedSent) quotedMember_ = toQuote qr $ direction
direction _ _ = Nothing
-- this function can be changed so it never fails, not only avoid failure on invalid json
-toGroupChatItem ::
- UTCTime ->
- Int64 ->
- ChatItemRow
- :. Only (Maybe GroupMemberId)
- :. MaybeGroupMemberRow
- :. GroupQuoteRow
- :. MaybeGroupMemberRow ->
- Either StoreError (CChatItem 'CTGroup)
-toGroupChatItem
- currentTs
- userContactId
- ( ( (itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId)
- :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt)
- :. forwardedFromRow
- :. (timedTTL, timedDeleteAt, itemLive, BI userMention)
- :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)
- )
- :. Only forwardedByMember
- :. memberRow_
- :. (quoteRow :. quotedMemberRow_)
- :. deletedByGroupMemberRow_
- ) = do
- chatItem $ fromRight invalid $ dbParseACIContent itemContentText
- where
- member_ = toMaybeGroupMember userContactId memberRow_
- quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
- deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_
- invalid = ACIContent msgDir $ CIInvalidJSON itemContentText
- chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of
- (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) ->
- Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent (maybeCIFile fileStatus)
- (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) ->
- Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent Nothing
- (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) ->
- Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent (maybeCIFile fileStatus)
- (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) ->
- Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent Nothing
- _ -> badItem
- maybeCIFile :: CIFileStatus d -> Maybe (CIFile d)
- maybeCIFile fileStatus =
- case (fileId_, fileName_, fileSize_, fileProtocol_) of
- (Just fileId, Just fileName, Just fileSize, Just fileProtocol) ->
- let cfArgs = CFArgs <$> fileKey <*> fileNonce
- fileSource = (`CryptoFile` cfArgs) <$> filePath
- in Just CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol}
- _ -> Nothing
- cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTGroup
- cItem d chatDir ciStatus content file =
- CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, mentions = M.empty, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, reactions = [], file}
- badItem = Left $ SEBadChatItem itemId (Just itemTs)
- ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTGroup d
- ciMeta content status =
- let itemDeleted' = case itemDeleted of
- DBCINotDeleted -> Nothing
- DBCIBlocked -> Just (CIBlocked deletedTs)
- DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs)
- _ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_)
- itemEdited' = maybe False unBI itemEdited
- itemForwarded = toCIForwardedFrom forwardedFromRow
- in mkCIMeta itemId content itemText status (unBI <$> sentViaProxy) sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed (unBI <$> itemLive) userMention currentTs itemTs forwardedByMember createdAt updatedAt
- ciTimed :: Maybe CITimed
- ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
+toGroupChatItem :: UTCTime -> Int64 -> ChatItemRow :. Only (Maybe GroupMemberId) :. MaybeGroupMemberRow :. GroupQuoteRow :. MaybeGroupMemberRow -> Either StoreError (CChatItem 'CTGroup)
+toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, itemContentText, itemText, itemStatus, sentViaProxy, sharedMsgId) :. (itemDeleted, deletedTs, itemEdited, createdAt, updatedAt) :. forwardedFromRow :. (timedTTL, timedDeleteAt, itemLive, BI userMention) :. (fileId_, fileName_, fileSize_, filePath, fileKey, fileNonce, fileStatus_, fileProtocol_)) :. Only forwardedByMember :. memberRow_ :. (quoteRow :. quotedMemberRow_) :. deletedByGroupMemberRow_) = do
+ chatItem $ fromRight invalid $ dbParseACIContent itemContentText
+ where
+ member_ = toMaybeGroupMember userContactId memberRow_
+ quotedMember_ = toMaybeGroupMember userContactId quotedMemberRow_
+ deletedByGroupMember_ = toMaybeGroupMember userContactId deletedByGroupMemberRow_
+ invalid = ACIContent msgDir $ CIInvalidJSON itemContentText
+ chatItem itemContent = case (itemContent, itemStatus, member_, fileStatus_) of
+ (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Just (AFS SMDSnd fileStatus)) ->
+ Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent (maybeCIFile fileStatus)
+ (ACIContent SMDSnd ciContent, ACIStatus SMDSnd ciStatus, _, Nothing) ->
+ Right $ cItem SMDSnd CIGroupSnd ciStatus ciContent Nothing
+ (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Just (AFS SMDRcv fileStatus)) ->
+ Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent (maybeCIFile fileStatus)
+ (ACIContent SMDRcv ciContent, ACIStatus SMDRcv ciStatus, Just member, Nothing) ->
+ Right $ cItem SMDRcv (CIGroupRcv member) ciStatus ciContent Nothing
+ _ -> badItem
+ maybeCIFile :: CIFileStatus d -> Maybe (CIFile d)
+ maybeCIFile fileStatus =
+ case (fileId_, fileName_, fileSize_, fileProtocol_) of
+ (Just fileId, Just fileName, Just fileSize, Just fileProtocol) ->
+ let cfArgs = CFArgs <$> fileKey <*> fileNonce
+ fileSource = (`CryptoFile` cfArgs) <$> filePath
+ in Just CIFile {fileId, fileName, fileSize, fileSource, fileStatus, fileProtocol}
+ _ -> Nothing
+ cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTGroup
+ cItem d chatDir ciStatus content file =
+ CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, mentions = M.empty, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, reactions = [], file}
+ badItem = Left $ SEBadChatItem itemId (Just itemTs)
+ ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTGroup d
+ ciMeta content status =
+ let itemDeleted' = case itemDeleted of
+ DBCINotDeleted -> Nothing
+ DBCIBlocked -> Just (CIBlocked deletedTs)
+ DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs)
+ _ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_)
+ itemEdited' = maybe False unBI itemEdited
+ itemForwarded = toCIForwardedFrom forwardedFromRow
+ in mkCIMeta itemId content itemText status (unBI <$> sentViaProxy) sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed (unBI <$> itemLive) userMention currentTs itemTs forwardedByMember createdAt updatedAt
+ ciTimed :: Maybe CITimed
+ ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt}
getAllChatItems :: DB.Connection -> VersionRangeChat -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem]
getAllChatItems db vr user@User {userId} pagination search_ = do
@@ -2287,7 +1982,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
<$> DB.query
db
[sql|
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
ORDER BY item_ts DESC, chat_item_id DESC
@@ -2298,7 +1993,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
DB.query
db
[sql|
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?))
@@ -2311,7 +2006,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
<$> DB.query
db
[sql|
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?))
@@ -2323,7 +2018,7 @@ getAllChatItems db vr user@User {userId} pagination search_ = do
DB.query
db
[sql|
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE chat_item_id = ?
|]
@@ -2675,8 +2370,14 @@ updateGroupChatItemModerated db User {userId} GroupInfo {groupId} ci m@GroupMemb
updateMemberCIsModerated :: MsgDirectionI d => DB.Connection -> User -> GroupInfo -> GroupMember -> GroupMember -> SMsgDirection d -> UTCTime -> IO ()
updateMemberCIsModerated db User {userId} GroupInfo {groupId, membership} member byGroupMember md deletedTs = do
itemIds <- updateCIs =<< getCurrentTime
+#if defined(dbPostgres)
+ let inItemIds = Only $ In (map fromOnly itemIds)
+ DB.execute db "DELETE FROM messages WHERE message_id IN (SELECT message_id FROM chat_item_messages WHERE chat_item_id IN ?)" inItemIds
+ DB.execute db "DELETE FROM chat_item_versions WHERE chat_item_id IN ?" inItemIds
+#else
DB.executeMany db deleteChatItemMessagesQuery itemIds
DB.executeMany db "DELETE FROM chat_item_versions WHERE chat_item_id = ?" itemIds
+#endif
where
memId = groupMemberId' member
updateQuery =
@@ -2889,7 +2590,6 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
@@ -2897,17 +2597,13 @@ getGroupChatItem db User {userId, userContactId} groupId itemId = ExceptT $ do
rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences,
rm.created_at, rm.updated_at,
- rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts,
-- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences,
- dbm.created_at, dbm.updated_at,
- dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts
+ dbm.created_at, dbm.updated_at
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
- LEFT JOIN group_members gsm ON gsm.group_member_id = i.group_scope_group_member_id
- LEFT JOIN contact_profiles gsp ON gsp.contact_profile_id = COALESCE(gsm.member_profile_id, gsm.contact_profile_id)
LEFT JOIN group_members m ON m.group_member_id = i.group_member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN chat_items ri ON ri.shared_msg_id = i.quoted_shared_msg_id AND ri.group_id = i.group_id
@@ -3069,7 +2765,7 @@ getChatItemByFileId db vr user@User {userId} fileId = do
DB.query
db
[sql|
- SELECT i.chat_item_id, i.contact_id, i.group_id, i.group_scope_tag, i.group_scope_group_member_id, i.note_folder_id
+ SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id
FROM chat_items i
JOIN files f ON f.chat_item_id = i.chat_item_id
WHERE f.user_id = ? AND f.file_id = ?
@@ -3091,7 +2787,7 @@ getChatItemByGroupId db vr user@User {userId} groupId = do
DB.query
db
[sql|
- SELECT i.chat_item_id, i.contact_id, i.group_id, i.group_scope_tag, i.group_scope_group_member_id, i.note_folder_id
+ SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id
FROM chat_items i
JOIN groups g ON g.chat_item_id = i.chat_item_id
WHERE g.user_id = ? AND g.group_id = ?
@@ -3106,26 +2802,24 @@ getChatRefViaItemId db User {userId} itemId = do
DB.query db "SELECT contact_id, group_id FROM chat_items WHERE user_id = ? AND chat_item_id = ?" (userId, itemId)
where
toChatRef = \case
- (Just contactId, Nothing) -> Right $ ChatRef CTDirect contactId Nothing
- -- Only used in CLI and unused APIs
- (Nothing, Just groupId) -> Right $ ChatRef CTGroup groupId Nothing
+ (Just contactId, Nothing) -> Right $ ChatRef CTDirect contactId
+ (Nothing, Just groupId) -> Right $ ChatRef CTGroup groupId
(_, _) -> Left $ SEBadChatItem itemId Nothing
getAChatItem :: DB.Connection -> VersionRangeChat -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem
-getAChatItem db vr user (ChatRef cType chatId scope) itemId = do
- aci <- case cType of
- CTDirect -> do
- ct <- getContact db vr user chatId
- (CChatItem msgDir ci) <- getDirectChatItem db user chatId itemId
+getAChatItem db vr user chatRef itemId = do
+ aci <- case chatRef of
+ ChatRef CTDirect contactId -> do
+ ct <- getContact db vr user contactId
+ (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId
pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci
- CTGroup -> do
- gInfo <- getGroupInfo db vr user chatId
- (CChatItem msgDir ci) <- getGroupChatItem db user chatId itemId
- scopeInfo <- mapM (getGroupChatScopeInfo db vr user gInfo) scope
- pure $ AChatItem SCTGroup msgDir (GroupChat gInfo scopeInfo) ci
- CTLocal -> do
- nf <- getNoteFolder db user chatId
- CChatItem msgDir ci <- getLocalChatItem db user chatId itemId
+ ChatRef CTGroup groupId -> do
+ gInfo <- getGroupInfo db vr user groupId
+ (CChatItem msgDir ci) <- getGroupChatItem db user groupId itemId
+ pure $ AChatItem SCTGroup msgDir (GroupChat gInfo) ci
+ ChatRef CTLocal folderId -> do
+ nf <- getNoteFolder db user folderId
+ CChatItem msgDir ci <- getLocalChatItem db user folderId itemId
pure $ AChatItem SCTLocal msgDir (LocalChat nf) ci
_ -> throwError $ SEChatItemNotFound itemId
liftIO $ getACIReactions db aci
@@ -3135,9 +2829,9 @@ getAChatItemBySharedMsgId db user cd sharedMsgId = case cd of
CDDirectRcv ct@Contact {contactId} -> do
(CChatItem msgDir ci) <- getDirectChatItemBySharedMsgId db user contactId sharedMsgId
pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci
- CDGroupRcv g scopeInfo GroupMember {groupMemberId} -> do
+ CDGroupRcv g GroupMember {groupMemberId} -> do
(CChatItem msgDir ci) <- getGroupChatItemBySharedMsgId db user g groupMemberId sharedMsgId
- pure $ AChatItem SCTGroup msgDir (GroupChat g scopeInfo) ci
+ pure $ AChatItem SCTGroup msgDir (GroupChat g) ci
getChatItemVersions :: DB.Connection -> ChatItemId -> IO [ChatItemVersion]
getChatItemVersions db itemId = do
@@ -3199,7 +2893,7 @@ getGroupCIMentions db ciId =
SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias
FROM chat_item_mentions r
LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id
- LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
+ LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
WHERE r.chat_item_id = ?
|]
(Only ciId)
@@ -3218,7 +2912,7 @@ getACIReactions db aci@(AChatItem _ md chat ci@ChatItem {meta = CIMeta {itemShar
DirectChat ct -> do
reactions <- getDirectCIReactions db ct itemSharedMId
pure $ AChatItem SCTDirect md chat ci {reactions}
- GroupChat g _s -> do
+ GroupChat g -> do
let GroupMember {memberId} = chatItemMember g ci
reactions <- getGroupCIReactions db g memberId itemSharedMId
pure $ AChatItem SCTGroup md chat ci {reactions}
@@ -3330,23 +3024,16 @@ getTimedItems db User {userId} startTimedThreadCutoff =
<$> DB.query
db
[sql|
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, timed_delete_at
+ SELECT chat_item_id, contact_id, group_id, timed_delete_at
FROM chat_items
WHERE user_id = ? AND timed_delete_at IS NOT NULL AND timed_delete_at <= ?
|]
(userId, startTimedThreadCutoff)
where
- toCIRefDeleteAt :: (ChatItemId, Maybe ContactId, Maybe GroupId, Maybe GroupChatScopeTag, Maybe GroupMemberId, UTCTime) -> Maybe ((ChatRef, ChatItemId), UTCTime)
+ toCIRefDeleteAt :: (ChatItemId, Maybe ContactId, Maybe GroupId, UTCTime) -> Maybe ((ChatRef, ChatItemId), UTCTime)
toCIRefDeleteAt = \case
- (itemId, Just contactId, Nothing, Nothing, Nothing, deleteAt) ->
- Just ((ChatRef CTDirect contactId Nothing, itemId), deleteAt)
- (itemId, Nothing, Just groupId, scopeTag_, scopeGMId_, deleteAt) ->
- let scope = case (scopeTag_, scopeGMId_) of
- (Nothing, Nothing) -> Nothing
- (Just GCSTMemberSupport_, Just groupMemberId) -> Just $ GCSMemberSupport (Just groupMemberId)
- (Just GCSTMemberSupport_, Nothing) -> Just $ GCSMemberSupport Nothing
- (Nothing, Just _) -> Nothing -- should not happen
- in Just ((ChatRef CTGroup groupId scope, itemId), deleteAt)
+ (itemId, Just contactId, Nothing, deleteAt) -> Just ((ChatRef CTDirect contactId, itemId), deleteAt)
+ (itemId, Nothing, Just groupId, deleteAt) -> Just ((ChatRef CTGroup groupId, itemId), deleteAt)
_ -> Nothing
getChatItemTTL :: DB.Connection -> User -> IO Int64
@@ -3518,6 +3205,7 @@ getGroupSndStatusCounts db itemId =
|]
(Only itemId)
+-- TODO [knocking] filter out messages sent to member only
getGroupHistoryItems :: DB.Connection -> User -> GroupInfo -> GroupMember -> Int -> IO [Either StoreError (CChatItem 'CTGroup)]
getGroupHistoryItems db user@User {userId} g@GroupInfo {groupId} m count = do
ciIds <- getLastItemIds_
diff --git a/src/Simplex/Chat/Store/Postgres/Migrations.hs b/src/Simplex/Chat/Store/Postgres/Migrations.hs
index 2e9ff571e9..c392c17db1 100644
--- a/src/Simplex/Chat/Store/Postgres/Migrations.hs
+++ b/src/Simplex/Chat/Store/Postgres/Migrations.hs
@@ -7,15 +7,13 @@ import Data.Text (Text)
import Simplex.Chat.Store.Postgres.Migrations.M20241220_initial
import Simplex.Chat.Store.Postgres.Migrations.M20250402_short_links
import Simplex.Chat.Store.Postgres.Migrations.M20250512_member_admission
-import Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Text, Maybe Text)]
schemaMigrations =
[ ("20241220_initial", m20241220_initial, Nothing),
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links),
- ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission),
- ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope)
+ ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission)
]
-- | The list of migrations in ascending order by date
diff --git a/src/Simplex/Chat/Store/Postgres/Migrations/M20250513_group_scope.hs b/src/Simplex/Chat/Store/Postgres/Migrations/M20250513_group_scope.hs
deleted file mode 100644
index cca2cff969..0000000000
--- a/src/Simplex/Chat/Store/Postgres/Migrations/M20250513_group_scope.hs
+++ /dev/null
@@ -1,64 +0,0 @@
-{-# LANGUAGE QuasiQuotes #-}
-
-module Simplex.Chat.Store.Postgres.Migrations.M20250513_group_scope where
-
-import Data.Text (Text)
-import qualified Data.Text as T
-import Text.RawString.QQ (r)
-
-m20250513_group_scope :: Text
-m20250513_group_scope =
- T.pack
- [r|
-ALTER TABLE group_members ADD COLUMN support_chat_ts TIMESTAMPTZ;
-ALTER TABLE group_members ADD COLUMN support_chat_items_unread BIGINT NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_items_member_attention BIGINT NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_items_mentions BIGINT NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_last_msg_from_member_ts TIMESTAMPTZ;
-
-ALTER TABLE groups ADD COLUMN members_require_attention BIGINT NOT NULL DEFAULT 0;
-
-ALTER TABLE chat_items ADD COLUMN group_scope_tag TEXT;
-ALTER TABLE chat_items ADD COLUMN group_scope_group_member_id BIGINT REFERENCES group_members(group_member_id) ON DELETE CASCADE;
-
-CREATE INDEX idx_chat_items_group_scope_group_member_id ON chat_items(group_scope_group_member_id);
-
-CREATE INDEX idx_chat_items_group_scope_item_ts ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_ts
-);
-
-CREATE INDEX idx_chat_items_group_scope_item_status ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_status,
- item_ts
-);
-|]
-
-down_m20250513_group_scope :: Text
-down_m20250513_group_scope =
- T.pack
- [r|
-DROP INDEX idx_chat_items_group_scope_item_status;
-
-DROP INDEX idx_chat_items_group_scope_item_ts;
-
-DROP INDEX idx_chat_items_group_scope_group_member_id;
-
-ALTER TABLE chat_items DROP COLUMN group_scope_tag;
-ALTER TABLE chat_items DROP COLUMN group_scope_group_member_id;
-
-ALTER TABLE groups DROP COLUMN members_require_attention;
-
-ALTER TABLE group_members DROP COLUMN support_chat_ts;
-ALTER TABLE group_members DROP COLUMN support_chat_items_unread;
-ALTER TABLE group_members DROP COLUMN support_chat_items_member_attention;
-ALTER TABLE group_members DROP COLUMN support_chat_items_mentions;
-ALTER TABLE group_members DROP COLUMN support_chat_last_msg_from_member_ts;
-|]
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations.hs b/src/Simplex/Chat/Store/SQLite/Migrations.hs
index 871cb62220..183d699f01 100644
--- a/src/Simplex/Chat/Store/SQLite/Migrations.hs
+++ b/src/Simplex/Chat/Store/SQLite/Migrations.hs
@@ -130,7 +130,6 @@ import Simplex.Chat.Store.SQLite.Migrations.M20250129_delete_unused_contacts
import Simplex.Chat.Store.SQLite.Migrations.M20250130_indexes
import Simplex.Chat.Store.SQLite.Migrations.M20250402_short_links
import Simplex.Chat.Store.SQLite.Migrations.M20250512_member_admission
-import Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
schemaMigrations :: [(String, Query, Maybe Query)]
@@ -260,8 +259,7 @@ schemaMigrations =
("20250129_delete_unused_contacts", m20250129_delete_unused_contacts, Just down_m20250129_delete_unused_contacts),
("20250130_indexes", m20250130_indexes, Just down_m20250130_indexes),
("20250402_short_links", m20250402_short_links, Just down_m20250402_short_links),
- ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission),
- ("20250513_group_scope", m20250513_group_scope, Just down_m20250513_group_scope)
+ ("20250512_member_admission", m20250512_member_admission, Just down_m20250512_member_admission)
]
-- | The list of migrations in ascending order by date
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/M20250513_group_scope.hs b/src/Simplex/Chat/Store/SQLite/Migrations/M20250513_group_scope.hs
deleted file mode 100644
index 34ec6ee382..0000000000
--- a/src/Simplex/Chat/Store/SQLite/Migrations/M20250513_group_scope.hs
+++ /dev/null
@@ -1,61 +0,0 @@
-{-# LANGUAGE QuasiQuotes #-}
-
-module Simplex.Chat.Store.SQLite.Migrations.M20250513_group_scope where
-
-import Database.SQLite.Simple (Query)
-import Database.SQLite.Simple.QQ (sql)
-
-m20250513_group_scope :: Query
-m20250513_group_scope =
- [sql|
-ALTER TABLE group_members ADD COLUMN support_chat_ts TEXT;
-ALTER TABLE group_members ADD COLUMN support_chat_items_unread INTEGER NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_items_member_attention INTEGER NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_items_mentions INTEGER NOT NULL DEFAULT 0;
-ALTER TABLE group_members ADD COLUMN support_chat_last_msg_from_member_ts TEXT;
-
-ALTER TABLE groups ADD COLUMN members_require_attention INTEGER NOT NULL DEFAULT 0;
-
-ALTER TABLE chat_items ADD COLUMN group_scope_tag TEXT;
-ALTER TABLE chat_items ADD COLUMN group_scope_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE CASCADE;
-
-CREATE INDEX idx_chat_items_group_scope_group_member_id ON chat_items(group_scope_group_member_id);
-
-CREATE INDEX idx_chat_items_group_scope_item_ts ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_ts
-);
-
-CREATE INDEX idx_chat_items_group_scope_item_status ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_status,
- item_ts
-);
-|]
-
-down_m20250513_group_scope :: Query
-down_m20250513_group_scope =
- [sql|
-DROP INDEX idx_chat_items_group_scope_item_status;
-
-DROP INDEX idx_chat_items_group_scope_item_ts;
-
-DROP INDEX idx_chat_items_group_scope_group_member_id;
-
-ALTER TABLE chat_items DROP COLUMN group_scope_tag;
-ALTER TABLE chat_items DROP COLUMN group_scope_group_member_id;
-
-ALTER TABLE groups DROP COLUMN members_require_attention;
-
-ALTER TABLE group_members DROP COLUMN support_chat_ts;
-ALTER TABLE group_members DROP COLUMN support_chat_items_unread;
-ALTER TABLE group_members DROP COLUMN support_chat_items_member_attention;
-ALTER TABLE group_members DROP COLUMN support_chat_items_mentions;
-ALTER TABLE group_members DROP COLUMN support_chat_last_msg_from_member_ts;
-|]
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt
index 13215dcb75..a85ba4a4cb 100644
--- a/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt
+++ b/src/Simplex/Chat/Store/SQLite/Migrations/agent_query_plans.txt
@@ -1071,10 +1071,6 @@ Query: UPDATE connections SET smp_agent_version = ? WHERE conn_id = ?
Plan:
SEARCH connections USING PRIMARY KEY (conn_id=?)
-Query: UPDATE connections SET user_id = ? WHERE conn_id = ? and user_id = ?
-Plan:
-SEARCH connections USING PRIMARY KEY (conn_id=?)
-
Query: UPDATE messages SET msg_body = x'' WHERE conn_id = ? AND internal_id = ?
Plan:
SEARCH messages USING PRIMARY KEY (conn_id=? AND internal_id=?)
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
index b0e98856d9..e9ade30f93 100644
--- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
+++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_query_plans.txt
@@ -1,21 +1,3 @@
-Query:
- UPDATE groups
- SET chat_ts = ?,
- members_require_attention = members_require_attention + 1
- WHERE user_id = ? AND group_id = ?
-
-Plan:
-SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
-
-Query:
- UPDATE groups
- SET chat_ts = ?,
- members_require_attention = members_require_attention - 1
- WHERE user_id = ? AND group_id = ?
-
-Plan:
-SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
INSERT INTO group_members
( group_id, member_id, member_role, member_category, member_status, invited_by, invited_by_group_member_id,
@@ -54,20 +36,17 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
-- from GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
- m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
+ m.created_at, m.updated_at
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
JOIN groups g ON g.group_id = m.group_id
@@ -100,40 +79,6 @@ Query:
Plan:
SEARCH contact_requests USING INTEGER PRIMARY KEY (rowid=?)
-Query:
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = support_chat_items_mentions + ?
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
-Query:
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = support_chat_items_member_attention + ?,
- support_chat_items_mentions = support_chat_items_mentions + ?
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
-Query:
- UPDATE group_members
- SET support_chat_ts = ?,
- support_chat_items_unread = support_chat_items_unread + ?,
- support_chat_items_member_attention = support_chat_items_member_attention + ?,
- support_chat_items_mentions = support_chat_items_mentions + ?,
- support_chat_last_msg_from_member_ts = ?
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
INSERT INTO contact_requests
(user_contact_link_id, agent_invitation_id, peer_chat_min_version, peer_chat_max_version, contact_profile_id, local_display_name, user_id,
@@ -264,6 +209,21 @@ Query:
Plan:
SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
+Query:
+ SELECT timed_ttl
+ FROM chat_items
+ WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ? AND timed_ttl IS NOT NULL AND timed_delete_at IS NULL
+
+Plan:
+SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
+
+Query:
+ UPDATE chat_items SET item_status = ?, updated_at = ?
+ WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ?
+
+Plan:
+SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
+
Query:
UPDATE contact_profiles
SET display_name = ?,
@@ -515,7 +475,7 @@ SEARCH uc USING INDEX sqlite_autoindex_user_contact_links_1 (user_id=? AND local
SEARCH c USING INDEX idx_connections_user_contact_link_id (user_contact_link_id=?)
Query:
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts < ? OR (item_ts = ? AND chat_item_id < ?))
@@ -523,18 +483,18 @@ Query:
LIMIT ?
Plan:
-SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
+SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?)
USE TEMP B-TREE FOR ORDER BY
Query:
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
ORDER BY item_ts DESC, chat_item_id DESC
LIMIT ?
Plan:
-SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
+SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?)
USE TEMP B-TREE FOR ORDER BY
Query:
@@ -614,8 +574,7 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category,
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
- m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts
+ m.created_at, m.updated_at
FROM group_members m
JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN contacts c ON m.contact_id = c.contact_id
@@ -631,7 +590,7 @@ SEARCH p USING INTEGER PRIMARY KEY (rowid=?)
SEARCH i USING COVERING INDEX idx_chat_items_group_shared_msg_id (user_id=? AND group_id=? AND group_member_id=? AND shared_msg_id=?) LEFT-JOIN
Query:
- SELECT i.chat_item_id, i.contact_id, i.group_id, i.group_scope_tag, i.group_scope_group_member_id, i.note_folder_id
+ SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id
FROM chat_items i
JOIN files f ON f.chat_item_id = i.chat_item_id
WHERE f.user_id = ? AND f.file_id = ?
@@ -666,14 +625,6 @@ SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH g USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH h USING INDEX idx_sent_probe_hashes_sent_probe_id (sent_probe_id=?)
-Query:
- UPDATE chat_items SET item_status = ?, updated_at = ?
- WHERE user_id = ? AND group_id = ? AND item_status = ? AND chat_item_id = ?
- RETURNING chat_item_id, timed_ttl, timed_delete_at, group_member_id, user_mention
-
-Plan:
-SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
DELETE FROM chat_item_reactions
WHERE contact_id = ? AND shared_msg_id = ? AND reaction_sent = ? AND reaction = ?
@@ -773,7 +724,6 @@ Query:
m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id,
p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
-- quoted ChatItem
ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent,
-- quoted GroupMember
@@ -781,17 +731,13 @@ Query:
rm.member_status, rm.show_messages, rm.member_restriction, rm.invited_by, rm.invited_by_group_member_id, rm.local_display_name, rm.contact_id, rm.contact_profile_id, rp.contact_profile_id,
rp.display_name, rp.full_name, rp.image, rp.contact_link, rp.local_alias, rp.preferences,
rm.created_at, rm.updated_at,
- rm.support_chat_ts, rm.support_chat_items_unread, rm.support_chat_items_member_attention, rm.support_chat_items_mentions, rm.support_chat_last_msg_from_member_ts,
-- deleted by GroupMember
dbm.group_member_id, dbm.group_id, dbm.member_id, dbm.peer_chat_min_version, dbm.peer_chat_max_version, dbm.member_role, dbm.member_category,
dbm.member_status, dbm.show_messages, dbm.member_restriction, dbm.invited_by, dbm.invited_by_group_member_id, dbm.local_display_name, dbm.contact_id, dbm.contact_profile_id, dbp.contact_profile_id,
dbp.display_name, dbp.full_name, dbp.image, dbp.contact_link, dbp.local_alias, dbp.preferences,
- dbm.created_at, dbm.updated_at,
- dbm.support_chat_ts, dbm.support_chat_items_unread, dbm.support_chat_items_member_attention, dbm.support_chat_items_mentions, dbm.support_chat_last_msg_from_member_ts
+ dbm.created_at, dbm.updated_at
FROM chat_items i
LEFT JOIN files f ON f.chat_item_id = i.chat_item_id
- LEFT JOIN group_members gsm ON gsm.group_member_id = i.group_scope_group_member_id
- LEFT JOIN contact_profiles gsp ON gsp.contact_profile_id = COALESCE(gsm.member_profile_id, gsm.contact_profile_id)
LEFT JOIN group_members m ON m.group_member_id = i.group_member_id
LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
LEFT JOIN chat_items ri ON ri.shared_msg_id = i.quoted_shared_msg_id AND ri.group_id = i.group_id
@@ -804,7 +750,6 @@ Query:
Plan:
SEARCH i USING INTEGER PRIMARY KEY (rowid=?)
SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?) LEFT-JOIN
-SEARCH gsm USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH m USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH p USING INTEGER PRIMARY KEY (rowid=?) LEFT-JOIN
SEARCH ri USING INDEX idx_chat_items_group_id_shared_msg_id (group_id=? AND shared_msg_id=?) LEFT-JOIN
@@ -861,20 +806,17 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupInfo {membership}
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
-- GroupInfo {membership = GroupMember {memberProfile}}
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts,
-- via GroupMember
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -909,12 +851,10 @@ Query:
SELECT
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
- mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
+ mu.created_at, mu.updated_at
FROM groups g
JOIN group_profiles gp USING (group_profile_id)
JOIN group_members mu USING (group_id)
@@ -1037,7 +977,7 @@ Plan:
SEARCH chat_items USING INDEX idx_chat_items_group_id (group_id=?)
Query:
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE chat_item_id = ?
@@ -1045,7 +985,7 @@ Plan:
SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
Query:
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, note_folder_id
+ SELECT chat_item_id, contact_id, group_id, note_folder_id
FROM chat_items
WHERE user_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%'
AND (item_ts > ? OR (item_ts = ? AND chat_item_id > ?))
@@ -1053,7 +993,7 @@ Query:
LIMIT ?
Plan:
-SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
+SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=?)
USE TEMP B-TREE FOR ORDER BY
Query:
@@ -1202,7 +1142,7 @@ SEARCH i USING INDEX idx_chat_items_group_id (group_id=?)
SEARCH m USING INTEGER PRIMARY KEY (rowid=?)
Query:
- SELECT i.chat_item_id, i.contact_id, i.group_id, i.group_scope_tag, i.group_scope_group_member_id, i.note_folder_id
+ SELECT i.chat_item_id, i.contact_id, i.group_id, i.note_folder_id
FROM chat_items i
JOIN groups g ON g.chat_item_id = i.chat_item_id
WHERE g.user_id = ? AND g.group_id = ?
@@ -1293,27 +1233,6 @@ Query:
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-Query:
- UPDATE group_members
- SET support_chat_items_unread = 0,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = 0
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
-Query:
- UPDATE group_members
- SET support_chat_items_unread = support_chat_items_unread - ?,
- support_chat_items_member_attention = support_chat_items_member_attention - ?,
- support_chat_items_mentions = support_chat_items_mentions - ?,
- updated_at = ?
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
UPDATE group_profiles
SET display_name = ?, full_name = ?, description = ?, image = ?, preferences = ?, member_admission = ?, updated_at = ?
@@ -1618,6 +1537,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1658,6 +1578,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1692,6 +1613,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1725,6 +1647,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1758,6 +1681,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1791,6 +1715,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1824,6 +1749,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1857,6 +1783,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1890,6 +1817,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1920,6 +1848,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1950,6 +1879,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
ct.unread_chat
FROM contacts ct
@@ -1975,7 +1905,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -1988,7 +1918,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2010,7 +1940,7 @@ Query:
ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
@@ -2018,7 +1948,7 @@ SEARCH gp USING INTEGER PRIMARY KEY (rowid=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2027,7 +1957,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2040,7 +1970,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2057,14 +1987,14 @@ Query:
ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2073,7 +2003,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2086,7 +2016,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2102,14 +2032,14 @@ Query:
AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2118,7 +2048,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2131,7 +2061,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2147,14 +2077,14 @@ Query:
AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2163,7 +2093,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2176,7 +2106,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2192,14 +2122,14 @@ Query:
ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2208,7 +2138,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2221,7 +2151,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2237,14 +2167,14 @@ Query:
AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2253,7 +2183,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2266,7 +2196,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2282,14 +2212,14 @@ Query:
AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2298,7 +2228,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2311,7 +2241,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2327,14 +2257,14 @@ Query:
ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2343,7 +2273,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2356,7 +2286,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2369,14 +2299,14 @@ Query:
WHERE g.user_id = ? AND g.chat_ts < ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2385,7 +2315,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2398,7 +2328,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2411,14 +2341,14 @@ Query:
WHERE g.user_id = ? AND g.chat_ts > ? ORDER BY g.chat_ts ASC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=? AND chat_ts>?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2427,7 +2357,7 @@ Query:
(
SELECT chat_item_id
FROM chat_items ci
- WHERE ci.user_id = ? AND ci.group_id = g.group_id AND ci.group_scope_tag IS NULL
+ WHERE ci.user_id = ? AND ci.group_id = g.group_id
ORDER BY ci.item_ts DESC
LIMIT 1
) AS chat_item_id,
@@ -2440,7 +2370,7 @@ Query:
LEFT JOIN (
SELECT group_id, COUNT(1) AS UnreadCount, SUM(user_mention) as UnreadMentions, MIN(chat_item_id) AS MinUnread
FROM chat_items
- WHERE user_id = ? AND group_id IS NOT NULL AND group_scope_tag IS NULL AND item_status = ?
+ WHERE user_id = ? AND group_id IS NOT NULL AND item_status = ?
GROUP BY group_id
) ChatStats ON ChatStats.group_id = g.group_id
LEFT JOIN (
@@ -2453,14 +2383,14 @@ Query:
WHERE g.user_id = ? ORDER BY g.chat_ts DESC LIMIT ?
Plan:
MATERIALIZE ChatStats
-SEARCH chat_items USING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id>?)
MATERIALIZE ReportCount
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_deleted (user_id=? AND group_id>?)
SEARCH g USING INDEX idx_groups_chat_ts (user_id=?)
SEARCH ChatStats USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
SEARCH ReportCount USING AUTOMATIC COVERING INDEX (group_id=?) LEFT-JOIN
CORRELATED SCALAR SUBQUERY 1
-SEARCH ci USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+SEARCH ci USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query:
SELECT
@@ -2474,6 +2404,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2509,6 +2440,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2543,6 +2475,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2577,6 +2510,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2611,6 +2545,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2645,6 +2580,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2679,6 +2615,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2713,6 +2650,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2744,6 +2682,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2775,6 +2714,7 @@ Query:
LIMIT 1
) AS chat_item_id,
COALESCE(ChatStats.UnreadCount, 0),
+ 0,
COALESCE(ChatStats.MinUnread, 0),
nf.unread_chat
FROM note_folders nf
@@ -2986,7 +2926,7 @@ Plan:
SEARCH chat_items USING INDEX chat_items_note_folder_id (note_folder_id=?)
Query:
- SELECT chat_item_id, contact_id, group_id, group_scope_tag, group_scope_group_member_id, timed_delete_at
+ SELECT chat_item_id, contact_id, group_id, timed_delete_at
FROM chat_items
WHERE user_id = ? AND timed_delete_at IS NOT NULL AND timed_delete_at <= ?
@@ -3174,14 +3114,6 @@ Query:
Plan:
SEARCH group_member_intros USING INDEX sqlite_autoindex_group_member_intros_1 (re_group_member_id=? AND to_group_member_id=?)
-Query:
- SELECT group_scope_tag, group_scope_group_member_id
- FROM chat_items
- WHERE chat_item_id = ?
-
-Plan:
-SEARCH chat_items USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
SELECT group_snd_item_status
FROM group_snd_item_statuses
@@ -3254,7 +3186,7 @@ Query:
SELECT r.display_name, r.member_id, m.group_member_id, m.member_role, p.display_name, p.local_alias
FROM chat_item_mentions r
LEFT JOIN group_members m ON r.group_id = m.group_id AND r.member_id = m.member_id
- LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
+ LEFT JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id)
WHERE r.chat_item_id = ?
Plan:
@@ -3475,21 +3407,6 @@ Query:
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-Query:
- DELETE FROM chat_items
- WHERE group_scope_group_member_id = ?
-
-Plan:
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_group_member_id (group_scope_group_member_id=?)
-SEARCH chat_item_mentions USING COVERING INDEX idx_chat_item_mentions_chat_item_id (chat_item_id=?)
-SEARCH group_snd_item_statuses USING COVERING INDEX idx_group_snd_item_statuses_chat_item_id (chat_item_id=?)
-SEARCH chat_item_versions USING COVERING INDEX idx_chat_item_versions_chat_item_id (chat_item_id=?)
-SEARCH calls USING COVERING INDEX idx_calls_chat_item_id (chat_item_id=?)
-SEARCH chat_item_messages USING COVERING INDEX sqlite_autoindex_chat_item_messages_2 (chat_item_id=?)
-SEARCH chat_items USING COVERING INDEX idx_chat_items_fwd_from_chat_item_id (fwd_from_chat_item_id=?)
-SEARCH files USING COVERING INDEX idx_files_chat_item_id (chat_item_id=?)
-SEARCH groups USING COVERING INDEX idx_groups_chat_item_id (chat_item_id=?)
-
Query:
DELETE FROM chat_items
WHERE user_id = ? AND contact_id = ? AND chat_item_id = ?
@@ -4007,7 +3924,7 @@ Plan:
Query:
INSERT INTO chat_items (
-- user and IDs
- user_id, created_by_msg_id, contact_id, group_id, group_member_id, note_folder_id, group_scope_tag, group_scope_group_member_id,
+ user_id, created_by_msg_id, contact_id, group_id, group_member_id, note_folder_id,
-- meta
item_sent, item_ts, item_content, item_content_tag, item_text, item_status, msg_content_tag, shared_msg_id,
forwarded_by_group_member_id, include_in_history, created_at, updated_at, item_live, user_mention, timed_ttl, timed_delete_at,
@@ -4015,7 +3932,7 @@ Query:
quoted_shared_msg_id, quoted_sent_at, quoted_content, quoted_sent, quoted_member_id,
-- forwarded from
fwd_from_tag, fwd_from_chat_name, fwd_from_msg_dir, fwd_from_contact_id, fwd_from_group_id, fwd_from_chat_item_id
- ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
Plan:
@@ -4298,14 +4215,6 @@ Query:
Plan:
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
-Query:
- UPDATE connections
- SET user_id = ?, custom_user_profile_id = NULL, updated_at = ?
- WHERE user_id = ? AND connection_id = ?
-
-Plan:
-SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
UPDATE contact_profiles
SET contact_link = ?, updated_at = ?
@@ -4413,19 +4322,6 @@ Query:
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-Query:
- UPDATE group_members
- SET support_chat_ts = NULL,
- support_chat_items_unread = 0,
- support_chat_items_member_attention = 0,
- support_chat_items_mentions = 0,
- support_chat_last_msg_from_member_ts = NULL,
- updated_at = ?
- WHERE group_member_id = ?
-
-Plan:
-SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
UPDATE group_profiles
SET preferences = ?, updated_at = ?
@@ -4456,22 +4352,6 @@ Query:
Plan:
SEARCH group_snd_item_statuses USING INDEX idx_group_snd_item_statuses_chat_item_id_group_member_id (chat_item_id=? AND group_member_id=?)
-Query:
- UPDATE groups
- SET members_require_attention = members_require_attention + 1
- WHERE user_id = ? AND group_id = ?
-
-Plan:
-SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
-
-Query:
- UPDATE groups
- SET members_require_attention = members_require_attention - 1
- WHERE user_id = ? AND group_id = ?
-
-Plan:
-SEARCH groups USING INTEGER PRIMARY KEY (rowid=?)
-
Query:
UPDATE groups
SET via_group_link_uri_hash = (SELECT via_contact_uri_hash FROM connections WHERE connection_id = ?)
@@ -4571,14 +4451,12 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
- mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
+ mu.created_at, mu.updated_at
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
JOIN group_members mu ON mu.group_id = g.group_id
@@ -4595,14 +4473,12 @@ Query:
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
- mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
+ mu.created_at, mu.updated_at
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
JOIN group_members mu ON mu.group_id = g.group_id
@@ -4619,7 +4495,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4652,7 +4527,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4677,7 +4551,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4702,7 +4575,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4727,7 +4599,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4752,7 +4623,6 @@ Query:
m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction,
m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences,
m.created_at, m.updated_at,
- m.support_chat_ts, m.support_chat_items_unread, m.support_chat_items_member_attention, m.support_chat_items_mentions, m.support_chat_last_msg_from_member_ts,
c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id,
c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id,
c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, c.quota_err_counter,
@@ -4799,7 +4669,7 @@ Query:
JOIN files f ON f.chat_item_id = i.chat_item_id
WHERE i.user_id = ?
Plan:
-SEARCH i USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
+SEARCH i USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=?)
SEARCH f USING INDEX idx_files_chat_item_id (chat_item_id=?)
Query:
@@ -5061,44 +4931,36 @@ Query:
Plan:
SCAN usage_conditions
-Query: SELECT chat_item_id FROM chat_items WHERE (( user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_ts < ? ) OR ( user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_ts = ? AND chat_item_id < ? )) ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
+Query: SELECT chat_item_id FROM chat_items WHERE (( user_id = ? AND group_id = ? AND item_ts < ? ) OR ( user_id = ? AND group_id = ? AND item_ts = ? AND chat_item_id < ? )) ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
Plan:
MULTI-INDEX OR
INDEX 1
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_ts)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=? AND item_ts)
INDEX 2
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_ts=? AND rowid)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=? AND item_ts=? AND rowid)
USE TEMP B-TREE FOR ORDER BY
-Query: SELECT chat_item_id FROM chat_items WHERE (( user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_ts > ? ) OR ( user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_ts = ? AND chat_item_id > ? )) ORDER BY item_ts ASC, chat_item_id ASC LIMIT ?
+Query: SELECT chat_item_id FROM chat_items WHERE (( user_id = ? AND group_id = ? AND item_ts > ? ) OR ( user_id = ? AND group_id = ? AND item_ts = ? AND chat_item_id > ? )) ORDER BY item_ts ASC, chat_item_id ASC LIMIT ?
Plan:
MULTI-INDEX OR
INDEX 1
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_ts>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=? AND item_ts>?)
INDEX 2
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_ts=? AND rowid>?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=? AND item_ts=? AND rowid>?)
USE TEMP B-TREE FOR ORDER BY
-Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag = ? AND group_scope_group_member_id = ? ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
+Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND LOWER(item_text) LIKE '%' || LOWER(?) || '%' ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
Plan:
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=?)
-
-Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag = ? AND group_scope_group_member_id IS NULL ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
-Plan:
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=?)
-
-Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND LOWER(item_text) LIKE '%' || LOWER(?) || '%' ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
-Plan:
-SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=?)
-
-Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
-Plan:
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=?)
+SEARCH chat_items USING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND msg_content_tag = ? ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
Plan:
SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_msg_content_tag_item_ts (user_id=? AND group_id=? AND msg_content_tag=?)
+Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? ORDER BY item_ts DESC, chat_item_id DESC LIMIT ?
+Plan:
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_item_ts (user_id=? AND group_id=?)
+
Query: CREATE TABLE temp_conn_ids (conn_id BLOB)
Error: SQLite3 returned ErrorError while attempting to perform prepare "explain query plan CREATE TABLE temp_conn_ids (conn_id BLOB)": table temp_conn_ids already exists
@@ -5282,7 +5144,6 @@ SEARCH sent_probes USING COVERING INDEX idx_sent_probes_group_member_id (group_m
SEARCH group_snd_item_statuses USING COVERING INDEX idx_group_snd_item_statuses_group_member_id (group_member_id=?)
SEARCH chat_item_moderations USING COVERING INDEX idx_chat_item_moderations_moderator_member_id (moderator_member_id=?)
SEARCH chat_item_reactions USING COVERING INDEX idx_chat_item_reactions_group_member_id (group_member_id=?)
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_group_member_id (group_scope_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_forwarded_by_group_member_id (forwarded_by_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_item_deleted_by_group_member_id (item_deleted_by_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_member_id (group_member_id=?)
@@ -5306,7 +5167,6 @@ SEARCH sent_probes USING COVERING INDEX idx_sent_probes_group_member_id (group_m
SEARCH group_snd_item_statuses USING COVERING INDEX idx_group_snd_item_statuses_group_member_id (group_member_id=?)
SEARCH chat_item_moderations USING COVERING INDEX idx_chat_item_moderations_moderator_member_id (moderator_member_id=?)
SEARCH chat_item_reactions USING COVERING INDEX idx_chat_item_reactions_group_member_id (group_member_id=?)
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_group_member_id (group_scope_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_forwarded_by_group_member_id (forwarded_by_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_item_deleted_by_group_member_id (item_deleted_by_group_member_id=?)
SEARCH chat_items USING COVERING INDEX idx_chat_items_group_member_id (group_member_id=?)
@@ -5441,7 +5301,7 @@ SEARCH protocol_servers USING COVERING INDEX idx_smp_servers_user_id (user_id=?)
SEARCH settings USING COVERING INDEX idx_settings_user_id (user_id=?)
SEARCH commands USING COVERING INDEX idx_commands_user_id (user_id=?)
SEARCH calls USING COVERING INDEX idx_calls_user_id (user_id=?)
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_ts (user_id=?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=?)
SEARCH contact_requests USING COVERING INDEX sqlite_autoindex_contact_requests_2 (user_id=?)
SEARCH user_contact_links USING COVERING INDEX sqlite_autoindex_user_contact_links_1 (user_id=?)
SEARCH connections USING COVERING INDEX idx_connections_group_member (user_id=?)
@@ -5598,9 +5458,9 @@ Query: SELECT COUNT(1) FROM groups WHERE user_id = ? AND chat_item_ttl > 0
Plan:
SEARCH groups USING INDEX idx_groups_chat_ts (user_id=?)
-Query: SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ?
+Query: SELECT COUNT(1), COALESCE(SUM(user_mention), 0) FROM chat_items WHERE user_id = ? AND group_id = ? AND item_status = ?
Plan:
-SEARCH chat_items USING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups_user_mention (user_id=? AND group_id=? AND item_status=?)
Query: SELECT accepted_at FROM operator_usage_conditions WHERE server_operator_id = ? AND conditions_commit = ?
Plan:
@@ -5626,9 +5486,9 @@ Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND contact_id = ?
Plan:
SEARCH chat_items USING INDEX idx_chat_items_direct_shared_msg_id (user_id=? AND contact_id=? AND shared_msg_id=?)
-Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_scope_tag IS NULL AND group_scope_group_member_id IS NULL AND item_status = ? ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1
+Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND item_status = ? ORDER BY item_ts ASC, chat_item_id ASC LIMIT 1
Plan:
-SEARCH chat_items USING COVERING INDEX idx_chat_items_group_scope_item_status (user_id=? AND group_id=? AND group_scope_tag=? AND group_scope_group_member_id=? AND item_status=?)
+SEARCH chat_items USING COVERING INDEX idx_chat_items_groups (user_id=? AND group_id=? AND item_status=?)
Query: SELECT chat_item_id FROM chat_items WHERE user_id = ? AND group_id = ? AND group_member_id = ? LIMIT 1
Plan:
@@ -5782,10 +5642,6 @@ Query: SELECT quota_err_counter FROM connections WHERE user_id = ? AND connectio
Plan:
SEARCH connections USING INTEGER PRIMARY KEY (rowid=?)
-Query: SELECT re_group_member_id FROM group_member_intros WHERE to_group_member_id = ?
-Plan:
-SEARCH group_member_intros USING INDEX idx_group_member_intros_to_group_member_id (to_group_member_id=?)
-
Query: SELECT sent_inv_queue_info FROM group_members WHERE group_member_id = ? AND user_id = ?
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
@@ -5986,7 +5842,11 @@ Query: UPDATE group_members SET member_role = ? WHERE user_id = ? AND group_memb
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
-Query: UPDATE group_members SET support_chat_ts = ? WHERE group_member_id = ?
+Query: UPDATE group_members SET member_status='pending_approval' WHERE group_member_id = 1
+Plan:
+SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
+
+Query: UPDATE group_members SET member_status='pending_approval' WHERE group_member_id = 2
Plan:
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
diff --git a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql
index 91670ffc7b..6fbed97d27 100644
--- a/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql
+++ b/src/Simplex/Chat/Store/SQLite/Migrations/chat_schema.sql
@@ -134,8 +134,7 @@ CREATE TABLE groups(
business_xcontact_id BLOB NULL,
customer_member_id BLOB NULL,
chat_item_ttl INTEGER,
- local_alias TEXT DEFAULT '',
- members_require_attention INTEGER NOT NULL DEFAULT 0, -- received
+ local_alias TEXT DEFAULT '', -- received
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -168,11 +167,6 @@ CREATE TABLE group_members(
peer_chat_min_version INTEGER NOT NULL DEFAULT 1,
peer_chat_max_version INTEGER NOT NULL DEFAULT 1,
member_restriction TEXT,
- support_chat_ts TEXT,
- support_chat_items_unread INTEGER NOT NULL DEFAULT 0,
- support_chat_items_member_attention INTEGER NOT NULL DEFAULT 0,
- support_chat_items_mentions INTEGER NOT NULL DEFAULT 0,
- support_chat_last_msg_from_member_ts TEXT,
FOREIGN KEY(user_id, local_display_name)
REFERENCES display_names(user_id, local_display_name)
ON DELETE CASCADE
@@ -417,9 +411,7 @@ CREATE TABLE chat_items(
via_proxy INTEGER,
msg_content_tag TEXT,
include_in_history INTEGER NOT NULL DEFAULT 0,
- user_mention INTEGER NOT NULL DEFAULT 0,
- group_scope_tag TEXT,
- group_scope_group_member_id INTEGER REFERENCES group_members(group_member_id) ON DELETE CASCADE
+ user_mention INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE sqlite_sequence(name,seq);
CREATE TABLE chat_item_messages(
@@ -1030,21 +1022,3 @@ CREATE INDEX idx_chat_items_group_id_shared_msg_id ON chat_items(
group_id,
shared_msg_id
);
-CREATE INDEX idx_chat_items_group_scope_group_member_id ON chat_items(
- group_scope_group_member_id
-);
-CREATE INDEX idx_chat_items_group_scope_item_ts ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_ts
-);
-CREATE INDEX idx_chat_items_group_scope_item_status ON chat_items(
- user_id,
- group_id,
- group_scope_tag,
- group_scope_group_member_id,
- item_status,
- item_ts
-);
diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs
index 8c9c50e9de..b32fd07bb5 100644
--- a/src/Simplex/Chat/Store/Shared.hs
+++ b/src/Simplex/Chat/Store/Shared.hs
@@ -579,37 +579,27 @@ safeDeleteLDN db User {userId} localDisplayName = do
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
-type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64, Int) :. GroupMemberRow
+type GroupInfoRow = (Int64, GroupName, GroupName, Text, Text, Maybe Text, Maybe ImageData, Maybe MsgFilter, Maybe BoolInt, BoolInt, Maybe GroupPreferences, Maybe GroupMemberAdmission) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData, Maybe Int64) :. GroupMemberRow
-type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime) :. (Maybe UTCTime, Int64, Int64, Int64, Maybe UTCTime)
+type GroupMemberRow = (Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, BoolInt, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnLinkContact, LocalAlias, Maybe Preferences) :. (UTCTime, UTCTime)
toGroupInfo :: VersionRangeChat -> Int64 -> [ChatTagId] -> GroupInfoRow -> GroupInfo
-toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL, membersRequireAttention) :. userMemberRow) =
+toGroupInfo vr userContactId chatTags ((groupId, localDisplayName, displayName, fullName, localAlias, description, image, enableNtfs_, sendRcpts, BI favorite, groupPreferences, memberAdmission) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData, chatItemTTL) :. userMemberRow) =
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts = unBI <$> sendRcpts, favorite}
fullGroupPreferences = mergeGroupPreferences groupPreferences
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences, memberAdmission}
businessChat = toBusinessChatInfo businessRow
- in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData, membersRequireAttention}
+ in GroupInfo {groupId, localDisplayName, groupProfile, localAlias, businessChat, fullGroupPreferences, membership, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, chatTags, chatItemTTL, uiThemes, customData}
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
-toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences) :. (createdAt, updatedAt) :. (supportChatTs_, supportChatUnread, supportChatMemberAttention, supportChatMentions, supportChatLastMsgFromMemberTs)) =
+toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, memberRole, memberCategory, memberStatus, BI showMessages, memberRestriction_) :. (invitedById, invitedByGroupMemberId, localDisplayName, memberContactId, memberContactProfileId, profileId, displayName, fullName, image, contactLink, localAlias, preferences) :. (createdAt, updatedAt)) =
let memberProfile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias}
memberSettings = GroupMemberSettings {showMessages}
blockedByAdmin = maybe False mrsBlocked memberRestriction_
invitedBy = toInvitedBy userContactId invitedById
activeConn = Nothing
memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
- supportChat = case supportChatTs_ of
- Just chatTs ->
- Just GroupSupportChat {
- chatTs,
- unread = supportChatUnread,
- memberAttention = supportChatMemberAttention,
- mentions = supportChatMentions,
- lastMsgFromMemberTs = supportChatLastMsgFromMemberTs
- }
- _ -> Nothing
in GroupMember {..}
toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo
@@ -623,14 +613,12 @@ groupInfoQuery =
-- GroupInfo
g.group_id, g.local_display_name, gp.display_name, gp.full_name, g.local_alias, gp.description, gp.image,
g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences, gp.member_admission,
- g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id,
- g.ui_themes, g.custom_data, g.chat_item_ttl, g.members_require_attention,
+ g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data, g.chat_item_ttl,
-- GroupMember - membership
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences,
- mu.created_at, mu.updated_at,
- mu.support_chat_ts, mu.support_chat_items_unread, mu.support_chat_items_member_attention, mu.support_chat_items_mentions, mu.support_chat_last_msg_from_member_ts
+ mu.created_at, mu.updated_at
FROM groups g
JOIN group_profiles gp ON gp.group_profile_id = g.group_profile_id
JOIN group_members mu ON mu.group_id = g.group_id
diff --git a/src/Simplex/Chat/Terminal/Output.hs b/src/Simplex/Chat/Terminal/Output.hs
index 1ec09d43d6..79fc08397c 100644
--- a/src/Simplex/Chat/Terminal/Output.hs
+++ b/src/Simplex/Chat/Terminal/Output.hs
@@ -183,7 +183,7 @@ chatEventNotification t@ChatTerminal {sendNotification} cc = \case
whenCurrUser cc u $ setActiveChat t cInfo
case (cInfo, chatDir) of
(DirectChat ct, _) -> sendNtf (viewContactName ct <> "> ", text)
- (GroupChat g scopeInfo, CIGroupRcv m) -> sendNtf (fromGroup_ g scopeInfo m, text)
+ (GroupChat g, CIGroupRcv m) -> sendNtf (fromGroup_ g m, text)
_ -> pure ()
where
text = msgText mc formattedText
@@ -207,8 +207,6 @@ chatEventNotification t@ChatTerminal {sendNotification} cc = \case
sendNtf ("#" <> viewGroupName g, "you are connected to group")
CEvtJoinedGroupMember u g m ->
when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected")
- CEvtJoinedGroupMemberConnecting u g _ m | memberStatus m == GSMemPendingReview ->
- when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is pending review")
CEvtConnectedToGroupMember u g m _ ->
when (groupNtf u g False) $ sendNtf ("#" <> viewGroupName g, "member " <> viewMemberName m <> " is connected")
CEvtReceivedContactRequest u UserContactRequest {localDisplayName = n} ->
@@ -235,7 +233,7 @@ chatActiveTo (ChatName cType name) = case cType of
chatInfoActiveTo :: ChatInfo c -> String
chatInfoActiveTo = \case
DirectChat c -> contactActiveTo c
- GroupChat g _scopeInfo -> groupActiveTo g
+ GroupChat g -> groupActiveTo g
_ -> ""
contactActiveTo :: Contact -> String
diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs
index 567baf0cdf..0063f711c7 100644
--- a/src/Simplex/Chat/Types.hs
+++ b/src/Simplex/Chat/Types.hs
@@ -421,8 +421,7 @@ data GroupInfo = GroupInfo
chatTags :: [ChatTagId],
chatItemTTL :: Maybe Int64,
uiThemes :: Maybe UIThemeEntityOverrides,
- customData :: Maybe CustomData,
- membersRequireAttention :: Int
+ customData :: Maybe CustomData
}
deriving (Eq, Show)
@@ -842,27 +841,10 @@ data GroupMember = GroupMember
-- but it's correctly set on read (see toGroupInfo)
memberChatVRange :: VersionRangeChat,
createdAt :: UTCTime,
- updatedAt :: UTCTime,
- supportChat :: Maybe GroupSupportChat
+ updatedAt :: UTCTime
}
deriving (Eq, Show)
-data GroupSupportChat = GroupSupportChat
- { chatTs :: UTCTime,
- unread :: Int64,
- memberAttention :: Int64,
- mentions :: Int64,
- lastMsgFromMemberTs :: Maybe UTCTime
- }
- deriving (Eq, Show)
-
-gmRequiresAttention :: GroupMember -> Bool
-gmRequiresAttention m@GroupMember {supportChat} =
- memberPending m || maybe False supportChatAttention supportChat
- where
- supportChatAttention GroupSupportChat {memberAttention, mentions} =
- memberAttention > 0 || mentions > 0
-
data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, profile :: Profile}
deriving (Eq, Show)
@@ -890,9 +872,6 @@ supportsVersion m v = maxVersion (memberChatVRange' m) >= v
groupMemberId' :: GroupMember -> GroupMemberId
groupMemberId' GroupMember {groupMemberId} = groupMemberId
-memberId' :: GroupMember -> MemberId
-memberId' GroupMember {memberId} = memberId
-
memberIncognito :: GroupMember -> IncognitoEnabled
memberIncognito GroupMember {memberProfile, memberContactProfileId} = localProfileId memberProfile /= memberContactProfileId
@@ -1036,7 +1015,6 @@ data GroupMemberStatus
| GSMemUnknown -- unknown member, whose message was forwarded by an admin (likely member wasn't introduced due to not being a current member, but message was included in history)
| GSMemInvited -- member is sent to or received invitation to join the group
| GSMemPendingApproval -- member is connected to host but pending host approval before connecting to other members ("knocking")
- | GSMemPendingReview -- member is introduced to admins but pending admin review before connecting to other members ("knocking")
| GSMemIntroduced -- user received x.grp.mem.intro for this member (only with GCPreMember)
| GSMemIntroInvited -- member is sent to or received from intro invitation
| GSMemAccepted -- member accepted invitation (only User and Invitee)
@@ -1057,12 +1035,10 @@ instance ToJSON GroupMemberStatus where
toJSON = J.String . textEncode
toEncoding = JE.text . textEncode
-acceptanceToStatus :: Maybe GroupMemberAdmission -> GroupAcceptance -> GroupMemberStatus
-acceptanceToStatus memberAdmission groupAcceptance
- | groupAcceptance == GAPendingApproval = GSMemPendingApproval
- | groupAcceptance == GAPendingReview = GSMemPendingReview
- | (memberAdmission >>= review) == Just MCAll = GSMemPendingReview
- | otherwise = GSMemAccepted
+acceptanceToStatus :: GroupAcceptance -> GroupMemberStatus
+acceptanceToStatus = \case
+ GAAccepted -> GSMemAccepted
+ GAPending -> GSMemPendingApproval
memberActive :: GroupMember -> Bool
memberActive m = case memberStatus m of
@@ -1073,7 +1049,6 @@ memberActive m = case memberStatus m of
GSMemUnknown -> False
GSMemInvited -> False
GSMemPendingApproval -> True
- GSMemPendingReview -> True
GSMemIntroduced -> False
GSMemIntroInvited -> False
GSMemAccepted -> False
@@ -1085,15 +1060,6 @@ memberActive m = case memberStatus m of
memberCurrent :: GroupMember -> Bool
memberCurrent = memberCurrent' . memberStatus
-memberPending :: GroupMember -> Bool
-memberPending m = case memberStatus m of
- GSMemPendingApproval -> True
- GSMemPendingReview -> True
- _ -> False
-
-memberCurrentOrPending :: GroupMember -> Bool
-memberCurrentOrPending m = memberCurrent m || memberPending m
-
-- update getGroupSummary if this is changed
memberCurrent' :: GroupMemberStatus -> Bool
memberCurrent' = \case
@@ -1104,7 +1070,6 @@ memberCurrent' = \case
GSMemUnknown -> False
GSMemInvited -> False
GSMemPendingApproval -> False
- GSMemPendingReview -> False
GSMemIntroduced -> True
GSMemIntroInvited -> True
GSMemAccepted -> True
@@ -1122,7 +1087,6 @@ memberRemoved m = case memberStatus m of
GSMemUnknown -> False
GSMemInvited -> False
GSMemPendingApproval -> False
- GSMemPendingReview -> False
GSMemIntroduced -> False
GSMemIntroInvited -> False
GSMemAccepted -> False
@@ -1140,7 +1104,6 @@ instance TextEncoding GroupMemberStatus where
"unknown" -> Just GSMemUnknown
"invited" -> Just GSMemInvited
"pending_approval" -> Just GSMemPendingApproval
- "pending_review" -> Just GSMemPendingReview
"introduced" -> Just GSMemIntroduced
"intro-inv" -> Just GSMemIntroInvited
"accepted" -> Just GSMemAccepted
@@ -1157,7 +1120,6 @@ instance TextEncoding GroupMemberStatus where
GSMemUnknown -> "unknown"
GSMemInvited -> "invited"
GSMemPendingApproval -> "pending_approval"
- GSMemPendingReview -> "pending_review"
GSMemIntroduced -> "introduced"
GSMemIntroInvited -> "intro-inv"
GSMemAccepted -> "accepted"
@@ -1894,8 +1856,6 @@ $(JQ.deriveJSON defaultJSON ''Connection)
$(JQ.deriveJSON defaultJSON ''PendingContactConnection)
-$(JQ.deriveJSON defaultJSON ''GroupSupportChat)
-
$(JQ.deriveJSON defaultJSON ''GroupMember)
$(JQ.deriveJSON (enumJSON $ dropPrefix "MF") ''MsgFilter)
diff --git a/src/Simplex/Chat/Types/Shared.hs b/src/Simplex/Chat/Types/Shared.hs
index 60ebe9d033..4c6adae4e9 100644
--- a/src/Simplex/Chat/Types/Shared.hs
+++ b/src/Simplex/Chat/Types/Shared.hs
@@ -49,17 +49,20 @@ instance ToJSON GroupMemberRole where
toJSON = strToJSON
toEncoding = strToJEncoding
-data GroupAcceptance = GAAccepted | GAPendingApproval | GAPendingReview deriving (Eq, Show)
+data GroupAcceptance = GAAccepted | GAPending deriving (Eq, Show)
+
+-- TODO [knocking] encoding doesn't match field type
+instance FromField GroupAcceptance where fromField = blobFieldDecoder strDecode
+
+instance ToField GroupAcceptance where toField = toField . strEncode
instance StrEncoding GroupAcceptance where
strEncode = \case
GAAccepted -> "accepted"
- GAPendingApproval -> "pending"
- GAPendingReview -> "pending_review"
+ GAPending -> "pending"
strDecode = \case
"accepted" -> Right GAAccepted
- "pending" -> Right GAPendingApproval
- "pending_review" -> Right GAPendingReview
+ "pending" -> Right GAPending
r -> Left $ "bad GroupAcceptance " <> B.unpack r
strP = strDecode <$?> A.takeByteString
diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs
index c13b164693..4ba5acbb43 100644
--- a/src/Simplex/Chat/View.hs
+++ b/src/Simplex/Chat/View.hs
@@ -134,9 +134,9 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
"server queue info: " <> viewJSON qInfo
]
CRContactSwitchStarted {} -> ["switch started"]
- CEvtGroupMemberSwitchStarted {} -> ["switch started"]
+ CRGroupMemberSwitchStarted {} -> ["switch started"]
CRContactSwitchAborted {} -> ["switch aborted"]
- CEvtGroupMemberSwitchAborted {} -> ["switch aborted"]
+ CRGroupMemberSwitchAborted {} -> ["switch aborted"]
CRContactRatchetSyncStarted {} -> ["connection synchronization started"]
CRGroupMemberRatchetSyncStarted {} -> ["connection synchronization started"]
CRConnectionVerified u verified code -> ttyUser u [plain $ if verified then "connection verified" else "connection not verified, current code is " <> code]
@@ -174,9 +174,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
CRContactRequestRejected u UserContactRequest {localDisplayName = c} -> ttyUser u [ttyContact c <> ": contact request rejected"]
CRGroupCreated u g -> ttyUser u $ viewGroupCreated g testView
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
- CRMemberSupportChats u g ms -> ttyUser u $ viewMemberSupportChats g ms
- -- CRGroupConversationsArchived u _g _conversations -> ttyUser u []
- -- CRGroupConversationsDeleted u _g _conversations -> ttyUser u []
CRGroupsList u gs -> ttyUser u $ viewGroupsList gs
CRSentGroupInvitation u g c _ -> ttyUser u $ viewSentGroupInvitation g c
CRFileTransferStatus u ftStatus -> ttyUser u $ viewFileTransferStatus ftStatus
@@ -192,7 +189,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
CRSentConfirmation u _ -> ttyUser u ["confirmation sent!"]
CRSentInvitation u _ customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
CRSentInvitationToContact u _c customUserProfile -> ttyUser u $ viewSentInvitation customUserProfile testView
- CRItemsReadForChat u _chatId -> ttyUser u ["items read for chat"]
CRContactDeleted u c -> ttyUser u [ttyContact' c <> ": contact is deleted"]
CRChatCleared u chatInfo -> ttyUser u $ viewChatCleared chatInfo
CRAcceptingContactRequest u c -> ttyUser u $ viewAcceptingContactRequest c
@@ -221,8 +217,6 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_
CRNetworkStatuses u statuses -> if testView then ttyUser' u $ viewNetworkStatuses statuses else []
CRJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
- CRMemberAccepted u g m -> ttyUser u $ viewMemberAccepted g m
- CRMemberSupportChatDeleted u g m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember m <> " support chat deleted"]
CRMembersRoleUser u g members r' -> ttyUser u $ viewMemberRoleUserChanged g members r'
CRMembersBlockedForAllUser u g members blocked -> ttyUser u $ viewMembersBlockedForAllUser g members blocked
CRGroupUpdated u g g' m -> ttyUser u $ viewGroupUpdated g g' m
@@ -317,7 +311,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
where
toChatView :: AChat -> (Text, Text, Maybe ConnStatus)
toChatView (AChat _ (Chat (DirectChat Contact {localDisplayName, activeConn}) items _)) = ("@" <> localDisplayName, toCIPreview items Nothing, connStatus <$> activeConn)
- toChatView (AChat _ (Chat (GroupChat GroupInfo {membership, localDisplayName} _scopeInfo) items _)) = ("#" <> localDisplayName, toCIPreview items (Just membership), Nothing)
+ toChatView (AChat _ (Chat (GroupChat GroupInfo {membership, localDisplayName}) items _)) = ("#" <> localDisplayName, toCIPreview items (Just membership), Nothing)
toChatView (AChat _ (Chat (LocalChat _) items _)) = ("*", toCIPreview items Nothing, Nothing)
toChatView (AChat _ (Chat (ContactRequest UserContactRequest {localDisplayName}) items _)) = ("<@" <> localDisplayName, toCIPreview items Nothing, Nothing)
toChatView (AChat _ (Chat (ContactConnection PendingContactConnection {pccConnId, pccConnStatus}) items _)) = (":" <> T.pack (show pccConnId), toCIPreview items Nothing, Just pccConnStatus)
@@ -463,9 +457,8 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
CEvtJoinedGroupMember u g m -> ttyUser u $ viewJoinedGroupMember g m
CEvtHostConnected p h -> [plain $ "connected to " <> viewHostEvent p h]
CEvtHostDisconnected p h -> [plain $ "disconnected from " <> viewHostEvent p h]
- CEvtJoinedGroupMemberConnecting u g host m -> ttyUser u $ viewJoinedGroupMemberConnecting g host m
- CEvtConnectedToGroupMember u g m _ -> ttyUser u $ viewConnectedToGroupMember g m
- CEvtMemberAcceptedByOther u g acceptingMember m -> ttyUser u $ viewMemberAcceptedByOther g acceptingMember m
+ CEvtJoinedGroupMemberConnecting u g host m -> ttyUser u [ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting...)"]
+ CEvtConnectedToGroupMember u g m _ -> ttyUser u [ttyGroup' g <> ": " <> connectedMember m <> " is connected"]
CEvtMemberRole u g by m r r' -> ttyUser u $ viewMemberRoleChanged g by m r r'
CEvtMemberBlockedForAll u g by m blocked -> ttyUser u $ viewMemberBlockedForAll g by m blocked
CEvtDeletedMemberUser u g by wm -> ttyUser u $ [ttyGroup' g <> ": " <> ttyMember by <> " removed you from the group" <> withMessages wm] <> groupPreserved g
@@ -556,7 +549,7 @@ userNtf User {showNtfs, activeUser} = showNtfs || activeUser
chatDirNtf :: User -> ChatInfo c -> CIDirection c d -> Bool -> Bool
chatDirNtf user cInfo chatDir mention = case (cInfo, chatDir) of
(DirectChat ct, CIDirectRcv) -> contactNtf user ct mention
- (GroupChat g _scopeInfo, CIGroupRcv m) -> groupNtf user g mention && not (blockedByAdmin m) && showMessages (memberSettings m)
+ (GroupChat g, CIGroupRcv m) -> groupNtf user g mention && not (blockedByAdmin m) && showMessages (memberSettings m)
_ -> True
contactNtf :: User -> Contact -> Bool -> Bool
@@ -626,7 +619,7 @@ viewChats ts tz = concatMap chatPreview . reverse
where
chatName = case chat of
DirectChat ct -> [" " <> ttyToContact' ct]
- GroupChat g scopeInfo -> [" " <> ttyToGroup g scopeInfo]
+ GroupChat g -> [" " <> ttyToGroup g]
_ -> []
viewChatItems ::
@@ -671,22 +664,22 @@ viewChatItem chat ci@ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, forwa
(maybe [] forwardedFrom itemForwarded)
(directQuote chatDir)
quotedItem
- GroupChat g scopeInfo -> case chatDir of
+ GroupChat g -> case chatDir of
CIGroupSnd -> case content of
CISndMsgContent mc -> hideLive meta $ withSndFile to $ sndMsg to context mc
CISndGroupInvitation {} -> showSndItemProhibited to
_ -> showSndItem to
where
- to = ttyToGroup g scopeInfo
+ to = ttyToGroup g
CIGroupRcv m -> case content of
CIRcvMsgContent mc -> withRcvFile from $ rcvMsg from context mc
CIRcvIntegrityError err -> viewRcvIntegrityError from err ts tz meta
CIRcvGroupInvitation {} -> showRcvItemProhibited from
- CIRcvModerated {} -> receivedWithTime_ ts tz (ttyFromGroup g scopeInfo m) context meta [plainContent content] False
- CIRcvBlocked {} -> receivedWithTime_ ts tz (ttyFromGroup g scopeInfo m) context meta [plainContent content] False
+ CIRcvModerated {} -> receivedWithTime_ ts tz (ttyFromGroup g m) context meta [plainContent content] False
+ CIRcvBlocked {} -> receivedWithTime_ ts tz (ttyFromGroup g m) context meta [plainContent content] False
_ -> showRcvItem from
where
- from = ttyFromGroupAttention g scopeInfo m userMention
+ from = ttyFromGroupAttention g m userMention
where
context =
maybe
@@ -764,8 +757,8 @@ viewChatItemInfo (AChatItem _ msgDir _ ChatItem {meta = CIMeta {itemTs, itemTime
fwdDir_ = case (fwdMsgDir, fwdChatInfo) of
(SMDSnd, DirectChat ct) -> Just $ "you @" <> viewContactName ct
(SMDRcv, DirectChat ct) -> Just $ "@" <> viewContactName ct
- (SMDSnd, GroupChat gInfo _scopeInfo) -> Just $ "you #" <> viewGroupName gInfo
- (SMDRcv, GroupChat gInfo _scopeInfo) -> Just $ "#" <> viewGroupName gInfo
+ (SMDSnd, GroupChat gInfo) -> Just $ "you #" <> viewGroupName gInfo
+ (SMDRcv, GroupChat gInfo) -> Just $ "#" <> viewGroupName gInfo
_ -> Nothing
fwdItemId = "chat item id: " <> (T.pack . show $ aChatItemId fwdACI)
_ -> []
@@ -816,19 +809,19 @@ viewItemUpdate chat ChatItem {chatDir, meta = meta@CIMeta {itemForwarded, itemEd
(maybe [] forwardedFrom itemForwarded)
(directQuote chatDir)
quotedItem
- GroupChat g scopeInfo -> case chatDir of
+ GroupChat g -> case chatDir of
CIGroupRcv m -> case content of
CIRcvMsgContent mc
| itemLive == Just True && not liveItems -> []
| otherwise -> viewReceivedUpdatedMessage from context mc ts tz meta
_ -> []
where
- from = if itemEdited then ttyFromGroupEdited g scopeInfo m else ttyFromGroup g scopeInfo m
+ from = if itemEdited then ttyFromGroupEdited g m else ttyFromGroup g m
CIGroupSnd -> case content of
CISndMsgContent mc -> hideLive meta $ viewSentMessage to context mc ts tz meta
_ -> []
where
- to = if itemEdited then ttyToGroupEdited g scopeInfo else ttyToGroup g scopeInfo
+ to = if itemEdited then ttyToGroupEdited g else ttyToGroup g
where
context =
maybe
@@ -871,10 +864,10 @@ viewItemDelete chat ci@ChatItem {chatDir, meta, content = deletedContent} toItem
DirectChat c -> case (chatDir, deletedContent) of
(CIDirectRcv, CIRcvMsgContent mc) -> viewReceivedMessage (ttyFromContactDeleted c deletedText_) [] mc ts tz meta
_ -> prohibited
- GroupChat g scopeInfo -> case ciMsgContent deletedContent of
+ GroupChat g -> case ciMsgContent deletedContent of
Just mc ->
let m = chatItemMember g ci
- in viewReceivedMessage (ttyFromGroupDeleted g scopeInfo m deletedText_) [] mc ts tz meta
+ in viewReceivedMessage (ttyFromGroupDeleted g m deletedText_) [] mc ts tz meta
_ -> prohibited
_ -> prohibited
where
@@ -893,11 +886,11 @@ viewItemReaction showReactions chat CIReaction {chatDir, chatItem = CChatItem md
where
from = ttyFromContact c
reactionMsg mc = quoteText mc $ if toMsgDirection md == MDSnd then ">>" else ">"
- (GroupChat g scopeInfo, CIGroupRcv m) -> case ciMsgContent content of
+ (GroupChat g, CIGroupRcv m) -> case ciMsgContent content of
Just mc -> view from $ reactionMsg mc
_ -> []
where
- from = ttyFromGroup g scopeInfo m
+ from = ttyFromGroup g m
reactionMsg mc = quoteText mc . ttyQuotedMember . Just $ sentByMember' g itemDir
(LocalChat _, CILocalRcv) -> case ciMsgContent content of
Just mc -> view from $ reactionMsg mc
@@ -1004,7 +997,7 @@ viewContactNotFound cName suspectedMember =
viewChatCleared :: AChatInfo -> [StyledString]
viewChatCleared (AChatInfo _ chatInfo) = case chatInfo of
DirectChat ct -> [ttyContact' ct <> ": all messages are removed locally ONLY"]
- GroupChat gi _scopeInfo -> [ttyGroup' gi <> ": all messages are removed locally ONLY"]
+ GroupChat gi -> [ttyGroup' gi <> ": all messages are removed locally ONLY"]
LocalChat _ -> ["notes: all messages are removed"]
ContactRequest _ -> []
ContactConnection _ -> []
@@ -1165,7 +1158,6 @@ viewUserJoinedGroup g@GroupInfo {membership} =
where
pendingApproval_ = case memberStatus membership of
GSMemPendingApproval -> ", pending approval"
- GSMemPendingReview -> ", connecting to group moderators for admission to group"
_ -> ""
viewJoinedGroupMember :: GroupInfo -> GroupMember -> [StyledString]
@@ -1174,37 +1166,7 @@ viewJoinedGroupMember g@GroupInfo {groupId} m@GroupMember {groupMemberId, member
[ (ttyGroup' g <> ": " <> ttyMember m <> " connected and pending approval, ")
<> ("use " <> highlight ("/_accept member #" <> show groupId <> " " <> show groupMemberId <> " ") <> " to accept member")
]
- GSMemPendingReview -> [ttyGroup' g <> ": " <> ttyMember m <> " connected and pending review"]
- _ -> [ttyGroup' g <> ": " <> ttyMember m <> " joined the group"]
-
-viewMemberAccepted :: GroupInfo -> GroupMember -> [StyledString]
-viewMemberAccepted g m@GroupMember {memberStatus} = case memberStatus of
- GSMemPendingReview -> [ttyGroup' g <> ": " <> ttyMember m <> " accepted and pending review (will introduce moderators)"]
- _ -> [ttyGroup' g <> ": " <> ttyMember m <> " accepted"]
-
-viewMemberAcceptedByOther :: GroupInfo -> GroupMember -> GroupMember -> [StyledString]
-viewMemberAcceptedByOther g acceptingMember m@GroupMember {memberCategory, memberStatus} = case memberCategory of
- GCUserMember -> case memberStatus of
- GSMemPendingReview -> [ttyGroup' g <> ": " <> ttyMember acceptingMember <> " accepted you to the group, pending review"]
- _ -> [ttyGroup' g <> ": " <> ttyMember acceptingMember <> " accepted you to the group [warning - unexpected]"]
- GCInviteeMember -> [ttyGroup' g <> ": " <> ttyMember acceptingMember <> " accepted " <> ttyMember m <> " to the group (will introduce remaining members)"]
- _ -> [ttyGroup' g <> ": " <> ttyMember acceptingMember <> " accepted " <> ttyMember m <> " to the group"]
-
-viewJoinedGroupMemberConnecting :: GroupInfo -> GroupMember -> GroupMember -> [StyledString]
-viewJoinedGroupMemberConnecting g@GroupInfo {groupId} host m@GroupMember {groupMemberId, memberStatus} = case memberStatus of
- GSMemPendingReview ->
- [ (ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting and pending review...), ")
- <> ("use " <> highlight ("/_accept member #" <> show groupId <> " " <> show groupMemberId <> " ") <> " to accept member")
- ]
- _ -> [ttyGroup' g <> ": " <> ttyMember host <> " added " <> ttyFullMember m <> " to the group (connecting...)"]
-
-viewConnectedToGroupMember :: GroupInfo -> GroupMember -> [StyledString]
-viewConnectedToGroupMember g@GroupInfo {groupId} m@GroupMember {groupMemberId, memberStatus} = case memberStatus of
- GSMemPendingReview ->
- [ (ttyGroup' g <> ": " <> connectedMember m <> " is connected and pending review, ")
- <> ("use " <> highlight ("/_accept member #" <> show groupId <> " " <> show groupMemberId <> "