From f9c691cab167268aa3e95c351b87da3137b84df8 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:53:43 +0100 Subject: [PATCH] ios: change member role (#1164) * ios: change member role * chat item types, error alerts * update alert * translations * update messages * translation Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * translation Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * translation Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> * update translations Co-authored-by: JRoberts <8711996+jr-simplex@users.noreply.github.com> --- apps/ios/Shared/Model/SimpleXAPI.swift | 6 + .../Chat/Group/AddGroupMembersView.swift | 4 +- .../Chat/Group/GroupMemberInfoView.swift | 81 +++++++++++- .../Shared/Views/Database/DatabaseView.swift | 4 +- .../de.xcloc/Localized Contents/de.xliff | 120 +++++++++++++++++- .../en.xcloc/Localized Contents/en.xliff | 120 +++++++++++++++++- .../ru.xcloc/Localized Contents/ru.xliff | 120 +++++++++++++++++- apps/ios/SimpleXChat/APITypes.swift | 10 +- apps/ios/SimpleXChat/ChatTypes.swift | 34 ++++- apps/ios/de.lproj/Localizable.strings | 76 ++++++++++- apps/ios/ru.lproj/Localizable.strings | 76 ++++++++++- src/Simplex/Chat/Messages.hs | 2 +- 12 files changed, 614 insertions(+), 39 deletions(-) diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index c9492a7ae..768be40c9 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -702,6 +702,12 @@ func apiRemoveMember(_ groupId: Int64, _ memberId: Int64) async throws -> GroupM throw r } +func apiMemberRole(_ groupId: Int64, _ memberId: Int64, _ memberRole: GroupMemberRole) async throws -> GroupMember { + let r = await chatSendCmd(.apiMemberRole(groupId: groupId, memberId: memberId, memberRole: memberRole), bgTask: false) + if case let .memberRoleUser(_, member, _, _) = r { return member } + throw r +} + func leaveGroup(_ groupId: Int64) async { do { let groupInfo = try await apiLeaveGroup(groupId) diff --git a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift index 6ccd970cb..0e7d37cf7 100644 --- a/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift +++ b/apps/ios/Shared/Views/Chat/Group/AddGroupMembersView.swift @@ -130,9 +130,9 @@ struct AddGroupMembersView: View { } catch { switch error as? ChatResponse { case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): - alert = .error(title: "Connection timeout", error: "Please check your network connection and try again.") + alert = .error(title: "Connection timeout", error: NSLocalizedString("Please check your network connection and try again.", comment: "alert message")) case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): - alert = .error(title: "Connection error", error: "Please check your network connection and try again.") + alert = .error(title: "Connection error", error: NSLocalizedString("Please check your network connection and try again.", comment: "alert message")) default: alert = .error(title: "Error adding member(s)", error: responseError(error)) } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index e68378f4c..4040cb844 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -13,15 +13,24 @@ struct GroupMemberInfoView: View { @EnvironmentObject var chatModel: ChatModel @Environment(\.dismiss) var dismiss: DismissAction var groupInfo: GroupInfo - var member: GroupMember + @State var member: GroupMember var connectionStats: ConnectionStats? + @State private var newRole: GroupMemberRole = .member @State private var alert: GroupMemberInfoViewAlert? @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false enum GroupMemberInfoViewAlert: Identifiable { case removeMemberAlert + case changeMemberRoleAlert(role: GroupMemberRole) + case error(title: LocalizedStringKey, error: String) - var id: GroupMemberInfoViewAlert { get { self } } + var id: String { + switch self { + case .removeMemberAlert: return "removeMemberAlert" + case let .changeMemberRoleAlert(role): return "changeMemberRoleAlert \(role.rawValue)" + case let .error(title, _): return "error \(title)" + } + } } var body: some View { @@ -38,8 +47,29 @@ struct GroupMemberInfoView: View { Section("Member") { infoRow("Group", groupInfo.displayName) - // TODO change role - // localizedInfoRow("Role", member.memberRole.text) + + HStack { + if let roles = member.canChangeRoleTo(groupInfo: groupInfo) { + Picker("Change role", selection: $newRole) { + ForEach(roles) { role in + Text(role.text) + .foregroundStyle(.secondary) + } + } + } else { + Text("Role") + Spacer() + Text(member.memberRole.text) + .foregroundStyle(.secondary) + } + } + .onAppear { newRole = member.memberRole } + .onChange(of: newRole) { _ in + if newRole != member.memberRole { + alert = .changeMemberRoleAlert(role: newRole) + } + } + // TODO invited by - need to get contact by contact id if let conn = member.activeConn { let connLevelDesc = conn.connLevel == 0 ? NSLocalizedString("direct", comment: "connection level description") : String.localizedStringWithFormat(NSLocalizedString("indirect (%d)", comment: "connection level description"), conn.connLevel) @@ -55,7 +85,7 @@ struct GroupMemberInfoView: View { } } - if member.canBeRemoved(membership: groupInfo.membership) { + if member.canBeRemoved(groupInfo: groupInfo) { Section { removeMemberButton() } @@ -74,6 +104,8 @@ struct GroupMemberInfoView: View { .alert(item: $alert) { alertItem in switch(alertItem) { case .removeMemberAlert: return removeMemberAlert() + case .changeMemberRoleAlert: return changeMemberRoleAlert() + case let .error(title, error): return Alert(title: Text(title), message: Text(error)) } } } @@ -139,17 +171,54 @@ struct GroupMemberInfoView: View { do { let member = try await apiRemoveMember(groupInfo.groupId, member.groupMemberId) await MainActor.run { - _ = ChatModel.shared.upsertGroupMember(groupInfo, member) + _ = chatModel.upsertGroupMember(groupInfo, member) dismiss() } } catch let error { logger.error("apiRemoveMember error: \(responseError(error))") + alert = errorAlert(error, "Error removing member") } } }, secondaryButton: .cancel() ) } + + private func changeMemberRoleAlert() -> Alert { + Alert( + title: Text("Change member role?"), + message: member.memberCurrent ? Text("Member role will be changed to \"\(newRole.text)\". All group members will be notified.") : Text("Member role will be changed to \"\(newRole.text)\". The member will receive a new invitation."), + primaryButton: .default(Text("Change")) { + Task { + do { + let mem = try await apiMemberRole(groupInfo.groupId, member.groupMemberId, newRole) + await MainActor.run { + member = mem + _ = chatModel.upsertGroupMember(groupInfo, mem) + } + } catch let error { + newRole = member.memberRole + logger.error("apiMemberRole error: \(responseError(error))") + alert = errorAlert(error, "Error changing role") + } + } + }, + secondaryButton: .cancel { + newRole = member.memberRole + } + ) + } + + private func errorAlert(_ error: Error, _ title: LocalizedStringKey) -> GroupMemberInfoViewAlert { + switch error as? ChatResponse { + case .chatCmdError(.errorAgent(.BROKER(.TIMEOUT))): + return .error(title: "Connection timeout", error: NSLocalizedString("Please check your network connection and try again.", comment: "alert message")) + case .chatCmdError(.errorAgent(.BROKER(.NETWORK))): + return .error(title: "Connection error", error: NSLocalizedString("Please check your network connection and try again.", comment: "alert message")) + default: + return .error(title: title, error: responseError(error)) + } + } } struct GroupMemberInfoView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 4aa013810..edb6d2b70 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -278,7 +278,7 @@ struct DatabaseView: View { case let .setChatItemTTL(ttl): return Alert( title: Text("Enable automatic message deletion?"), - message: Text("This action cannot be undone - once you confirm, messages older than specified age will start to get deleted. It may take up to several minutes to delete old messages initially after changing this setting."), + message: Text("This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes."), primaryButton: .destructive(Text("Delete messages")) { setCiTTL(ttl) }, @@ -435,7 +435,7 @@ struct DatabaseView: View { } } catch { await MainActor.run { - alert = .error(title: "Error changing automatic message deletion", error: responseError(error)) + alert = .error(title: "Error changing setting", error: responseError(error)) chatItemTTL = currentChatItemTTL progressIndicator = false appFilesCountAndSize = directoryFileCountAndSize(getAppFilesDirectory()) 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 cec63b256..32f576659 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -92,6 +92,11 @@ %lld Mitglieder No comment provided by engineer. + + %lld second(s) + *** %lld second(s) + No comment provided by engineer. + %lldk %lldk @@ -172,6 +177,21 @@ , No comment provided by engineer. + + 1 day + *** 1 day + No comment provided by engineer. + + + 1 month + *** 1 month + No comment provided by engineer. + + + 1 week + *** 1 week + No comment provided by engineer. + 6 6 @@ -333,11 +353,26 @@ Datei kann nicht empfangen werden No comment provided by engineer. + + Change + *** Change + No comment provided by engineer. + Change database passphrase? Datenbank-Passwort ändern? No comment provided by engineer. + + Change member role? + *** Change member role? + No comment provided by engineer. + + + Change role + *** Change role + No comment provided by engineer. + Chat archive Datenbank Archiv @@ -588,6 +623,11 @@ Dunkel No comment provided by engineer. + + Data + *** Data + No comment provided by engineer. + Database ID Datenbank-ID @@ -761,6 +801,16 @@ Nachricht löschen? No comment provided by engineer. + + Delete messages + *** Delete messages + No comment provided by engineer. + + + Delete messages after + *** Delete messages after + No comment provided by engineer. + Delete old database Alte Datenbank löschen @@ -856,6 +906,11 @@ TCP-Keep-alive aktivieren No comment provided by engineer. + + Enable automatic message deletion? + *** Enable automatic message deletion? + No comment provided by engineer. + Enable instant notifications? Sofortige Benachrichtigungen aktivieren? @@ -936,6 +991,16 @@ Fehler beim Hinzufügen von Mitgliedern No comment provided by engineer. + + Error changing role + *** Error changing role + No comment provided by engineer. + + + Error changing setting + *** Error changing setting + No comment provided by engineer. + Error creating address Fehler beim Erstellen der Adresse @@ -1001,6 +1066,11 @@ Fehler beim Empfangen der Datei No comment provided by engineer. + + Error removing member + *** Error removing member + No comment provided by engineer. + Error saving ICE servers Fehler beim Speichern der ICE-Server @@ -1443,6 +1513,16 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v Mitglied No comment provided by engineer. + + Member role will be changed to "%@". All group members will be notified. + *** 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. + *** Member role will be changed to "%@". The member will receive a new invitation. + 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! @@ -1686,7 +1766,7 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v Please check your network connection and try again. Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut. - No comment provided by engineer. + alert message Please enter correct current passphrase. @@ -1863,6 +1943,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v Zurückkehren No comment provided by engineer. + + Role + *** Role + No comment provided by engineer. + Run chat Chat starten @@ -2013,6 +2098,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v Einmal-Einladungslink teilen No comment provided by engineer. + + Shared one-time link + *** Shared one-time link + No comment provided by engineer. + Show preview Vorschau anzeigen @@ -2213,6 +2303,11 @@ Wir werden Serverredundanzen hinzufügen, um verloren gegangene Nachrichten zu v Diese Aktion kann nicht rückgängig gemacht werden - Alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten. No comment provided by engineer. + + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + *** This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + No comment provided by engineer. + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Diese Aktion kann nicht rückgängig gemacht werden - Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. @@ -2694,7 +2789,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. admin Admin - No comment provided by engineer. + member role audio call (not e2e encrypted) @@ -2924,7 +3019,13 @@ SimpleX-Server können Ihr Profil nicht einsehen. member Mitglied - No comment provided by engineer. + member role + + + member %1$@ role: %2$@ + *** member %1$@ role: %2$@ + rcv group event chat item + snd group event chat item connected @@ -2946,6 +3047,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. Neue Nachricht notification + + no + *** nein + No comment provided by engineer. + no e2e encryption Keine E2E-Verschlüsselung @@ -2959,7 +3065,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. owner Eigentümer - No comment provided by engineer. + member role peer-to-peer @@ -3101,6 +3207,12 @@ SimpleX-Server können Ihr Profil nicht einsehen. Sie: No comment provided by engineer. + + your role: %@ + *** your role: %@ + rcv group event chat item + snd group event chat item + \~strike~ \~durchstreichen~ 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 b84b019c4..45d0a0807 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -92,6 +92,11 @@ %lld members No comment provided by engineer. + + %lld second(s) + %lld second(s) + No comment provided by engineer. + %lldk %lldk @@ -172,6 +177,21 @@ , No comment provided by engineer. + + 1 day + 1 day + No comment provided by engineer. + + + 1 month + 1 month + No comment provided by engineer. + + + 1 week + 1 week + No comment provided by engineer. + 6 6 @@ -333,11 +353,26 @@ Cannot receive file No comment provided by engineer. + + Change + Change + No comment provided by engineer. + Change database passphrase? Change database passphrase? No comment provided by engineer. + + Change member role? + Change member role? + No comment provided by engineer. + + + Change role + Change role + No comment provided by engineer. + Chat archive Chat archive @@ -588,6 +623,11 @@ Dark No comment provided by engineer. + + Data + Data + No comment provided by engineer. + Database ID Database ID @@ -761,6 +801,16 @@ Delete message? No comment provided by engineer. + + Delete messages + Delete messages + No comment provided by engineer. + + + Delete messages after + Delete messages after + No comment provided by engineer. + Delete old database Delete old database @@ -856,6 +906,11 @@ Enable TCP keep-alive No comment provided by engineer. + + Enable automatic message deletion? + Enable automatic message deletion? + No comment provided by engineer. + Enable instant notifications? Enable instant notifications? @@ -936,6 +991,16 @@ Error adding member(s) No comment provided by engineer. + + Error changing role + Error changing role + No comment provided by engineer. + + + Error changing setting + Error changing setting + No comment provided by engineer. + Error creating address Error creating address @@ -1001,6 +1066,11 @@ Error receiving file No comment provided by engineer. + + Error removing member + Error removing member + No comment provided by engineer. + Error saving ICE servers Error saving ICE servers @@ -1443,6 +1513,16 @@ We will be adding server redundancy to prevent lost messages. Member No comment provided by engineer. + + Member role will be changed to "%@". All group members will be notified. + 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. + Member role will be changed to "%@". The member will receive a new invitation. + No comment provided by engineer. + Member will be removed from group - this cannot be undone! Member will be removed from group - this cannot be undone! @@ -1686,7 +1766,7 @@ We will be adding server redundancy to prevent lost messages. Please check your network connection and try again. Please check your network connection and try again. - No comment provided by engineer. + alert message Please enter correct current passphrase. @@ -1863,6 +1943,11 @@ We will be adding server redundancy to prevent lost messages. Revert No comment provided by engineer. + + Role + Role + No comment provided by engineer. + Run chat Run chat @@ -2013,6 +2098,11 @@ We will be adding server redundancy to prevent lost messages. Share one-time invitation link No comment provided by engineer. + + Shared one-time link + Shared one-time link + No comment provided by engineer. + Show preview Show preview @@ -2213,6 +2303,11 @@ We will be adding server redundancy to prevent lost messages. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. No comment provided by engineer. + + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + No comment provided by engineer. + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. @@ -2694,7 +2789,7 @@ SimpleX servers cannot see your profile. admin admin - No comment provided by engineer. + member role audio call (not e2e encrypted) @@ -2924,7 +3019,13 @@ SimpleX servers cannot see your profile. member member - No comment provided by engineer. + member role + + + member %1$@ role: %2$@ + member %1$@ role: %2$@ + rcv group event chat item + snd group event chat item connected @@ -2946,6 +3047,11 @@ SimpleX servers cannot see your profile. new message notification + + no + no + No comment provided by engineer. + no e2e encryption no e2e encryption @@ -2959,7 +3065,7 @@ SimpleX servers cannot see your profile. owner owner - No comment provided by engineer. + member role peer-to-peer @@ -3101,6 +3207,12 @@ SimpleX servers cannot see your profile. you: No comment provided by engineer. + + your role: %@ + your role: %@ + rcv group event chat item + snd group event chat item + \~strike~ \~strike~ 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 da7aaf661..3dd48e40d 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -92,6 +92,11 @@ Членов группы: %lld No comment provided by engineer. + + %lld second(s) + %lld секунд + No comment provided by engineer. + %lldk %lldk @@ -172,6 +177,21 @@ , No comment provided by engineer. + + 1 day + 1 день + No comment provided by engineer. + + + 1 month + 1 месяц + No comment provided by engineer. + + + 1 week + 1 неделю + No comment provided by engineer. + 6 6 @@ -333,11 +353,26 @@ Невозможно получить файл No comment provided by engineer. + + Change + Поменять + No comment provided by engineer. + Change database passphrase? Поменять пароль базы данных? No comment provided by engineer. + + Change member role? + Поменять роль члена группы? + No comment provided by engineer. + + + Change role + Поменять роль + No comment provided by engineer. + Chat archive Архив чата @@ -588,6 +623,11 @@ Тёмная No comment provided by engineer. + + Data + Данные + No comment provided by engineer. + Database ID ID базы данных @@ -761,6 +801,16 @@ Удалить сообщение? No comment provided by engineer. + + Delete messages + Удалить сообщения + No comment provided by engineer. + + + Delete messages after + Удалять сообщения через + No comment provided by engineer. + Delete old database Удалить предыдущую версию данных @@ -856,6 +906,11 @@ Включить TCP keep-alive No comment provided by engineer. + + Enable automatic message deletion? + Включить автоматическое удаление сообщений? + No comment provided by engineer. + Enable instant notifications? Включить мгновенные уведомления? @@ -936,6 +991,16 @@ Ошибка при добавлении членов группы No comment provided by engineer. + + Error changing role + Ошибка при изменении роли + No comment provided by engineer. + + + Error changing setting + Ошибка при изменении настройки + No comment provided by engineer. + Error creating address Ошибка при создании адреса @@ -1001,6 +1066,11 @@ Ошибка при получении файла No comment provided by engineer. + + Error removing member + Ошибка при удалении члена группы + No comment provided by engineer. + Error saving ICE servers Ошибка при сохранении ICE серверов @@ -1443,6 +1513,16 @@ We will be adding server redundancy to prevent lost messages. Член группы 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 group - this cannot be undone! Член группы будет удален - это действие нельзя отменить! @@ -1686,7 +1766,7 @@ We will be adding server redundancy to prevent lost messages. Please check your network connection and try again. Пожалуйста, проверьте ваше соединение с сетью и попробуйте еще раз. - No comment provided by engineer. + alert message Please enter correct current passphrase. @@ -1863,6 +1943,11 @@ We will be adding server redundancy to prevent lost messages. Отменить изменения No comment provided by engineer. + + Role + Роль + No comment provided by engineer. + Run chat Запустить chat @@ -2013,6 +2098,11 @@ We will be adding server redundancy to prevent lost messages. Поделиться ссылкой-приглашением No comment provided by engineer. + + Shared one-time link + Одноразовая ссылка-приглашение + No comment provided by engineer. + Show preview Показывать уведомления @@ -2213,6 +2303,11 @@ We will be adding server redundancy to prevent lost messages. Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении. No comment provided by engineer. + + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут. + No comment provided by engineer. + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. @@ -2694,7 +2789,7 @@ SimpleX серверы не могут получить доступ к ваше admin админ - No comment provided by engineer. + member role audio call (not e2e encrypted) @@ -2924,7 +3019,13 @@ SimpleX серверы не могут получить доступ к ваше member член группы - No comment provided by engineer. + member role + + + member %1$@ role: %2$@ + роль %1$@: %2$@ + rcv group event chat item + snd group event chat item connected @@ -2946,6 +3047,11 @@ SimpleX серверы не могут получить доступ к ваше новое сообщение notification + + no + нет + No comment provided by engineer. + no e2e encryption нет e2e шифрования @@ -2959,7 +3065,7 @@ SimpleX серверы не могут получить доступ к ваше owner владелец - No comment provided by engineer. + member role peer-to-peer @@ -3101,6 +3207,12 @@ SimpleX серверы не могут получить доступ к ваше вы: No comment provided by engineer. + + your role: %@ + ваша роль: %@ + rcv group event chat item + snd group event chat item + \~strike~ \~зачеркнуть~ diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 167227aab..a88ef3c19 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -38,7 +38,7 @@ public enum ChatCommand { case newGroup(groupProfile: GroupProfile) case apiAddMember(groupId: Int64, contactId: Int64, memberRole: GroupMemberRole) case apiJoinGroup(groupId: Int64) - // case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole) + case apiMemberRole(groupId: Int64, memberId: Int64, memberRole: GroupMemberRole) case apiRemoveMember(groupId: Int64, memberId: Int64) case apiLeaveGroup(groupId: Int64) case apiListMembers(groupId: Int64) @@ -109,6 +109,7 @@ public enum ChatCommand { case let .newGroup(groupProfile): return "/_group \(encodeJSON(groupProfile))" case let .apiAddMember(groupId, contactId, memberRole): return "/_add #\(groupId) \(contactId) \(memberRole)" case let .apiJoinGroup(groupId): return "/_join #\(groupId)" + case let .apiMemberRole(groupId, memberId, memberRole): return "/_member role #\(groupId) \(memberId) \(memberRole.rawValue)" case let .apiRemoveMember(groupId, memberId): return "/_remove #\(groupId) \(memberId)" case let .apiLeaveGroup(groupId): return "/_leave #\(groupId)" case let .apiListMembers(groupId): return "/_members #\(groupId)" @@ -178,6 +179,7 @@ public enum ChatCommand { case .newGroup: return "newGroup" case .apiAddMember: return "apiAddMember" case .apiJoinGroup: return "apiJoinGroup" + case .apiMemberRole: return "apiMemberRole" case .apiRemoveMember: return "apiRemoveMember" case .apiLeaveGroup: return "apiLeaveGroup" case .apiListMembers: return "apiListMembers" @@ -312,6 +314,8 @@ public enum ChatResponse: Decodable, Error { case receivedGroupInvitation(groupInfo: GroupInfo, contact: Contact, memberRole: GroupMemberRole) case groupDeletedUser(groupInfo: GroupInfo) case joinedGroupMemberConnecting(groupInfo: GroupInfo, hostMember: GroupMember, member: GroupMember) + case memberRole(groupInfo: GroupInfo, byMember: GroupMember, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) + case memberRoleUser(groupInfo: GroupInfo, member: GroupMember, fromRole: GroupMemberRole, toRole: GroupMemberRole) case deletedMemberUser(groupInfo: GroupInfo, member: GroupMember) case deletedMember(groupInfo: GroupInfo, byMember: GroupMember, deletedMember: GroupMember) case leftMember(groupInfo: GroupInfo, member: GroupMember) @@ -406,6 +410,8 @@ public enum ChatResponse: Decodable, Error { case .receivedGroupInvitation: return "receivedGroupInvitation" case .groupDeletedUser: return "groupDeletedUser" case .joinedGroupMemberConnecting: return "joinedGroupMemberConnecting" + case .memberRole: return "memberRole" + case .memberRoleUser: return "memberRoleUser" case .deletedMemberUser: return "deletedMemberUser" case .deletedMember: return "deletedMember" case .leftMember: return "leftMember" @@ -501,6 +507,8 @@ public enum ChatResponse: Decodable, Error { case let .receivedGroupInvitation(groupInfo, contact, memberRole): return "groupInfo: \(groupInfo)\ncontact: \(contact)\nmemberRole: \(memberRole)" case let .groupDeletedUser(groupInfo): return String(describing: groupInfo) case let .joinedGroupMemberConnecting(groupInfo, hostMember, member): return "groupInfo: \(groupInfo)\nhostMember: \(hostMember)\nmember: \(member)" + case let .memberRole(groupInfo, byMember, member, fromRole, toRole): return "groupInfo: \(groupInfo)\nbyMember: \(byMember)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)" + case let .memberRoleUser(groupInfo, member, fromRole, toRole): return "groupInfo: \(groupInfo)\nmember: \(member)\nfromRole: \(fromRole)\ntoRole: \(toRole)" case let .deletedMemberUser(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)" case let .deletedMember(groupInfo, byMember, deletedMember): return "groupInfo: \(groupInfo)\nbyMember: \(byMember)\ndeletedMember: \(deletedMember)" case let .leftMember(groupInfo, member): return "groupInfo: \(groupInfo)\nmember: \(member)" diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 076647d10..5f758adcc 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -669,10 +669,16 @@ public struct GroupMember: Identifiable, Decodable { } } - public func canBeRemoved(membership: GroupMember) -> Bool { - let userRole = membership.memberRole + public func canBeRemoved(groupInfo: GroupInfo) -> Bool { + let userRole = groupInfo.membership.memberRole return memberStatus != .memRemoved && memberStatus != .memLeft - && userRole >= .admin && userRole >= memberRole && membership.memberCurrent + && userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberCurrent + } + + public func canChangeRoleTo(groupInfo: GroupInfo) -> [GroupMemberRole]? { + if !canBeRemoved(groupInfo: groupInfo) { return nil } + let userRole = groupInfo.membership.memberRole + return GroupMemberRole.allCases.filter { $0 <= userRole } } public var memberIncognito: Bool { @@ -702,11 +708,11 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Dec public var id: Self { self } - public var text: LocalizedStringKey { + public var text: String { switch self { - case .member: return "member" - case .admin: return "admin" - case .owner: return "owner" + case .member: return NSLocalizedString("member", comment: "member role") + case .admin: return NSLocalizedString("admin", comment: "member role") + case .owner: return NSLocalizedString("owner", comment: "member role") } } @@ -898,6 +904,8 @@ public struct ChatItem: Identifiable, Decodable { switch event { case .groupUpdated: return true case .memberConnected: return true + case .memberRole: return true + case .userRole: return false case .userDeleted: return false case .groupDeleted: return false case .memberAdded: return false @@ -1430,6 +1438,8 @@ public enum RcvGroupEvent: Decodable { case memberAdded(groupMemberId: Int64, profile: Profile) case memberConnected case memberLeft + case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole) + case userRole(role: GroupMemberRole) case memberDeleted(groupMemberId: Int64, profile: Profile) case userDeleted case groupDeleted @@ -1441,6 +1451,10 @@ public enum RcvGroupEvent: Decodable { 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 .memberLeft: return NSLocalizedString("left", comment: "rcv group event chat item") + case let .memberRole(_, profile, role): + return String.localizedStringWithFormat(NSLocalizedString("member %@ role: %@", comment: "rcv group event chat item"), profile.profileViewName, role.text) + case let .userRole(role): + return String.localizedStringWithFormat(NSLocalizedString("your role: %@", comment: "rcv group event chat item"), role.text) case let .memberDeleted(_, profile): return String.localizedStringWithFormat(NSLocalizedString("removed %@", comment: "rcv group event chat item"), profile.profileViewName) case .userDeleted: return NSLocalizedString("removed you", comment: "rcv group event chat item") @@ -1451,12 +1465,18 @@ public enum RcvGroupEvent: Decodable { } public enum SndGroupEvent: Decodable { + case memberRole(groupMemberId: Int64, profile: Profile, role: GroupMemberRole) + case userRole(role: GroupMemberRole) case memberDeleted(groupMemberId: Int64, profile: Profile) case userLeft case groupUpdated(groupProfile: GroupProfile) var text: String { switch self { + case let .memberRole(_, profile, role): + return String.localizedStringWithFormat(NSLocalizedString("member %@ role: %@", comment: "snd group event chat item"), profile.profileViewName, role.text) + case let .userRole(role): + return String.localizedStringWithFormat(NSLocalizedString("your role: %@", comment: "snd group event chat item"), role.text) case let .memberDeleted(_, profile): 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") diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 9d78ff572..2d7c2776e 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -103,6 +103,9 @@ /* No comment provided by engineer. */ "%lld members" = "%lld Mitglieder"; +/* No comment provided by engineer. */ +"%lld second(s)" = "*** %lld second(s)"; + /* No comment provided by engineer. */ "%lldk" = "%lldk"; @@ -112,6 +115,15 @@ /* No comment provided by engineer. */ "~strike~" = "\\~durchstreichen~"; +/* No comment provided by engineer. */ +"1 day" = "*** 1 day"; + +/* No comment provided by engineer. */ +"1 month" = "*** 1 month"; + +/* No comment provided by engineer. */ +"1 week" = "*** 1 week"; + /* No comment provided by engineer. */ "6" = "6"; @@ -152,7 +164,7 @@ /* call status */ "accepted call" = "Anruf angenommen"; -/* No comment provided by engineer. */ +/* member role */ "admin" = "Admin"; /* No comment provided by engineer. */ @@ -236,9 +248,18 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Datei kann nicht empfangen werden"; +/* No comment provided by engineer. */ +"Change" = "*** Change"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Datenbank-Passwort ändern?"; +/* No comment provided by engineer. */ +"Change member role?" = "*** Change member role?"; + +/* No comment provided by engineer. */ +"Change role" = "*** Change role"; + /* No comment provided by engineer. */ "Chat archive" = "Datenbank Archiv"; @@ -437,6 +458,9 @@ /* No comment provided by engineer. */ "Dark" = "Dunkel"; +/* No comment provided by engineer. */ +"Data" = "*** Data"; + /* No comment provided by engineer. */ "Database encrypted!" = "Datenbank verschlüsselt!"; @@ -536,6 +560,12 @@ /* No comment provided by engineer. */ "Delete message?" = "Nachricht löschen?"; +/* No comment provided by engineer. */ +"Delete messages" = "*** Delete messages"; + +/* No comment provided by engineer. */ +"Delete messages after" = "*** Delete messages after"; + /* No comment provided by engineer. */ "Delete old database" = "Alte Datenbank löschen"; @@ -602,6 +632,9 @@ /* No comment provided by engineer. */ "Enable" = "Aktivieren"; +/* No comment provided by engineer. */ +"Enable automatic message deletion?" = "*** Enable automatic message deletion?"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Sofortige Benachrichtigungen aktivieren?"; @@ -665,6 +698,12 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* No comment provided by engineer. */ +"Error changing role" = "*** Error changing role"; + +/* No comment provided by engineer. */ +"Error changing setting" = "*** Error changing setting"; + /* No comment provided by engineer. */ "Error creating address" = "Fehler beim Erstellen der Adresse"; @@ -704,6 +743,9 @@ /* No comment provided by engineer. */ "Error receiving file" = "Fehler beim Empfangen der Datei"; +/* No comment provided by engineer. */ +"Error removing member" = "*** Error removing member"; + /* No comment provided by engineer. */ "Error saving group profile" = "Fehler beim Speichern des Gruppenprofils"; @@ -1001,15 +1043,25 @@ /* No comment provided by engineer. */ "Markdown in messages" = "Markdowns in Nachrichten"; -/* No comment provided by engineer. */ +/* member role */ "member" = "Mitglied"; /* No comment provided by engineer. */ "Member" = "Mitglied"; +/* rcv group event chat item + snd group event chat item */ +"member %@ role: %@" = "*** member %1$@ role: %2$@"; + /* rcv group event chat item */ "member connected" = "verbunden"; +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All group members will be notified." = "*** 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." = "*** Member role will be changed to \"%@\". The member will receive a new invitation."; + /* 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!"; @@ -1076,6 +1128,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Neues Passwort…"; +/* No comment provided by engineer. */ +"no" = "*** nein"; + /* No comment provided by engineer. */ "No" = "Nein"; @@ -1145,7 +1200,7 @@ /* No comment provided by engineer. */ "or chat with the developers" = "oder chatten Sie mit den Entwicklern"; -/* No comment provided by engineer. */ +/* member role */ "owner" = "Eigentümer"; /* No comment provided by engineer. */ @@ -1175,7 +1230,7 @@ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Überprüfen Sie bitte, ob Sie den richtigen Link genutzt haben oder bitten Sie Ihren Kontakt nochmal darum, Ihnen einen Link zuzusenden."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection and try again." = "Bitte überprüfen Sie Ihre Netzwerkverbindung und versuchen Sie es erneut."; /* No comment provided by engineer. */ @@ -1301,6 +1356,9 @@ /* No comment provided by engineer. */ "Revert" = "Zurückkehren"; +/* No comment provided by engineer. */ +"Role" = "*** Role"; + /* No comment provided by engineer. */ "Run chat" = "Chat starten"; @@ -1391,6 +1449,9 @@ /* No comment provided by engineer. */ "Share one-time invitation link" = "Einmal-Einladungslink teilen"; +/* No comment provided by engineer. */ +"Shared one-time link" = "*** Shared one-time link"; + /* No comment provided by engineer. */ "Show preview" = "Vorschau anzeigen"; @@ -1523,6 +1584,9 @@ /* No comment provided by engineer. */ "This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Diese Aktion kann nicht rückgängig gemacht werden - Alle empfangenen und gesendeten Dateien und Medien werden gelöscht. Bilder mit niedriger Auflösung bleiben erhalten."; +/* No comment provided by engineer. */ +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "*** This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes."; + /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Diese Aktion kann nicht rückgängig gemacht werden - Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren."; @@ -1832,6 +1896,10 @@ /* No comment provided by engineer. */ "Your random profile" = "Ihr Zufallsprofil"; +/* rcv group event chat item + snd group event chat item */ +"your role: %@" = "*** your role: %@"; + /* No comment provided by engineer. */ "Your settings" = "Ihre Einstellungen"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 8ac1fc1f8..6bae21eea 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -103,6 +103,9 @@ /* No comment provided by engineer. */ "%lld members" = "Членов группы: %lld"; +/* No comment provided by engineer. */ +"%lld second(s)" = "%lld секунд"; + /* No comment provided by engineer. */ "%lldk" = "%lldk"; @@ -112,6 +115,15 @@ /* No comment provided by engineer. */ "~strike~" = "\\~зачеркнуть~"; +/* No comment provided by engineer. */ +"1 day" = "1 день"; + +/* No comment provided by engineer. */ +"1 month" = "1 месяц"; + +/* No comment provided by engineer. */ +"1 week" = "1 неделю"; + /* No comment provided by engineer. */ "6" = "6"; @@ -152,7 +164,7 @@ /* call status */ "accepted call" = " принятый звонок"; -/* No comment provided by engineer. */ +/* member role */ "admin" = "админ"; /* No comment provided by engineer. */ @@ -236,9 +248,18 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Невозможно получить файл"; +/* No comment provided by engineer. */ +"Change" = "Поменять"; + /* No comment provided by engineer. */ "Change database passphrase?" = "Поменять пароль базы данных?"; +/* No comment provided by engineer. */ +"Change member role?" = "Поменять роль члена группы?"; + +/* No comment provided by engineer. */ +"Change role" = "Поменять роль"; + /* No comment provided by engineer. */ "Chat archive" = "Архив чата"; @@ -437,6 +458,9 @@ /* No comment provided by engineer. */ "Dark" = "Тёмная"; +/* No comment provided by engineer. */ +"Data" = "Данные"; + /* No comment provided by engineer. */ "Database encrypted!" = "База данных зашифрована!"; @@ -536,6 +560,12 @@ /* No comment provided by engineer. */ "Delete message?" = "Удалить сообщение?"; +/* No comment provided by engineer. */ +"Delete messages" = "Удалить сообщения"; + +/* No comment provided by engineer. */ +"Delete messages after" = "Удалять сообщения через"; + /* No comment provided by engineer. */ "Delete old database" = "Удалить предыдущую версию данных"; @@ -602,6 +632,9 @@ /* No comment provided by engineer. */ "Enable" = "Включить"; +/* No comment provided by engineer. */ +"Enable automatic message deletion?" = "Включить автоматическое удаление сообщений?"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Включить мгновенные уведомления?"; @@ -665,6 +698,12 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* No comment provided by engineer. */ +"Error changing role" = "Ошибка при изменении роли"; + +/* No comment provided by engineer. */ +"Error changing setting" = "Ошибка при изменении настройки"; + /* No comment provided by engineer. */ "Error creating address" = "Ошибка при создании адреса"; @@ -704,6 +743,9 @@ /* No comment provided by engineer. */ "Error receiving file" = "Ошибка при получении файла"; +/* No comment provided by engineer. */ +"Error removing member" = "Ошибка при удалении члена группы"; + /* No comment provided by engineer. */ "Error saving group profile" = "Ошибка при сохранении профиля группы"; @@ -1001,15 +1043,25 @@ /* No comment provided by engineer. */ "Markdown in messages" = "Форматирование сообщений"; -/* No comment provided by engineer. */ +/* member role */ "member" = "член группы"; /* No comment provided by engineer. */ "Member" = "Член группы"; +/* rcv group event chat item + snd group event chat item */ +"member %@ role: %@" = "роль %1$@: %2$@"; + /* rcv group event chat item */ "member connected" = "соединен(а)"; +/* 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 group - this cannot be undone!" = "Член группы будет удален - это действие нельзя отменить!"; @@ -1076,6 +1128,9 @@ /* No comment provided by engineer. */ "New passphrase…" = "Новый пароль…"; +/* No comment provided by engineer. */ +"no" = "нет"; + /* No comment provided by engineer. */ "No" = "Нет"; @@ -1145,7 +1200,7 @@ /* No comment provided by engineer. */ "or chat with the developers" = "или соединитесь с разработчиками"; -/* No comment provided by engineer. */ +/* member role */ "owner" = "владелец"; /* No comment provided by engineer. */ @@ -1175,7 +1230,7 @@ /* No comment provided by engineer. */ "Please check that you used the correct link or ask your contact to send you another one." = "Пожалуйста, проверьте, что вы использовали правильную ссылку или попросите, чтобы ваш контакт отправил вам другую ссылку."; -/* No comment provided by engineer. */ +/* alert message */ "Please check your network connection and try again." = "Пожалуйста, проверьте ваше соединение с сетью и попробуйте еще раз."; /* No comment provided by engineer. */ @@ -1301,6 +1356,9 @@ /* No comment provided by engineer. */ "Revert" = "Отменить изменения"; +/* No comment provided by engineer. */ +"Role" = "Роль"; + /* No comment provided by engineer. */ "Run chat" = "Запустить chat"; @@ -1391,6 +1449,9 @@ /* No comment provided by engineer. */ "Share one-time invitation link" = "Поделиться ссылкой-приглашением"; +/* No comment provided by engineer. */ +"Shared one-time link" = "Одноразовая ссылка-приглашение"; + /* No comment provided by engineer. */ "Show preview" = "Показывать уведомления"; @@ -1523,6 +1584,9 @@ /* No comment provided by engineer. */ "This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Это действие нельзя отменить — все полученные и отправленные файлы будут удалены. Изображения останутся в низком разрешении."; +/* No comment provided by engineer. */ +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Это действие нельзя отменить — все сообщения, отправленные или полученные раньше чем выбрано, будут удалены. Это может занять несколько минут."; + /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; @@ -1832,6 +1896,10 @@ /* No comment provided by engineer. */ "Your random profile" = "Ваш случайный профиль"; +/* rcv group event chat item + snd group event chat item */ +"your role: %@" = "ваша роль: %@"; + /* No comment provided by engineer. */ "Your settings" = "Настройки"; diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 5782609df..614bada62 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -35,7 +35,7 @@ import Simplex.Chat.Types import Simplex.Chat.Util (safeDecodeUtf8) import Simplex.Messaging.Agent.Protocol (AgentErrorType, AgentMsgId, MsgErrorType (..), MsgMeta (..)) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, singleFieldJSON, sumTypeJSON, fstToLower) +import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON, sumTypeJSON) import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Util (eitherToMaybe, (<$?>))