ios: delivery receipts (#2701)

* ios: delivery receipts wip

* remove state variable

* fix 1 toggle

* fix 2nd toggle

* undo some changes, remove prints, remove commented code

* remove diff

* icon color

* comment

* refactor, double tick

* remove color from spaces

* update messages

* do not show Enable delievery receipts screen if any of the profiles was enabled

* update footer

* fix text

* better double ticks

* softer double tick

* improve double ticks

* a bit bigger gap in double tick

---------

Co-authored-by: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com>
This commit is contained in:
spaced4ndy 2023-07-16 14:55:31 +04:00 committed by GitHub
parent f594752bb1
commit 72c0c61a86
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 280 additions and 54 deletions

View File

@ -159,6 +159,18 @@ func apiSetActiveUserAsync(_ userId: Int64, viewPwd: String?) async throws -> Us
throw r
}
func apiSetAllContactReceipts(enable: Bool) async throws {
let r = await chatSendCmd(.setAllContactReceipts(enable: enable))
if case .cmdOk = r { return }
throw r
}
func apiSetUserContactReceipts(_ userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings) async throws {
let r = await chatSendCmd(.apiSetUserContactReceipts(userId: userId, userMsgReceiptSettings: userMsgReceiptSettings))
if case .cmdOk = r { return }
throw r
}
func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User {
try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd))
}
@ -1121,6 +1133,7 @@ func startChat(refreshInvitations: Bool = true) throws {
m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1
? .step3_CreateSimpleXAddress
: savedOnboardingStage
// TODO don't show on first start
if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() {
m.setDeliveryReceipts = true
}

View File

@ -71,6 +71,21 @@ enum SendReceipts: Identifiable, Hashable {
case let .userDefault(on): return on ? "default (yes)" : "default (no)"
}
}
func bool() -> Bool? {
switch self {
case .yes: return true
case .no: return false
case .userDefault: return nil
}
}
static func fromBool(_ enable: Bool?, userDefault def: Bool) -> SendReceipts {
if let enable = enable {
return enable ? .yes : .no
}
return .userDefault(def)
}
}
struct ChatInfoView: View {
@ -84,7 +99,8 @@ struct ChatInfoView: View {
@Binding var connectionCode: String?
@FocusState private var aliasTextFieldFocused: Bool
@State private var alert: ChatInfoViewAlert? = nil
@State private var sendReceipts = SendReceipts.yes
@State private var sendReceipts = SendReceipts.userDefault(true)
@State private var sendReceiptsUserDefault = true
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
enum ChatInfoViewAlert: Identifiable {
@ -200,6 +216,12 @@ struct ChatInfoView: View {
.navigationBarHidden(true)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.onAppear {
if let currentUser = chatModel.currentUser {
sendReceiptsUserDefault = currentUser.sendRcptsContacts
}
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
}
.alert(item: $alert) { alertItem in
switch(alertItem) {
case .deleteContactAlert: return deleteContactAlert()
@ -315,13 +337,22 @@ struct ChatInfoView: View {
private func sendReceiptsOption() -> some View {
Picker(selection: $sendReceipts) {
ForEach([.yes, .no, .userDefault(true)]) { (opt: SendReceipts) in
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
Text(opt.text)
}
} label: {
Label("Send receipts", systemImage: "checkmark.message")
}
.frame(height: 36)
.onChange(of: sendReceipts) { _ in
setSendReceipts()
}
}
private func setSendReceipts() {
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
chatSettings.sendRcpts = sendReceipts.bool()
updateChatSettings(chat, chatSettings: chatSettings)
}
private func synchronizeConnectionButton() -> some View {

View File

@ -18,12 +18,30 @@ struct CIMetaView: View {
if chatItem.isDeletedContent {
chatItem.timestampText.font(.caption).foregroundColor(metaColor)
} else {
ciMetaText(chatItem.meta, chatTTL: chat.chatInfo.timedMessagesTTL, color: metaColor)
let meta = chatItem.meta
let ttl = chat.chatInfo.timedMessagesTTL
switch meta.itemStatus {
case .sndSent:
ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .sent)
case .sndRcvd:
ZStack {
ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .rcvd1)
ciMetaText(meta, chatTTL: ttl, color: metaColor, sent: .rcvd2)
}
default:
ciMetaText(meta, chatTTL: ttl, color: metaColor)
}
}
}
}
func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparent: Bool = false) -> Text {
enum SentCheckmark {
case sent
case rcvd1
case rcvd2
}
func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparent: Bool = false, sent: SentCheckmark? = nil) -> Text {
var r = Text("")
if meta.itemEdited {
r = r + statusIconText("pencil", color)
@ -37,7 +55,16 @@ func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparen
r = r + Text(" ")
}
if let (icon, statusColor) = meta.statusIcon(color) {
r = r + statusIconText(icon, transparent ? .clear : statusColor) + Text(" ")
let t = Text(Image(systemName: icon)).font(.caption2)
let gap = Text(" ").kerning(-1.25)
let t1 = t.foregroundColor(transparent ? .clear : statusColor.opacity(0.67))
switch sent {
case nil: r = r + t1
case .sent: r = r + t1 + gap
case .rcvd1: r = r + t.foregroundColor(transparent ? .clear : color.opacity(0.67)) + gap
case .rcvd2: r = r + gap + t1
}
r = r + Text(" ")
} else if !meta.disappearing {
r = r + statusIconText("circlebadge.fill", .clear) + Text(" ")
}

View File

@ -248,7 +248,7 @@ private let versionDescriptions: [VersionDescription] = [
FeatureDescription(
icon: "gift",
title: "A few more things",
description: "- more stabile message delivery.\n- a bit better groups.\n- and more!"
description: "- more stable message delivery.\n- a bit better groups.\n- and more!"
),
]
)

View File

@ -10,12 +10,28 @@ import SwiftUI
import SimpleXChat
struct PrivacySettings: View {
@EnvironmentObject var m: ChatModel
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
@State private var currentLAMode = privacyLocalAuthModeDefault.get()
@State private var contactReceipts = false
@State private var contactReceiptsReset = false
@State private var contactReceiptsOverrides = 0
@State private var contactReceiptsDialogue = false
@State private var alert: PrivacySettingsViewAlert?
enum PrivacySettingsViewAlert: Identifiable {
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
var id: String {
switch self {
case let .error(title, _): return "error \(title)"
}
}
}
var body: some View {
VStack {
@ -71,22 +87,99 @@ struct PrivacySettings: View {
Section {
settingsRow("person") {
Toggle("Contacts", isOn: $useLinkPreviews)
Toggle("Contacts", isOn: $contactReceipts)
}
settingsRow("person.2") {
Toggle("Small groups (max 10)", isOn: Binding.constant(false))
}
.foregroundColor(.secondary)
.disabled(true)
// settingsRow("person.2") {
// Toggle("Small groups (max 20)", isOn: Binding.constant(false))
// }
} header: {
Text("Send delivery receipts to")
} footer: {
VStack(alignment: .leading) {
Text("These settings are for your current profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.")
Text("They can be overridden in contact and group settings")
Text("They can be overridden in contact settings")
}
.frame(maxWidth: .infinity, alignment: .leading)
}
.confirmationDialog(contactReceiptsDialogTitle, isPresented: $contactReceiptsDialogue, titleVisibility: .visible) {
Button(contactReceipts ? "Enable (keep overrides)" : "Disable (keep overrides)") {
setSendReceiptsContacts(contactReceipts, clearOverrides: false)
}
Button(contactReceipts ? "Enable for all" : "Disable for all", role: .destructive) {
setSendReceiptsContacts(contactReceipts, clearOverrides: true)
}
Button("Cancel", role: .cancel) {
contactReceiptsReset = true
contactReceipts.toggle()
}
}
}
}
.onChange(of: contactReceipts) { _ in // sometimes there is race with onAppear
if contactReceiptsReset {
contactReceiptsReset = false
} else {
setOrAskSendReceiptsContacts(contactReceipts)
}
}
.onAppear {
if let u = m.currentUser, contactReceipts != u.sendRcptsContacts {
contactReceiptsReset = true
contactReceipts = u.sendRcptsContacts
}
}
.alert(item: $alert) { alert in
switch alert {
case let .error(title, error):
return Alert(title: Text(title), message: Text(error))
}
}
}
private func setOrAskSendReceiptsContacts(_ enable: Bool) {
contactReceiptsOverrides = m.chats.reduce(0) { count, chat in
let sendRcpts = chat.chatInfo.contact?.chatSettings.sendRcpts
return count + (sendRcpts == nil || sendRcpts == enable ? 0 : 1)
}
if contactReceiptsOverrides == 0 {
setSendReceiptsContacts(enable, clearOverrides: false)
} else {
contactReceiptsDialogue = true
}
}
private var contactReceiptsDialogTitle: LocalizedStringKey {
contactReceipts
? "Sending receipts is disabled for \(contactReceiptsOverrides) contacts"
: "Sending receipts is enabled for \(contactReceiptsOverrides) contacts"
}
private func setSendReceiptsContacts(_ enable: Bool, clearOverrides: Bool) {
Task {
do {
if let currentUser = m.currentUser {
let userMsgReceiptSettings = UserMsgReceiptSettings(enable: enable, clearOverrides: clearOverrides)
try await apiSetUserContactReceipts(currentUser.userId, userMsgReceiptSettings: userMsgReceiptSettings)
privacyDeliveryReceiptsSet.set(true)
await MainActor.run {
var updatedUser = currentUser
updatedUser.sendRcptsContacts = enable
m.updateUser(updatedUser)
if clearOverrides {
m.chats.forEach { chat in
if var contact = chat.chatInfo.contact {
let sendRcpts = contact.chatSettings.sendRcpts
if sendRcpts != nil && sendRcpts != enable {
contact.chatSettings.sendRcpts = nil
m.updateContact(contact)
}
}
}
}
}
}
} catch let error {
alert = .error(title: "Error setting delivery receipts!", error: "Error: \(responseError(error))")
}
}
}

View File

@ -7,6 +7,7 @@
//
import SwiftUI
import SimpleXChat
struct SetDeliveryReceiptsView: View {
@EnvironmentObject var m: ChatModel
@ -22,36 +23,61 @@ struct SetDeliveryReceiptsView: View {
Spacer()
Button("Enable") {
m.setDeliveryReceipts = false
Task {
do {
try await apiSetAllContactReceipts(enable: true)
await MainActor.run {
m.setDeliveryReceipts = false
privacyDeliveryReceiptsSet.set(true)
}
} catch let error {
AlertManager.shared.showAlert(Alert(
title: Text("Error enabling delivery receipts!"),
message: Text("Error: \(responseError(error))")
))
await MainActor.run {
m.setDeliveryReceipts = false
}
}
}
}
.font(.largeTitle)
Group {
if m.users.count > 1 {
Text("Delivery receipts will be enabled for all contacts in all visible chat profiles.")
Text("Sending delivery receipts will be enabled for all contacts in all visible chat profiles.")
} else {
Text("Delivery receipts will be enabled for all contacts.")
Text("Sending delivery receipts will be enabled for all contacts.")
}
}
.multilineTextAlignment(.center)
Spacer()
Button("Enable later via Settings") {
AlertManager.shared.showAlert(Alert(
title: Text("Delivery receipts are disabled!"),
message: Text("You can enable them later via app Privacy & Security settings."),
primaryButton: .default(Text("Don't show again")) {
m.setDeliveryReceipts = false
},
secondaryButton: .default(Text("Ok")) {
m.setDeliveryReceipts = false
VStack(spacing: 8) {
Button {
AlertManager.shared.showAlert(Alert(
title: Text("Delivery receipts are disabled!"),
message: Text("You can enable them later via app Privacy & Security settings."),
primaryButton: .default(Text("Don't show again")) {
m.setDeliveryReceipts = false
privacyDeliveryReceiptsSet.set(true)
},
secondaryButton: .default(Text("Ok")) {
m.setDeliveryReceipts = false
}
))
} label: {
HStack {
Text("Don't enable")
Image(systemName: "chevron.right")
}
))
}
Text("You can enable later via Settings").font(.footnote)
}
}
.padding()
.padding(.horizontal)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(uiColor: .systemBackground))
}
}

View File

@ -160,11 +160,11 @@
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */; };
645041592A5C5749000221AD /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041542A5C5748000221AD /* libffi.a */; };
6450415A2A5C5749000221AD /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041552A5C5748000221AD /* libgmp.a */; };
6450415B2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041562A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a */; };
6450415C2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041572A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a */; };
6450415D2A5C5749000221AD /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041582A5C5748000221AD /* libgmpxx.a */; };
64519A182A615B010011988A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A132A615B010011988A /* libgmpxx.a */; };
64519A192A615B020011988A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A142A615B010011988A /* libgmp.a */; };
64519A1A2A615B020011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A152A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a */; };
64519A1B2A615B020011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A162A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a */; };
64519A1C2A615B020011988A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A172A615B010011988A /* libffi.a */; };
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; };
@ -437,11 +437,11 @@
644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIVoiceView.swift; sourceTree = "<group>"; };
644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FramedCIVoiceView.swift; sourceTree = "<group>"; };
644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkedDeletedItemView.swift; sourceTree = "<group>"; };
645041542A5C5748000221AD /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
645041552A5C5748000221AD /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
645041562A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a"; sourceTree = "<group>"; };
645041572A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a"; sourceTree = "<group>"; };
645041582A5C5748000221AD /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64519A132A615B010011988A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
64519A142A615B010011988A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
64519A152A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a"; sourceTree = "<group>"; };
64519A162A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a"; sourceTree = "<group>"; };
64519A172A615B010011988A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
6454036E2822A9750090DDFF /* ComposeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComposeFileView.swift; sourceTree = "<group>"; };
646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = LocalAuthentication.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/System/Library/Frameworks/LocalAuthentication.framework; sourceTree = DEVELOPER_DIR; };
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = "<group>"; };
@ -501,13 +501,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
64519A1A2A615B020011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a in Frameworks */,
64519A182A615B010011988A /* libgmpxx.a in Frameworks */,
64519A1B2A615B020011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
645041592A5C5749000221AD /* libffi.a in Frameworks */,
6450415B2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a in Frameworks */,
6450415A2A5C5749000221AD /* libgmp.a in Frameworks */,
6450415C2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a in Frameworks */,
64519A1C2A615B020011988A /* libffi.a in Frameworks */,
64519A192A615B020011988A /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
6450415D2A5C5749000221AD /* libgmpxx.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -568,11 +568,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
645041542A5C5748000221AD /* libffi.a */,
645041552A5C5748000221AD /* libgmp.a */,
645041582A5C5748000221AD /* libgmpxx.a */,
645041562A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a */,
645041572A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a */,
64519A172A615B010011988A /* libffi.a */,
64519A142A615B010011988A /* libgmp.a */,
64519A132A615B010011988A /* libgmpxx.a */,
64519A162A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a */,
64519A152A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a */,
);
path = Libraries;
sourceTree = "<group>";

