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:
parent
f594752bb1
commit
72c0c61a86
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(" ")
|
||||
}
|
||||
|
@ -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!"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
@ -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))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
@ -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>";
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user