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
|
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 {
|
func apiHideUser(_ userId: Int64, viewPwd: String) async throws -> User {
|
||||||
try await setUserPrivacy_(.apiHideUser(userId: userId, viewPwd: viewPwd))
|
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
|
m.onboardingStage = [.step1_SimpleXInfo, .step2_CreateProfile].contains(savedOnboardingStage) && m.users.count == 1
|
||||||
? .step3_CreateSimpleXAddress
|
? .step3_CreateSimpleXAddress
|
||||||
: savedOnboardingStage
|
: savedOnboardingStage
|
||||||
|
// TODO don't show on first start
|
||||||
if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() {
|
if m.onboardingStage == .onboardingComplete && !privacyDeliveryReceiptsSet.get() {
|
||||||
m.setDeliveryReceipts = true
|
m.setDeliveryReceipts = true
|
||||||
}
|
}
|
||||||
|
@ -71,6 +71,21 @@ enum SendReceipts: Identifiable, Hashable {
|
|||||||
case let .userDefault(on): return on ? "default (yes)" : "default (no)"
|
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 {
|
struct ChatInfoView: View {
|
||||||
@ -84,7 +99,8 @@ struct ChatInfoView: View {
|
|||||||
@Binding var connectionCode: String?
|
@Binding var connectionCode: String?
|
||||||
@FocusState private var aliasTextFieldFocused: Bool
|
@FocusState private var aliasTextFieldFocused: Bool
|
||||||
@State private var alert: ChatInfoViewAlert? = nil
|
@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
|
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
||||||
|
|
||||||
enum ChatInfoViewAlert: Identifiable {
|
enum ChatInfoViewAlert: Identifiable {
|
||||||
@ -200,6 +216,12 @@ struct ChatInfoView: View {
|
|||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
.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
|
.alert(item: $alert) { alertItem in
|
||||||
switch(alertItem) {
|
switch(alertItem) {
|
||||||
case .deleteContactAlert: return deleteContactAlert()
|
case .deleteContactAlert: return deleteContactAlert()
|
||||||
@ -315,13 +337,22 @@ struct ChatInfoView: View {
|
|||||||
|
|
||||||
private func sendReceiptsOption() -> some View {
|
private func sendReceiptsOption() -> some View {
|
||||||
Picker(selection: $sendReceipts) {
|
Picker(selection: $sendReceipts) {
|
||||||
ForEach([.yes, .no, .userDefault(true)]) { (opt: SendReceipts) in
|
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
||||||
Text(opt.text)
|
Text(opt.text)
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Label("Send receipts", systemImage: "checkmark.message")
|
Label("Send receipts", systemImage: "checkmark.message")
|
||||||
}
|
}
|
||||||
.frame(height: 36)
|
.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 {
|
private func synchronizeConnectionButton() -> some View {
|
||||||
|
@ -18,12 +18,30 @@ struct CIMetaView: View {
|
|||||||
if chatItem.isDeletedContent {
|
if chatItem.isDeletedContent {
|
||||||
chatItem.timestampText.font(.caption).foregroundColor(metaColor)
|
chatItem.timestampText.font(.caption).foregroundColor(metaColor)
|
||||||
} else {
|
} 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("")
|
var r = Text("")
|
||||||
if meta.itemEdited {
|
if meta.itemEdited {
|
||||||
r = r + statusIconText("pencil", color)
|
r = r + statusIconText("pencil", color)
|
||||||
@ -37,7 +55,16 @@ func ciMetaText(_ meta: CIMeta, chatTTL: Int?, color: Color = .clear, transparen
|
|||||||
r = r + Text(" ")
|
r = r + Text(" ")
|
||||||
}
|
}
|
||||||
if let (icon, statusColor) = meta.statusIcon(color) {
|
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 {
|
} else if !meta.disappearing {
|
||||||
r = r + statusIconText("circlebadge.fill", .clear) + Text(" ")
|
r = r + statusIconText("circlebadge.fill", .clear) + Text(" ")
|
||||||
}
|
}
|
||||||
|
@ -248,7 +248,7 @@ private let versionDescriptions: [VersionDescription] = [
|
|||||||
FeatureDescription(
|
FeatureDescription(
|
||||||
icon: "gift",
|
icon: "gift",
|
||||||
title: "A few more things",
|
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
|
import SimpleXChat
|
||||||
|
|
||||||
struct PrivacySettings: View {
|
struct PrivacySettings: View {
|
||||||
|
@EnvironmentObject var m: ChatModel
|
||||||
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
|
@AppStorage(DEFAULT_PRIVACY_ACCEPT_IMAGES) private var autoAcceptImages = true
|
||||||
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
|
@AppStorage(DEFAULT_PRIVACY_LINK_PREVIEWS) private var useLinkPreviews = true
|
||||||
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
@State private var simplexLinkMode = privacySimplexLinkModeDefault.get()
|
||||||
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
@AppStorage(DEFAULT_PRIVACY_PROTECT_SCREEN) private var protectScreen = false
|
||||||
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
@AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false
|
||||||
@State private var currentLAMode = privacyLocalAuthModeDefault.get()
|
@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 {
|
var body: some View {
|
||||||
VStack {
|
VStack {
|
||||||
@ -71,22 +87,99 @@ struct PrivacySettings: View {
|
|||||||
|
|
||||||
Section {
|
Section {
|
||||||
settingsRow("person") {
|
settingsRow("person") {
|
||||||
Toggle("Contacts", isOn: $useLinkPreviews)
|
Toggle("Contacts", isOn: $contactReceipts)
|
||||||
}
|
}
|
||||||
settingsRow("person.2") {
|
// settingsRow("person.2") {
|
||||||
Toggle("Small groups (max 10)", isOn: Binding.constant(false))
|
// Toggle("Small groups (max 20)", isOn: Binding.constant(false))
|
||||||
}
|
// }
|
||||||
.foregroundColor(.secondary)
|
|
||||||
.disabled(true)
|
|
||||||
} header: {
|
} header: {
|
||||||
Text("Send delivery receipts to")
|
Text("Send delivery receipts to")
|
||||||
} footer: {
|
} footer: {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
Text("These settings are for your current profile **\(ChatModel.shared.currentUser?.displayName ?? "")**.")
|
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)
|
.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 SwiftUI
|
||||||
|
import SimpleXChat
|
||||||
|
|
||||||
struct SetDeliveryReceiptsView: View {
|
struct SetDeliveryReceiptsView: View {
|
||||||
@EnvironmentObject var m: ChatModel
|
@EnvironmentObject var m: ChatModel
|
||||||
@ -22,36 +23,61 @@ struct SetDeliveryReceiptsView: View {
|
|||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Enable") {
|
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)
|
.font(.largeTitle)
|
||||||
Group {
|
Group {
|
||||||
if m.users.count > 1 {
|
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 {
|
} else {
|
||||||
Text("Delivery receipts will be enabled for all contacts.")
|
Text("Sending delivery receipts will be enabled for all contacts.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
Button("Enable later via Settings") {
|
VStack(spacing: 8) {
|
||||||
AlertManager.shared.showAlert(Alert(
|
Button {
|
||||||
title: Text("Delivery receipts are disabled!"),
|
AlertManager.shared.showAlert(Alert(
|
||||||
message: Text("You can enable them later via app Privacy & Security settings."),
|
title: Text("Delivery receipts are disabled!"),
|
||||||
primaryButton: .default(Text("Don't show again")) {
|
message: Text("You can enable them later via app Privacy & Security settings."),
|
||||||
m.setDeliveryReceipts = false
|
primaryButton: .default(Text("Don't show again")) {
|
||||||
},
|
m.setDeliveryReceipts = false
|
||||||
secondaryButton: .default(Text("Ok")) {
|
privacyDeliveryReceiptsSet.set(true)
|
||||||
m.setDeliveryReceipts = false
|
},
|
||||||
|
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()
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
.background(Color(uiColor: .systemBackground))
|
.background(Color(uiColor: .systemBackground))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -160,11 +160,11 @@
|
|||||||
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
644EFFE0292CFD7F00525D5B /* CIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFDF292CFD7F00525D5B /* CIVoiceView.swift */; };
|
||||||
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
644EFFE2292D089800525D5B /* FramedCIVoiceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE1292D089800525D5B /* FramedCIVoiceView.swift */; };
|
||||||
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */; };
|
644EFFE42937BE9700525D5B /* MarkedDeletedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644EFFE32937BE9700525D5B /* MarkedDeletedItemView.swift */; };
|
||||||
645041592A5C5749000221AD /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041542A5C5748000221AD /* libffi.a */; };
|
64519A182A615B010011988A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A132A615B010011988A /* libgmpxx.a */; };
|
||||||
6450415A2A5C5749000221AD /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041552A5C5748000221AD /* libgmp.a */; };
|
64519A192A615B020011988A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A142A615B010011988A /* 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 */; };
|
64519A1A2A615B020011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A152A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a */; };
|
||||||
6450415C2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041572A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.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 */; };
|
||||||
6450415D2A5C5749000221AD /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 645041582A5C5748000221AD /* libgmpxx.a */; };
|
64519A1C2A615B020011988A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64519A172A615B010011988A /* libffi.a */; };
|
||||||
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
|
6454036F2822A9750090DDFF /* ComposeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6454036E2822A9750090DDFF /* ComposeFileView.swift */; };
|
||||||
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
|
646BB38C283BEEB9001CE359 /* LocalAuthentication.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 646BB38B283BEEB9001CE359 /* LocalAuthentication.framework */; };
|
||||||
646BB38E283FDB6D001CE359 /* LocalAuthenticationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
64519A132A615B010011988A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
645041552A5C5748000221AD /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
64519A142A615B010011988A /* 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>"; };
|
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>"; };
|
||||||
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>"; };
|
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>"; };
|
||||||
645041582A5C5748000221AD /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.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>"; };
|
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; };
|
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>"; };
|
646BB38D283FDB6D001CE359 /* LocalAuthenticationUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalAuthenticationUtils.swift; sourceTree = "<group>"; };
|
||||||
@ -501,13 +501,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
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 */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
645041592A5C5749000221AD /* libffi.a in Frameworks */,
|
64519A1C2A615B020011988A /* libffi.a in Frameworks */,
|
||||||
6450415B2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a in Frameworks */,
|
64519A192A615B020011988A /* libgmp.a in Frameworks */,
|
||||||
6450415A2A5C5749000221AD /* libgmp.a in Frameworks */,
|
|
||||||
6450415C2A5C5749000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a in Frameworks */,
|
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
6450415D2A5C5749000221AD /* libgmpxx.a in Frameworks */,
|
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@ -568,11 +568,11 @@
|
|||||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
645041542A5C5748000221AD /* libffi.a */,
|
64519A172A615B010011988A /* libffi.a */,
|
||||||
645041552A5C5748000221AD /* libgmp.a */,
|
64519A142A615B010011988A /* libgmp.a */,
|
||||||
645041582A5C5748000221AD /* libgmpxx.a */,
|
64519A132A615B010011988A /* libgmpxx.a */,
|
||||||
645041562A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7-ghc8.10.7.a */,
|
64519A162A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL-ghc8.10.7.a */,
|
||||||
645041572A5C5748000221AD /* libHSsimplex-chat-5.2.0.1-EEhQOsrCplxKU03XLccWe7.a */,
|
64519A152A615B010011988A /* libHSsimplex-chat-5.2.0.1-7dcwuQLxmes5EQ6Qc6lMkL.a */,
|
||||||
);
|
);
|
||||||
path = Libraries;
|
path = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -17,6 +17,8 @@ public enum ChatCommand {
|
|||||||
case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool)
|
case createActiveUser(profile: Profile?, sameServers: Bool, pastTimestamp: Bool)
|
||||||
case listUsers
|
case listUsers
|
||||||
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
case apiSetActiveUser(userId: Int64, viewPwd: String?)
|
||||||
|
case setAllContactReceipts(enable: Bool)
|
||||||
|
case apiSetUserContactReceipts(userId: Int64, userMsgReceiptSettings: UserMsgReceiptSettings)
|
||||||
case apiHideUser(userId: Int64, viewPwd: String)
|
case apiHideUser(userId: Int64, viewPwd: String)
|
||||||
case apiUnhideUser(userId: Int64, viewPwd: String)
|
case apiUnhideUser(userId: Int64, viewPwd: String)
|
||||||
case apiMuteUser(userId: Int64)
|
case apiMuteUser(userId: Int64)
|
||||||
@ -122,6 +124,10 @@ public enum ChatCommand {
|
|||||||
return "/_create user \(encodeJSON(user))"
|
return "/_create user \(encodeJSON(user))"
|
||||||
case .listUsers: return "/users"
|
case .listUsers: return "/users"
|
||||||
case let .apiSetActiveUser(userId, viewPwd): return "/_user \(userId)\(maybePwd(viewPwd))"
|
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 .apiHideUser(userId, viewPwd): return "/_hide user \(userId) \(encodeJSON(viewPwd))"
|
||||||
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))"
|
case let .apiUnhideUser(userId, viewPwd): return "/_unhide user \(userId) \(encodeJSON(viewPwd))"
|
||||||
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
||||||
@ -249,6 +255,8 @@ public enum ChatCommand {
|
|||||||
case .createActiveUser: return "createActiveUser"
|
case .createActiveUser: return "createActiveUser"
|
||||||
case .listUsers: return "listUsers"
|
case .listUsers: return "listUsers"
|
||||||
case .apiSetActiveUser: return "apiSetActiveUser"
|
case .apiSetActiveUser: return "apiSetActiveUser"
|
||||||
|
case .setAllContactReceipts: return "setAllContactReceipts"
|
||||||
|
case .apiSetUserContactReceipts: return "apiSetUserContactReceipts"
|
||||||
case .apiHideUser: return "apiHideUser"
|
case .apiHideUser: return "apiHideUser"
|
||||||
case .apiUnhideUser: return "apiUnhideUser"
|
case .apiUnhideUser: return "apiUnhideUser"
|
||||||
case .apiMuteUser: return "apiMuteUser"
|
case .apiMuteUser: return "apiMuteUser"
|
||||||
@ -1134,14 +1142,26 @@ public struct KeepAliveOpts: Codable, Equatable {
|
|||||||
|
|
||||||
public struct ChatSettings: Codable {
|
public struct ChatSettings: Codable {
|
||||||
public var enableNtfs: Bool
|
public var enableNtfs: Bool
|
||||||
|
public var sendRcpts: Bool?
|
||||||
public var favorite: Bool
|
public var favorite: Bool
|
||||||
|
|
||||||
public init(enableNtfs: Bool, favorite: Bool) {
|
public init(enableNtfs: Bool, sendRcpts: Bool?, favorite: Bool) {
|
||||||
self.enableNtfs = enableNtfs
|
self.enableNtfs = enableNtfs
|
||||||
|
self.sendRcpts = sendRcpts
|
||||||
self.favorite = favorite
|
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 {
|
public struct ConnectionStats: Decodable {
|
||||||
|
@ -16,6 +16,8 @@ public struct User: Decodable, NamedChat, Identifiable {
|
|||||||
public var profile: LocalProfile
|
public var profile: LocalProfile
|
||||||
public var fullPreferences: FullPreferences
|
public var fullPreferences: FullPreferences
|
||||||
public var activeUser: Bool
|
public var activeUser: Bool
|
||||||
|
public var sendRcptsContacts: Bool
|
||||||
|
public var sendRcptsSmallGroups: Bool
|
||||||
|
|
||||||
public var displayName: String { get { profile.displayName } }
|
public var displayName: String { get { profile.displayName } }
|
||||||
public var fullName: String { get { profile.fullName } }
|
public var fullName: String { get { profile.fullName } }
|
||||||
@ -44,6 +46,8 @@ public struct User: Decodable, NamedChat, Identifiable {
|
|||||||
profile: LocalProfile.sampleData,
|
profile: LocalProfile.sampleData,
|
||||||
fullPreferences: FullPreferences.sampleData,
|
fullPreferences: FullPreferences.sampleData,
|
||||||
activeUser: true,
|
activeUser: true,
|
||||||
|
sendRcptsContacts: true,
|
||||||
|
sendRcptsSmallGroups: false,
|
||||||
showNtfs: true
|
showNtfs: true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -2269,6 +2273,11 @@ public struct CIMeta: Decodable {
|
|||||||
public func statusIcon(_ metaColor: Color = .secondary) -> (String, Color)? {
|
public func statusIcon(_ metaColor: Color = .secondary) -> (String, Color)? {
|
||||||
switch itemStatus {
|
switch itemStatus {
|
||||||
case .sndSent: return ("checkmark", metaColor)
|
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 .sndErrorAuth: return ("multiply", .red)
|
||||||
case .sndError: return ("exclamationmark.triangle.fill", .yellow)
|
case .sndError: return ("exclamationmark.triangle.fill", .yellow)
|
||||||
case .rcvNew: return ("circlebadge.fill", Color.accentColor)
|
case .rcvNew: return ("circlebadge.fill", Color.accentColor)
|
||||||
@ -2337,6 +2346,7 @@ private func recent(_ date: Date) -> Bool {
|
|||||||
public enum CIStatus: Decodable {
|
public enum CIStatus: Decodable {
|
||||||
case sndNew
|
case sndNew
|
||||||
case sndSent
|
case sndSent
|
||||||
|
case sndRcvd(msgRcptStatus: MsgReceiptStatus)
|
||||||
case sndErrorAuth
|
case sndErrorAuth
|
||||||
case sndError(agentError: String)
|
case sndError(agentError: String)
|
||||||
case rcvNew
|
case rcvNew
|
||||||
@ -2344,16 +2354,22 @@ public enum CIStatus: Decodable {
|
|||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .sndNew: return "sndNew"
|
case .sndNew: return "sndNew"
|
||||||
case .sndSent: return "sndSent"
|
case .sndSent: return "sndSent"
|
||||||
case .sndErrorAuth: return "sndErrorAuth"
|
case .sndRcvd: return "sndRcvd"
|
||||||
case .sndError: return "sndError"
|
case .sndErrorAuth: return "sndErrorAuth"
|
||||||
case .rcvNew: return "rcvNew"
|
case .sndError: return "sndError"
|
||||||
|
case .rcvNew: return "rcvNew"
|
||||||
case .rcvRead: return "rcvRead"
|
case .rcvRead: return "rcvRead"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum MsgReceiptStatus: String, Decodable {
|
||||||
|
case ok
|
||||||
|
case badMsgHash
|
||||||
|
}
|
||||||
|
|
||||||
public enum CIDeleted: Decodable {
|
public enum CIDeleted: Decodable {
|
||||||
case deleted(deletedTs: Date?)
|
case deleted(deletedTs: Date?)
|
||||||
case moderated(deletedTs: Date?, byGroupMember: GroupMember)
|
case moderated(deletedTs: Date?, byGroupMember: GroupMember)
|
||||||
|
Loading…
Reference in New Issue
Block a user