2022-02-05 20:10:47 +00:00
|
|
|
//
|
|
|
|
// ChatInfoView.swift
|
|
|
|
// SimpleX
|
|
|
|
//
|
|
|
|
// Created by Evgeny Poberezkin on 05/02/2022.
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import SwiftUI
|
2022-05-31 07:55:13 +01:00
|
|
|
import SimpleXChat
|
2022-02-05 20:10:47 +00:00
|
|
|
|
2022-12-23 19:55:45 +00:00
|
|
|
func infoRow(_ title: LocalizedStringKey, _ value: String) -> some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
HStack {
|
|
|
|
Text(title)
|
|
|
|
Spacer()
|
|
|
|
Text(value)
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-23 19:55:45 +00:00
|
|
|
func infoRow(_ title: Text, _ value: String) -> some View {
|
|
|
|
HStack {
|
|
|
|
title
|
|
|
|
Spacer()
|
|
|
|
Text(value)
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-27 13:40:26 +04:00
|
|
|
func localizedInfoRow(_ title: LocalizedStringKey, _ value: LocalizedStringKey) -> some View {
|
|
|
|
HStack {
|
|
|
|
Text(title)
|
|
|
|
Spacer()
|
|
|
|
Text(value)
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-19 14:46:08 +04:00
|
|
|
@ViewBuilder func smpServers(_ title: LocalizedStringKey, _ servers: [String]) -> some View {
|
|
|
|
if servers.count > 0 {
|
2022-11-01 20:30:53 +00:00
|
|
|
HStack {
|
|
|
|
Text(title).frame(width: 120, alignment: .leading)
|
|
|
|
Button(serverHost(servers[0])) {
|
|
|
|
UIPasteboard.general.string = servers.joined(separator: ";")
|
|
|
|
}
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
.lineLimit(1)
|
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func serverHost(_ s: String) -> String {
|
|
|
|
if let i = s.range(of: "@")?.lowerBound {
|
|
|
|
return String(s[i...].dropFirst())
|
|
|
|
} else {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 23:48:25 +01:00
|
|
|
enum SendReceipts: Identifiable, Hashable {
|
|
|
|
case yes
|
|
|
|
case no
|
|
|
|
case userDefault(Bool)
|
|
|
|
|
|
|
|
var id: Self { self }
|
|
|
|
|
|
|
|
var text: LocalizedStringKey {
|
|
|
|
switch self {
|
2023-07-14 13:15:27 +01:00
|
|
|
case .yes: return "yes"
|
|
|
|
case .no: return "no"
|
|
|
|
case let .userDefault(on): return on ? "default (yes)" : "default (no)"
|
2023-07-13 23:48:25 +01:00
|
|
|
}
|
|
|
|
}
|
2023-07-16 14:55:31 +04:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
2023-07-13 23:48:25 +01:00
|
|
|
}
|
|
|
|
|
2022-02-05 20:10:47 +00:00
|
|
|
struct ChatInfoView: View {
|
2022-02-07 10:36:11 +00:00
|
|
|
@EnvironmentObject var chatModel: ChatModel
|
2022-07-30 13:03:44 +01:00
|
|
|
@Environment(\.dismiss) var dismiss: DismissAction
|
2022-02-05 20:10:47 +00:00
|
|
|
@ObservedObject var chat: Chat
|
2022-11-16 20:26:43 +04:00
|
|
|
@State var contact: Contact
|
2022-11-01 20:30:53 +00:00
|
|
|
@Binding var connectionStats: ConnectionStats?
|
2022-12-12 08:59:35 +00:00
|
|
|
@Binding var customUserProfile: Profile?
|
2022-08-25 17:36:26 +04:00
|
|
|
@State var localAlias: String
|
2022-12-12 08:59:35 +00:00
|
|
|
@Binding var connectionCode: String?
|
2022-08-25 17:36:26 +04:00
|
|
|
@FocusState private var aliasTextFieldFocused: Bool
|
2022-07-27 13:40:26 +04:00
|
|
|
@State private var alert: ChatInfoViewAlert? = nil
|
2023-10-19 19:52:59 +04:00
|
|
|
@State private var showDeleteContactActionSheet = false
|
2023-07-16 14:55:31 +04:00
|
|
|
@State private var sendReceipts = SendReceipts.userDefault(true)
|
|
|
|
@State private var sendReceiptsUserDefault = true
|
2022-08-02 17:00:12 +04:00
|
|
|
@AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false
|
2022-02-05 20:10:47 +00:00
|
|
|
|
2022-05-19 16:56:34 +04:00
|
|
|
enum ChatInfoViewAlert: Identifiable {
|
|
|
|
case clearChatAlert
|
2022-07-27 13:40:26 +04:00
|
|
|
case networkStatusAlert
|
2022-11-02 09:48:20 +00:00
|
|
|
case switchAddressAlert
|
2023-06-19 14:46:08 +04:00
|
|
|
case abortSwitchAddressAlert
|
2023-07-10 19:01:22 +04:00
|
|
|
case syncConnectionForceAlert
|
2022-11-02 09:48:20 +00:00
|
|
|
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
|
2022-05-19 16:56:34 +04:00
|
|
|
|
2022-09-21 17:18:48 +04:00
|
|
|
var id: String {
|
|
|
|
switch self {
|
|
|
|
case .clearChatAlert: return "clearChatAlert"
|
|
|
|
case .networkStatusAlert: return "networkStatusAlert"
|
2022-11-02 09:48:20 +00:00
|
|
|
case .switchAddressAlert: return "switchAddressAlert"
|
2023-06-19 14:46:08 +04:00
|
|
|
case .abortSwitchAddressAlert: return "abortSwitchAddressAlert"
|
2023-07-10 19:01:22 +04:00
|
|
|
case .syncConnectionForceAlert: return "syncConnectionForceAlert"
|
2022-11-02 09:48:20 +00:00
|
|
|
case let .error(title, _): return "error \(title)"
|
2022-09-21 17:18:48 +04:00
|
|
|
}
|
|
|
|
}
|
2022-05-19 16:56:34 +04:00
|
|
|
}
|
|
|
|
|
2022-02-05 20:10:47 +00:00
|
|
|
var body: some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
NavigationView {
|
|
|
|
List {
|
|
|
|
contactInfoHeader()
|
|
|
|
.listRowBackground(Color.clear)
|
2022-08-25 17:36:26 +04:00
|
|
|
.contentShape(Rectangle())
|
|
|
|
.onTapGesture {
|
|
|
|
aliasTextFieldFocused = false
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
Group {
|
|
|
|
localAliasTextEdit()
|
|
|
|
}
|
2022-08-25 17:36:26 +04:00
|
|
|
.listRowBackground(Color.clear)
|
|
|
|
.listRowSeparator(.hidden)
|
2022-02-05 20:10:47 +00:00
|
|
|
|
2022-08-23 18:18:12 +04:00
|
|
|
if let customUserProfile = customUserProfile {
|
|
|
|
Section("Incognito") {
|
2023-08-08 17:26:56 +04:00
|
|
|
HStack {
|
|
|
|
Text("Your random profile")
|
|
|
|
Spacer()
|
|
|
|
Text(customUserProfile.chatViewName)
|
|
|
|
.foregroundStyle(.indigo)
|
|
|
|
}
|
2022-08-23 18:18:12 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-17 12:59:13 +04:00
|
|
|
Section {
|
2022-12-12 08:59:35 +00:00
|
|
|
if let code = connectionCode { verifyCodeButton(code) }
|
2022-11-17 12:59:13 +04:00
|
|
|
contactPreferencesButton()
|
2023-07-13 23:48:25 +01:00
|
|
|
sendReceiptsOption()
|
2023-07-10 19:01:22 +04:00
|
|
|
if let connStats = connectionStats,
|
|
|
|
connStats.ratchetSyncAllowed {
|
|
|
|
synchronizeConnectionButton()
|
|
|
|
}
|
2023-07-20 15:50:29 +04:00
|
|
|
// } else if developerTools {
|
|
|
|
// synchronizeConnectionButtonForce()
|
|
|
|
// }
|
2022-11-14 10:12:17 +00:00
|
|
|
}
|
2023-09-27 20:07:32 +04:00
|
|
|
.disabled(!contact.ready || !contact.active)
|
2022-11-14 10:12:17 +00:00
|
|
|
|
2023-04-27 17:19:21 +04:00
|
|
|
if let contactLink = contact.contactLink {
|
|
|
|
Section {
|
2023-10-17 12:56:12 +04:00
|
|
|
SimpleXLinkQRCode(uri: contactLink)
|
2023-04-27 17:19:21 +04:00
|
|
|
Button {
|
2023-10-17 12:56:12 +04:00
|
|
|
showShareSheet(items: [simplexChatLink(contactLink)])
|
2023-04-27 17:19:21 +04:00
|
|
|
} label: {
|
|
|
|
Label("Share address", systemImage: "square.and.arrow.up")
|
|
|
|
}
|
|
|
|
} header: {
|
|
|
|
Text("Address")
|
|
|
|
} footer: {
|
|
|
|
Text("You can share this address with your contacts to let them connect with **\(contact.displayName)**.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-27 20:07:32 +04:00
|
|
|
if contact.ready && contact.active {
|
2023-09-20 12:26:16 +04:00
|
|
|
Section("Servers") {
|
|
|
|
networkStatusRow()
|
|
|
|
.onTapGesture {
|
|
|
|
alert = .networkStatusAlert
|
|
|
|
}
|
|
|
|
if let connStats = connectionStats {
|
|
|
|
Button("Change receiving address") {
|
|
|
|
alert = .switchAddressAlert
|
2023-06-19 14:46:08 +04:00
|
|
|
}
|
2023-07-10 19:01:22 +04:00
|
|
|
.disabled(
|
2023-09-27 20:07:32 +04:00
|
|
|
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil }
|
2023-07-10 19:01:22 +04:00
|
|
|
|| connStats.ratchetSyncSendProhibited
|
|
|
|
)
|
2023-09-20 12:26:16 +04:00
|
|
|
if connStats.rcvQueuesInfo.contains(where: { $0.rcvSwitchStatus != nil }) {
|
|
|
|
Button("Abort changing address") {
|
|
|
|
alert = .abortSwitchAddressAlert
|
|
|
|
}
|
|
|
|
.disabled(
|
|
|
|
connStats.rcvQueuesInfo.contains { $0.rcvSwitchStatus != nil && !$0.canAbortSwitch }
|
|
|
|
|| connStats.ratchetSyncSendProhibited
|
|
|
|
)
|
|
|
|
}
|
|
|
|
smpServers("Receiving via", connStats.rcvQueuesInfo.map { $0.rcvServer })
|
|
|
|
smpServers("Sending via", connStats.sndQueuesInfo.map { $0.sndServer })
|
2023-06-19 14:46:08 +04:00
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
|
|
|
}
|
2022-02-07 10:36:11 +00:00
|
|
|
|
2022-07-27 13:40:26 +04:00
|
|
|
Section {
|
|
|
|
clearChatButton()
|
|
|
|
deleteContactButton()
|
|
|
|
}
|
|
|
|
|
2022-08-02 17:00:12 +04:00
|
|
|
if developerTools {
|
|
|
|
Section(header: Text("For console")) {
|
|
|
|
infoRow("Local name", chat.chatInfo.localDisplayName)
|
|
|
|
infoRow("Database ID", "\(chat.chatInfo.apiId)")
|
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
2022-07-14 16:40:32 +04:00
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
.navigationBarHidden(true)
|
2022-07-14 16:40:32 +04:00
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
|
2023-07-16 14:55:31 +04:00
|
|
|
.onAppear {
|
|
|
|
if let currentUser = chatModel.currentUser {
|
|
|
|
sendReceiptsUserDefault = currentUser.sendRcptsContacts
|
|
|
|
}
|
|
|
|
sendReceipts = SendReceipts.fromBool(contact.chatSettings.sendRcpts, userDefault: sendReceiptsUserDefault)
|
|
|
|
}
|
2022-07-14 16:40:32 +04:00
|
|
|
.alert(item: $alert) { alertItem in
|
|
|
|
switch(alertItem) {
|
|
|
|
case .clearChatAlert: return clearChatAlert()
|
2022-07-27 13:40:26 +04:00
|
|
|
case .networkStatusAlert: return networkStatusAlert()
|
2022-11-02 09:48:20 +00:00
|
|
|
case .switchAddressAlert: return switchAddressAlert(switchContactAddress)
|
2023-06-19 14:46:08 +04:00
|
|
|
case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress)
|
2023-07-10 19:01:22 +04:00
|
|
|
case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) })
|
2022-11-02 09:48:20 +00:00
|
|
|
case let .error(title, error): return mkAlert(title: title, message: error)
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
|
|
|
}
|
2023-10-19 19:52:59 +04:00
|
|
|
.actionSheet(isPresented: $showDeleteContactActionSheet) {
|
|
|
|
if contact.ready && contact.active {
|
2023-10-25 06:01:47 +08:00
|
|
|
return ActionSheet(
|
2023-10-19 19:52:59 +04:00
|
|
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
|
|
|
buttons: [
|
|
|
|
.destructive(Text("Delete and notify contact")) { deleteContact(notify: true) },
|
|
|
|
.destructive(Text("Delete")) { deleteContact(notify: false) },
|
|
|
|
.cancel()
|
|
|
|
]
|
|
|
|
)
|
|
|
|
} else {
|
2023-10-25 06:01:47 +08:00
|
|
|
return ActionSheet(
|
2023-10-19 19:52:59 +04:00
|
|
|
title: Text("Delete contact?\nThis cannot be undone!"),
|
|
|
|
buttons: [
|
|
|
|
.destructive(Text("Delete")) { deleteContact() },
|
|
|
|
.cancel()
|
|
|
|
]
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func contactInfoHeader() -> some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
VStack {
|
|
|
|
let cInfo = chat.chatInfo
|
|
|
|
ChatInfoImage(chat: chat, color: Color(uiColor: .tertiarySystemFill))
|
|
|
|
.frame(width: 192, height: 192)
|
|
|
|
.padding(.top, 12)
|
|
|
|
.padding()
|
2023-07-19 15:16:50 +04:00
|
|
|
if contact.verified {
|
|
|
|
(
|
|
|
|
Text(Image(systemName: "checkmark.shield"))
|
2022-12-12 08:59:35 +00:00
|
|
|
.foregroundColor(.secondary)
|
2023-07-19 15:16:50 +04:00
|
|
|
.font(.title2)
|
|
|
|
+ Text(" ")
|
|
|
|
+ Text(contact.profile.displayName)
|
|
|
|
.font(.largeTitle)
|
|
|
|
)
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.lineLimit(2)
|
|
|
|
.padding(.bottom, 2)
|
|
|
|
} else {
|
2022-12-12 08:59:35 +00:00
|
|
|
Text(contact.profile.displayName)
|
|
|
|
.font(.largeTitle)
|
2023-07-19 15:16:50 +04:00
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.lineLimit(2)
|
2022-12-12 08:59:35 +00:00
|
|
|
.padding(.bottom, 2)
|
|
|
|
}
|
2022-08-25 17:36:26 +04:00
|
|
|
if cInfo.fullName != "" && cInfo.fullName != cInfo.displayName && cInfo.fullName != contact.profile.displayName {
|
2022-07-27 13:40:26 +04:00
|
|
|
Text(cInfo.fullName)
|
|
|
|
.font(.title2)
|
2023-07-19 15:16:50 +04:00
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.lineLimit(4)
|
2022-07-27 13:40:26 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
.frame(maxWidth: .infinity, alignment: .center)
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func localAliasTextEdit() -> some View {
|
2022-08-25 17:36:26 +04:00
|
|
|
TextField("Set contact name…", text: $localAlias)
|
|
|
|
.disableAutocorrection(true)
|
|
|
|
.focused($aliasTextFieldFocused)
|
|
|
|
.submitLabel(.done)
|
|
|
|
.onChange(of: aliasTextFieldFocused) { focused in
|
|
|
|
if !focused {
|
|
|
|
setContactAlias()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.onSubmit {
|
|
|
|
setContactAlias()
|
|
|
|
}
|
|
|
|
.multilineTextAlignment(.center)
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func setContactAlias() {
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
if let contact = try await apiSetContactAlias(contactId: chat.chatInfo.apiId, localAlias: localAlias) {
|
|
|
|
await MainActor.run {
|
|
|
|
chatModel.updateContact(contact)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch {
|
|
|
|
logger.error("setContactAlias error: \(responseError(error))")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func verifyCodeButton(_ code: String) -> some View {
|
|
|
|
NavigationLink {
|
|
|
|
VerifyCodeView(
|
|
|
|
displayName: contact.displayName,
|
|
|
|
connectionCode: code,
|
|
|
|
connectionVerified: contact.verified,
|
|
|
|
verify: { code in
|
|
|
|
if let r = apiVerifyContact(chat.chatInfo.apiId, connectionCode: code) {
|
|
|
|
let (verified, existingCode) = r
|
|
|
|
contact.activeConn.connectionCode = verified ? SecurityCode(securityCode: existingCode, verifiedAt: .now) : nil
|
|
|
|
connectionCode = existingCode
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
chat.chatInfo = .direct(contact: contact)
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
)
|
|
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
|
|
.navigationTitle("Security code")
|
|
|
|
} label: {
|
|
|
|
Label(
|
|
|
|
contact.verified ? "View security code" : "Verify security code",
|
|
|
|
systemImage: contact.verified ? "checkmark.shield" : "shield"
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func contactPreferencesButton() -> some View {
|
2022-11-17 12:59:13 +04:00
|
|
|
NavigationLink {
|
|
|
|
ContactPreferencesView(
|
|
|
|
contact: $contact,
|
|
|
|
featuresAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences),
|
|
|
|
currentFeaturesAllowed: contactUserPrefsToFeaturesAllowed(contact.mergedPreferences)
|
|
|
|
)
|
|
|
|
.navigationBarTitle("Contact preferences")
|
|
|
|
.navigationBarTitleDisplayMode(.large)
|
|
|
|
} label: {
|
|
|
|
Label("Contact preferences", systemImage: "switch.2")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-13 23:48:25 +01:00
|
|
|
private func sendReceiptsOption() -> some View {
|
|
|
|
Picker(selection: $sendReceipts) {
|
2023-07-16 14:55:31 +04:00
|
|
|
ForEach([.yes, .no, .userDefault(sendReceiptsUserDefault)]) { (opt: SendReceipts) in
|
2023-07-13 23:48:25 +01:00
|
|
|
Text(opt.text)
|
|
|
|
}
|
|
|
|
} label: {
|
|
|
|
Label("Send receipts", systemImage: "checkmark.message")
|
|
|
|
}
|
|
|
|
.frame(height: 36)
|
2023-07-16 14:55:31 +04:00
|
|
|
.onChange(of: sendReceipts) { _ in
|
|
|
|
setSendReceipts()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func setSendReceipts() {
|
|
|
|
var chatSettings = chat.chatInfo.chatSettings ?? ChatSettings.defaults
|
|
|
|
chatSettings.sendRcpts = sendReceipts.bool()
|
|
|
|
updateChatSettings(chat, chatSettings: chatSettings)
|
2023-07-13 23:48:25 +01:00
|
|
|
}
|
|
|
|
|
2023-07-10 19:01:22 +04:00
|
|
|
private func synchronizeConnectionButton() -> some View {
|
|
|
|
Button {
|
|
|
|
syncContactConnection(force: false)
|
|
|
|
} label: {
|
|
|
|
Label("Fix connection", systemImage: "exclamationmark.arrow.triangle.2.circlepath")
|
|
|
|
.foregroundColor(.orange)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private func synchronizeConnectionButtonForce() -> some View {
|
|
|
|
Button {
|
|
|
|
alert = .syncConnectionForceAlert
|
|
|
|
} label: {
|
|
|
|
Label("Renegotiate encryption", systemImage: "exclamationmark.triangle")
|
|
|
|
.foregroundColor(.red)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func networkStatusRow() -> some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
HStack {
|
|
|
|
Text("Network status")
|
|
|
|
Image(systemName: "info.circle")
|
|
|
|
.foregroundColor(.accentColor)
|
|
|
|
.font(.system(size: 14))
|
|
|
|
Spacer()
|
2023-01-20 14:56:05 +04:00
|
|
|
Text(chatModel.contactNetworkStatus(contact).statusString)
|
2022-07-27 13:40:26 +04:00
|
|
|
.foregroundColor(.secondary)
|
|
|
|
serverImage()
|
|
|
|
}
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func serverImage() -> some View {
|
2023-01-20 14:56:05 +04:00
|
|
|
let status = chatModel.contactNetworkStatus(contact)
|
2022-02-05 20:10:47 +00:00
|
|
|
return Image(systemName: status.imageName)
|
|
|
|
.foregroundColor(status == .connected ? .green : .secondary)
|
2022-07-27 13:40:26 +04:00
|
|
|
.font(.system(size: 12))
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func deleteContactButton() -> some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
Button(role: .destructive) {
|
2023-10-19 19:52:59 +04:00
|
|
|
showDeleteContactActionSheet = true
|
2022-07-27 13:40:26 +04:00
|
|
|
} label: {
|
|
|
|
Label("Delete contact", systemImage: "trash")
|
|
|
|
.foregroundColor(Color.red)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-12 08:59:35 +00:00
|
|
|
private func clearChatButton() -> some View {
|
2022-07-27 13:40:26 +04:00
|
|
|
Button() {
|
|
|
|
alert = .clearChatAlert
|
|
|
|
} label: {
|
|
|
|
Label("Clear conversation", systemImage: "gobackward")
|
|
|
|
.foregroundColor(Color.orange)
|
|
|
|
}
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
2022-02-07 10:36:11 +00:00
|
|
|
|
2023-10-19 19:52:59 +04:00
|
|
|
private func deleteContact(notify: Bool? = nil) {
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
try await apiDeleteChat(type: chat.chatInfo.chatType, id: chat.chatInfo.apiId, notify: notify)
|
|
|
|
await MainActor.run {
|
|
|
|
dismiss()
|
|
|
|
chatModel.chatId = nil
|
|
|
|
chatModel.removeChat(chat.chatInfo.id)
|
2022-02-07 10:36:11 +00:00
|
|
|
}
|
2023-10-19 19:52:59 +04:00
|
|
|
} catch let error {
|
|
|
|
logger.error("deleteContactAlert apiDeleteChat error: \(responseError(error))")
|
|
|
|
let a = getErrorAlert(error, "Error deleting contact")
|
|
|
|
await MainActor.run {
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-07 10:36:11 +00:00
|
|
|
}
|
2022-05-19 16:56:34 +04:00
|
|
|
|
|
|
|
private func clearChatAlert() -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("Clear conversation?"),
|
|
|
|
message: Text("All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you."),
|
|
|
|
primaryButton: .destructive(Text("Clear")) {
|
|
|
|
Task {
|
|
|
|
await clearChat(chat)
|
2022-07-30 13:03:44 +01:00
|
|
|
await MainActor.run { dismiss() }
|
2022-05-19 16:56:34 +04:00
|
|
|
}
|
|
|
|
},
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
|
|
|
}
|
2022-07-27 13:40:26 +04:00
|
|
|
|
|
|
|
private func networkStatusAlert() -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("Network status"),
|
2023-01-20 14:56:05 +04:00
|
|
|
message: Text(chatModel.contactNetworkStatus(contact).statusExplanation)
|
2022-07-27 13:40:26 +04:00
|
|
|
)
|
|
|
|
}
|
2022-11-02 09:48:20 +00:00
|
|
|
|
|
|
|
private func switchContactAddress() {
|
|
|
|
Task {
|
|
|
|
do {
|
2023-06-20 10:09:04 +04:00
|
|
|
let stats = try apiSwitchContact(contactId: contact.apiId)
|
|
|
|
connectionStats = stats
|
2023-07-10 19:01:22 +04:00
|
|
|
await MainActor.run {
|
|
|
|
chatModel.updateContactConnectionStats(contact, stats)
|
|
|
|
dismiss()
|
|
|
|
}
|
2022-11-02 09:48:20 +00:00
|
|
|
} catch let error {
|
|
|
|
logger.error("switchContactAddress apiSwitchContact error: \(responseError(error))")
|
|
|
|
let a = getErrorAlert(error, "Error changing address")
|
|
|
|
await MainActor.run {
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-06-19 14:46:08 +04:00
|
|
|
|
|
|
|
private func abortSwitchContactAddress() {
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
let stats = try apiAbortSwitchContact(contact.apiId)
|
|
|
|
connectionStats = stats
|
2023-07-10 19:01:22 +04:00
|
|
|
await MainActor.run {
|
|
|
|
chatModel.updateContactConnectionStats(contact, stats)
|
|
|
|
}
|
2023-06-19 14:46:08 +04:00
|
|
|
} catch let error {
|
|
|
|
logger.error("abortSwitchContactAddress apiAbortSwitchContact error: \(responseError(error))")
|
|
|
|
let a = getErrorAlert(error, "Error aborting address change")
|
|
|
|
await MainActor.run {
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-10 19:01:22 +04:00
|
|
|
|
|
|
|
private func syncContactConnection(force: Bool) {
|
|
|
|
Task {
|
|
|
|
do {
|
|
|
|
let stats = try apiSyncContactRatchet(contact.apiId, force)
|
|
|
|
connectionStats = stats
|
|
|
|
await MainActor.run {
|
|
|
|
chatModel.updateContactConnectionStats(contact, stats)
|
|
|
|
dismiss()
|
|
|
|
}
|
|
|
|
} catch let error {
|
|
|
|
logger.error("syncContactConnection apiSyncContactRatchet error: \(responseError(error))")
|
|
|
|
let a = getErrorAlert(error, "Error synchronizing connection")
|
|
|
|
await MainActor.run {
|
|
|
|
alert = .error(title: a.title, error: a.message)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-11-02 09:48:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func switchAddressAlert(_ switchAddress: @escaping () -> Void) -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("Change receiving address?"),
|
2023-06-19 14:46:08 +04:00
|
|
|
message: Text("Receiving address will be changed to a different server. Address change will complete after sender comes online."),
|
|
|
|
primaryButton: .default(Text("Change"), action: switchAddress),
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
func abortSwitchAddressAlert(_ abortSwitchAddress: @escaping () -> Void) -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("Abort changing address?"),
|
|
|
|
message: Text("Address change will be aborted. Old receiving address will be used."),
|
|
|
|
primaryButton: .destructive(Text("Abort"), action: abortSwitchAddress),
|
2022-11-02 09:48:20 +00:00
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
|
|
|
|
2023-07-10 19:01:22 +04:00
|
|
|
func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Alert {
|
|
|
|
Alert(
|
|
|
|
title: Text("Renegotiate encryption?"),
|
|
|
|
message: Text("The encryption is working and the new encryption agreement is not required. It may result in connection errors!"),
|
|
|
|
primaryButton: .destructive(Text("Renegotiate"), action: syncConnectionForce),
|
|
|
|
secondaryButton: .cancel()
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-02-05 20:10:47 +00:00
|
|
|
struct ChatInfoView_Previews: PreviewProvider {
|
|
|
|
static var previews: some View {
|
2022-11-01 20:30:53 +00:00
|
|
|
ChatInfoView(
|
|
|
|
chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []),
|
|
|
|
contact: Contact.sampleData,
|
|
|
|
connectionStats: Binding.constant(nil),
|
2022-12-12 08:59:35 +00:00
|
|
|
customUserProfile: Binding.constant(nil),
|
|
|
|
localAlias: "",
|
|
|
|
connectionCode: Binding.constant(nil)
|
2022-11-01 20:30:53 +00:00
|
|
|
)
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
|
|
|
}
|