View File

@ -17,6 +17,8 @@ public enum ChatCommand {
case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool)
case listUsers
case apiSetActiveUser(userId: Int64, viewPwd: String?)
case setAllContactReceipts(enable: Bool)
case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings)
case apiHideUser(userId: Int64, viewPwd: String)
case apiUnhideUser(userId: Int64, viewPwd: String)
case apiMuteUser(userId: Int64)
@ -122,6 +124,10 @@ public enum ChatCommand {
return "/_create user \(encodeJSON(user))"
case .listUsers: return "/users"
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
case let .setAllContactReceipts(enable): return "/set receipts all \(onOff(enable))"
case let .apiSetUserContactReceipts(userId, userMsgReceiptSettings):
let umrs = userMsgReceiptSettings
return "/_set receipts \(userId) \(onOff(umrs.enable)) clear_overrides=\(onOff(umrs.clearOverrides))"
case let .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))"
case let .apiMuteUser(userId): return "/_mute user \(userId)"
@ -249,6 +255,8 @@ public enum ChatCommand {
case .createActiveUser: return "createActiveUser"
case .listUsers: return "listUsers"
case .apiSetActiveUser: return "apiSetActiveUser"
case .setAllContactReceipts: return "setAllContactReceipts"
case .apiSetUserContactReceipts: return "apiSetUserContactReceipts"
case .apiHideUser: return "apiHideUser"
case .apiUnhideUser: return "apiUnhideUser"
case .apiMuteUser: return "apiMuteUser"
@ -1134,14 +1142,26 @@ public struct KeepAliveOpts: Codable, Equatable {
public struct ChatSettings: Codable {
public var enableNtfs: Bool
public var sendRcpts: Bool?
public var favorite: Bool
public init(enableNtfs: Bool, favorite: Bool) {
public init(enableNtfs: Bool, sendRcpts: Bool?, favorite: Bool) {
self.enableNtfs = enableNtfs
self.sendRcpts = sendRcpts
self.favorite = favorite
}
public static let defaults: ChatSettings = ChatSettings(enableNtfs: true, favorite: false)
public static let defaults: ChatSettings = ChatSettings(enableNtfs: true, sendRcpts: nil, favorite: false)
}
public struct UserMsgReceiptSettings: Codable {
public var enable: Bool
public var clearOverrides: Bool
public init(enable: Bool, clearOverrides: Bool) {
self.enable = enable
self.clearOverrides = clearOverrides
}
}
public struct ConnectionStats: Decodable {

View File

@ -16,6 +16,8 @@ public struct User: Decodable, NamedChat, Identifiable {
public var profile: LocalProfile
public var fullPreferences: FullPreferences
public var activeUser: Bool
public var sendRcptsContacts: Bool
public var sendRcptsSmallGroups: Bool
public var displayName: String { get { profile.displayName } }
public var fullName: String { get { profile.fullName } }
@ -44,6 +46,8 @@ public struct User: Decodable, NamedChat, Identifiable {
profile: LocalProfile.sampleData,
fullPreferences: FullPreferences.sampleData,
activeUser: true,
sendRcptsContacts: true,
sendRcptsSmallGroups: false,
showNtfs: true
)
}
@ -2269,6 +2273,11 @@ public struct CIMeta: Decodable {
public func statusIcon(_ metaColor: Color = .secondary) -> (String, Color)? {
switch itemStatus {
case .sndSent: return ("checkmark", metaColor)
case let .sndRcvd(msgRcptStatus):
switch msgRcptStatus {
case .ok: return ("checkmark", metaColor) // ("checkmark.circle", metaColor)
case .badMsgHash: return ("checkmark", .red) // ("checkmark.circle", .red)
}
case .sndErrorAuth: return ("multiply", .red)
case .sndError: return ("exclamationmark.triangle.fill", .yellow)
case .rcvNew: return ("circlebadge.fill", Color.accentColor)
@ -2337,6 +2346,7 @@ private func recent(_ date: Date) -> Bool {
public enum CIStatus: Decodable {
case sndNew
case sndSent
case sndRcvd(msgRcptStatus: MsgReceiptStatus)
case sndErrorAuth
case sndError(agentError: String)
case rcvNew
@ -2344,16 +2354,22 @@ public enum CIStatus: Decodable {
var id: String {
switch self {
case .sndNew: return "sndNew"
case .sndSent: return "sndSent"
case .sndErrorAuth: return "sndErrorAuth"
case .sndError: return "sndError"
case .rcvNew: return "rcvNew"
case .sndNew: return "sndNew"
case .sndSent: return "sndSent"
case .sndRcvd: return "sndRcvd"
case .sndErrorAuth: return "sndErrorAuth"
case .sndError: return "sndError"
case .rcvNew: return "rcvNew"
case .rcvRead: return "rcvRead"
}
}
}
public enum MsgReceiptStatus: String, Decodable {
case ok
case badMsgHash
}
public enum CIDeleted: Decodable {
case deleted(deletedTs: Date?)
case moderated(deletedTs: Date?, byGroupMember: GroupMember)