mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 22:54:29 +00:00
ios: better error handling when connecting via links, improve alerts with chat information (#6012)
* simplexmq * ios: error handling * better new chat alerts * vertical buttons in alert when they dont fit * allow messages in prepared groups
This commit is contained in:
@@ -581,15 +581,18 @@ final class ChatModel: ObservableObject {
|
||||
// groups[group.groupInfo.id] = group
|
||||
// }
|
||||
|
||||
func addChatItem(_ cInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
func addChatItem(_ chatInfo: ChatInfo, _ cItem: ChatItem) {
|
||||
// updates membersRequireAttention
|
||||
updateChatInfo(cInfo)
|
||||
// mark chat non deleted
|
||||
if case let .direct(contact) = cInfo, contact.chatDeleted {
|
||||
let cInfo: ChatInfo
|
||||
if case let .direct(contact) = chatInfo, contact.chatDeleted {
|
||||
// mark chat non deleted
|
||||
var updatedContact = contact
|
||||
updatedContact.chatDeleted = false
|
||||
updateContact(updatedContact)
|
||||
cInfo = .direct(contact: updatedContact)
|
||||
} else {
|
||||
cInfo = chatInfo
|
||||
}
|
||||
updateChatInfo(cInfo)
|
||||
// update chat list
|
||||
if let i = getChatIndex(cInfo.id) {
|
||||
// update preview
|
||||
|
||||
@@ -899,8 +899,7 @@ func apiConnectPlan(connLink: String) async -> ((CreatedConnLink, ConnectionPlan
|
||||
}
|
||||
let r: APIResult<ChatResponse1> = await chatApiSendCmd(.apiConnectPlan(userId: userId, connLink: connLink))
|
||||
if case let .result(.connectionPlan(_, connLink, connPlan)) = r { return ((connLink, connPlan), nil) }
|
||||
let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
return (nil, apiConnectResponseAlert(r))
|
||||
}
|
||||
|
||||
func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? {
|
||||
@@ -933,12 +932,11 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT
|
||||
return (nil, alert)
|
||||
default: ()
|
||||
}
|
||||
let alert = apiConnectResponseAlert(r.unexpected) ?? connectionErrorAlert(r)
|
||||
return (nil, alert)
|
||||
return (nil, apiConnectResponseAlert(r))
|
||||
}
|
||||
|
||||
private func apiConnectResponseAlert(_ r: ChatError) -> Alert? {
|
||||
switch r {
|
||||
private func apiConnectResponseAlert<R>(_ r: APIResult<R>) -> Alert {
|
||||
switch r.unexpected {
|
||||
case .error(.invalidConnReq):
|
||||
mkAlert(
|
||||
title: "Invalid connection link",
|
||||
@@ -974,12 +972,12 @@ private func apiConnectResponseAlert(_ r: ChatError) -> Alert? {
|
||||
if internalErr == "SEUniqueID" {
|
||||
mkAlert(
|
||||
title: "Already connected?",
|
||||
message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(responseError(r)))."
|
||||
message: "It seems like you are already connected via this link. If it is not the case, there was an error (\(internalErr))."
|
||||
)
|
||||
} else {
|
||||
nil
|
||||
connectionErrorAlert(r)
|
||||
}
|
||||
default: nil
|
||||
default: connectionErrorAlert(r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1027,16 +1025,18 @@ func apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64) async throws -
|
||||
throw r.unexpected
|
||||
}
|
||||
|
||||
func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async throws -> Contact {
|
||||
let r: ChatResponse1 = try await chatSendCmd(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg))
|
||||
if case let .startedConnectionToContact(_, contact) = r { return contact }
|
||||
throw r.unexpected
|
||||
func apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?) async -> Contact? {
|
||||
let r: APIResult<ChatResponse1> = await chatApiSendCmd(.apiConnectPreparedContact(contactId: contactId, incognito: incognito, msg: msg))
|
||||
if case let .result(.startedConnectionToContact(_, contact)) = r { return contact }
|
||||
AlertManager.shared.showAlert(apiConnectResponseAlert(r))
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async throws -> GroupInfo {
|
||||
let r: ChatResponse1 = try await chatSendCmd(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg))
|
||||
if case let .startedConnectionToGroup(_, groupInfo) = r { return groupInfo }
|
||||
throw r.unexpected
|
||||
func apiConnectPreparedGroup(groupId: Int64, incognito: Bool, msg: MsgContent?) async -> GroupInfo? {
|
||||
let r: APIResult<ChatResponse1> = await chatApiSendCmd(.apiConnectPreparedGroup(groupId: groupId, incognito: incognito, msg: msg))
|
||||
if case let .result(.startedConnectionToGroup(_, groupInfo)) = r { return groupInfo }
|
||||
AlertManager.shared.showAlert(apiConnectResponseAlert(r))
|
||||
return nil
|
||||
}
|
||||
|
||||
func apiConnectContactViaAddress(incognito: Bool, contactId: Int64) async -> (Contact?, Alert?) {
|
||||
|
||||
@@ -405,14 +405,15 @@ struct ComposeView: View {
|
||||
}
|
||||
|
||||
if chat.chatInfo.groupInfo?.nextConnectPrepared == true {
|
||||
Button(action: connectPreparedGroup) {
|
||||
if chat.chatInfo.groupInfo?.businessChat == nil {
|
||||
if chat.chatInfo.groupInfo?.businessChat == nil {
|
||||
Button(action: connectPreparedGroup) {
|
||||
Label("Join group", systemImage: "person.2.fill")
|
||||
} else {
|
||||
Label("Connect", systemImage: "briefcase.fill")
|
||||
}
|
||||
.frame(height: 60)
|
||||
.disabled(composeState.inProgress)
|
||||
} else {
|
||||
sendContactRequestView(disableSendButton, icon: "briefcase.fill", sendRequest: connectPreparedGroup)
|
||||
}
|
||||
.frame(height: 60)
|
||||
} else if contact?.nextSendGrpInv == true {
|
||||
contextSendMessageToConnect("Send direct message to connect")
|
||||
Divider()
|
||||
@@ -428,24 +429,9 @@ struct ComposeView: View {
|
||||
Label("Connect", systemImage: "person.fill.badge.plus")
|
||||
}
|
||||
.frame(height: 60)
|
||||
.disabled(composeState.inProgress)
|
||||
case .con:
|
||||
HStack (alignment: .center) {
|
||||
sendMessageView(
|
||||
disableSendButton,
|
||||
placeholder: NSLocalizedString("Add message", comment: "placeholder for sending contact request"),
|
||||
sendToConnect: sendConnectPreparedContactRequest
|
||||
)
|
||||
if composeState.whitespaceOnly {
|
||||
Button(action: sendConnectPreparedContactRequest) {
|
||||
HStack {
|
||||
Text("Connect").fontWeight(.medium)
|
||||
Image(systemName: "person.fill.badge.plus")
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
sendContactRequestView(disableSendButton, icon: "person.fill.badge.plus", sendRequest: sendConnectPreparedContactRequest)
|
||||
}
|
||||
} else if contact?.nextAcceptContactRequest == true, let crId = contact?.contactRequestId {
|
||||
ContextContactRequestActionsView(contactRequestId: crId)
|
||||
@@ -620,6 +606,27 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func sendContactRequestView(_ disableSendButton: Bool, icon: String, sendRequest: @escaping () -> Void) -> some View {
|
||||
HStack (alignment: .center) {
|
||||
sendMessageView(
|
||||
disableSendButton,
|
||||
placeholder: NSLocalizedString("Add message", comment: "placeholder for sending contact request"),
|
||||
sendToConnect: sendRequest
|
||||
)
|
||||
if composeState.whitespaceOnly {
|
||||
Button(action: sendRequest) {
|
||||
HStack {
|
||||
Text("Connect").fontWeight(.medium)
|
||||
Image(systemName: icon)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.disabled(composeState.inProgress)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 12)
|
||||
}
|
||||
|
||||
private func sendMessageView(_ disableSendButton: Bool, placeholder: String? = nil, sendToConnect: (() -> Void)? = nil) -> some View {
|
||||
ZStack(alignment: .leading) {
|
||||
SendMessageView(
|
||||
@@ -695,6 +702,7 @@ struct ComposeView: View {
|
||||
Task {
|
||||
do {
|
||||
if let mc = connectCheckLinkPreview() {
|
||||
await sending()
|
||||
let contact = try await apiSendMemberContactInvitation(chat.chatInfo.apiId, mc)
|
||||
await MainActor.run {
|
||||
self.chatModel.updateContact(contact)
|
||||
@@ -704,6 +712,7 @@ struct ComposeView: View {
|
||||
AlertManager.shared.showAlertMsg(title: "Empty message!")
|
||||
}
|
||||
} catch {
|
||||
await MainActor.run { composeState.inProgress = false }
|
||||
logger.error("ChatView.sendMemberContactInvitation error: \(error.localizedDescription)")
|
||||
AlertManager.shared.showAlertMsg(title: "Error sending member contact invitation", message: "Error: \(responseError(error))")
|
||||
}
|
||||
@@ -730,32 +739,30 @@ struct ComposeView: View {
|
||||
|
||||
private func sendConnectPreparedContact() {
|
||||
Task {
|
||||
do {
|
||||
let mc = connectCheckLinkPreview()
|
||||
let contact = try await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc)
|
||||
await sending()
|
||||
let mc = connectCheckLinkPreview()
|
||||
if let contact = await apiConnectPreparedContact(contactId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) {
|
||||
await MainActor.run {
|
||||
self.chatModel.updateContact(contact)
|
||||
clearState()
|
||||
}
|
||||
} catch {
|
||||
logger.error("ChatView.sendConnectPreparedContact error: \(error.localizedDescription)")
|
||||
AlertManager.shared.showAlertMsg(title: "Error connecting with contact", message: "Error: \(responseError(error))")
|
||||
} else {
|
||||
await MainActor.run { composeState.inProgress = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connectPreparedGroup() {
|
||||
Task {
|
||||
do {
|
||||
let mc = connectCheckLinkPreview()
|
||||
let groupInfo = try await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc)
|
||||
await sending()
|
||||
let mc = connectCheckLinkPreview()
|
||||
if let groupInfo = await apiConnectPreparedGroup(groupId: chat.chatInfo.apiId, incognito: incognitoGroupDefault.get(), msg: mc) {
|
||||
await MainActor.run {
|
||||
self.chatModel.updateGroup(groupInfo)
|
||||
clearState()
|
||||
}
|
||||
} catch {
|
||||
logger.error("ChatView.connectPreparedGroup error: \(error.localizedDescription)")
|
||||
AlertManager.shared.showAlertMsg(title: "Error joining group", message: "Error: \(responseError(error))")
|
||||
} else {
|
||||
await MainActor.run { composeState.inProgress = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1094,10 +1101,6 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func sending() async {
|
||||
await MainActor.run { composeState.inProgress = true }
|
||||
}
|
||||
|
||||
func updateMessage(_ ei: ChatItem, live: Bool) async -> ChatItem? {
|
||||
if let oldMsgContent = ei.content.msgContent {
|
||||
do {
|
||||
@@ -1270,6 +1273,10 @@ struct ComposeView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func sending() async {
|
||||
await MainActor.run { composeState.inProgress = true }
|
||||
}
|
||||
|
||||
private func startVoiceMessageRecording() async {
|
||||
startingRecording = true
|
||||
let fileName = generateNewFileName("voice", "m4a")
|
||||
|
||||
@@ -13,60 +13,66 @@ struct ContextContactRequestActionsView: View {
|
||||
@EnvironmentObject var theme: AppTheme
|
||||
var contactRequestId: Int64
|
||||
@UserDefault(DEFAULT_TOOLBAR_MATERIAL) private var toolbarMaterial = ToolbarMaterial.defaultMaterial
|
||||
@State private var inProgress = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
.foregroundColor(.red)
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
showRejectRequestAlert(contactRequestId)
|
||||
Button(role: .destructive, action: showRejectRequestAlert) {
|
||||
Label("Reject", systemImage: "multiply")
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 60)
|
||||
|
||||
Label("Accept", systemImage: "checkmark").foregroundColor(theme.colors.primary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
Button {
|
||||
if ChatModel.shared.addressShortLinkDataSet {
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
acceptRequest()
|
||||
} else {
|
||||
showAcceptRequestAlert(contactRequestId)
|
||||
showAcceptRequestAlert()
|
||||
}
|
||||
} label: {
|
||||
Label("Accept", systemImage: "checkmark")
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 60)
|
||||
}
|
||||
.frame(minHeight: 60)
|
||||
.disabled(inProgress)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(ToolbarMaterial.material(toolbarMaterial))
|
||||
}
|
||||
}
|
||||
|
||||
func showRejectRequestAlert(_ contactRequestId: Int64) {
|
||||
showAlert(
|
||||
NSLocalizedString("Reject contact request", comment: "alert title"),
|
||||
message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in
|
||||
Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) }
|
||||
},
|
||||
cancelAlertAction
|
||||
]}
|
||||
)
|
||||
}
|
||||
private func showRejectRequestAlert() {
|
||||
showAlert(
|
||||
NSLocalizedString("Reject contact request", comment: "alert title"),
|
||||
message: NSLocalizedString("The sender will NOT be notified", comment: "alert message"),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Reject", comment: "alert action"), style: .destructive) { _ in
|
||||
Task { await rejectContactRequest(contactRequestId, dismissToChatList: true) }
|
||||
},
|
||||
cancelAlertAction
|
||||
]}
|
||||
)
|
||||
}
|
||||
|
||||
func showAcceptRequestAlert(_ contactRequestId: Int64) {
|
||||
showAlert(
|
||||
NSLocalizedString("Accept contact request", comment: "alert title"),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in
|
||||
Task { await acceptContactRequest(incognito: false, contactRequestId: contactRequestId) }
|
||||
},
|
||||
UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in
|
||||
Task { await acceptContactRequest(incognito: true, contactRequestId: contactRequestId) }
|
||||
},
|
||||
cancelAlertAction
|
||||
]}
|
||||
)
|
||||
private func showAcceptRequestAlert() {
|
||||
showAlert(
|
||||
NSLocalizedString("Accept contact request", comment: "alert title"),
|
||||
actions: {[
|
||||
UIAlertAction(title: NSLocalizedString("Accept", comment: "alert action"), style: .default) { _ in
|
||||
acceptRequest()
|
||||
},
|
||||
UIAlertAction(title: NSLocalizedString("Accept incognito", comment: "alert action"), style: .default) { _ in
|
||||
acceptRequest(incognito: true)
|
||||
},
|
||||
cancelAlertAction
|
||||
]}
|
||||
)
|
||||
}
|
||||
|
||||
private func acceptRequest(incognito: Bool = false) {
|
||||
inProgress = true
|
||||
Task {
|
||||
await acceptContactRequest(incognito: false, contactRequestId: contactRequestId)
|
||||
await MainActor.run { inProgress = false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
|
||||
@@ -887,7 +887,6 @@ struct GroupPreferencesButton: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -90,8 +90,15 @@ let okAlertAction = UIAlertAction(title: NSLocalizedString("Ok", comment: "alert
|
||||
|
||||
let cancelAlertAction = UIAlertAction(title: NSLocalizedString("Cancel", comment: "alert button"), style: .cancel)
|
||||
|
||||
let alertProfileImageSize: CGFloat = 103
|
||||
|
||||
let alertWidth: CGFloat = 270
|
||||
|
||||
let alertButtonHeight: CGFloat = 44
|
||||
|
||||
class OpenChatAlertViewController: UIViewController {
|
||||
private let profileName: String
|
||||
private let profileFullName: String
|
||||
private let profileImage: UIView
|
||||
private let cancelTitle: String
|
||||
private let confirmTitle: String
|
||||
@@ -100,6 +107,7 @@ class OpenChatAlertViewController: UIViewController {
|
||||
|
||||
init(
|
||||
profileName: String,
|
||||
profileFullName: String,
|
||||
profileImage: UIView,
|
||||
cancelTitle: String = "Cancel",
|
||||
confirmTitle: String = "Open",
|
||||
@@ -107,6 +115,7 @@ class OpenChatAlertViewController: UIViewController {
|
||||
onConfirm: @escaping () -> Void
|
||||
) {
|
||||
self.profileName = profileName
|
||||
self.profileFullName = profileFullName
|
||||
self.profileImage = profileImage
|
||||
self.cancelTitle = cancelTitle
|
||||
self.confirmTitle = confirmTitle
|
||||
@@ -135,54 +144,72 @@ class OpenChatAlertViewController: UIViewController {
|
||||
// Profile image sizing
|
||||
profileImage.translatesAutoresizingMaskIntoConstraints = false
|
||||
NSLayoutConstraint.activate([
|
||||
profileImage.widthAnchor.constraint(equalToConstant: 60),
|
||||
profileImage.heightAnchor.constraint(equalToConstant: 60)
|
||||
profileImage.widthAnchor.constraint(equalToConstant: alertProfileImageSize),
|
||||
profileImage.heightAnchor.constraint(equalToConstant: alertProfileImageSize)
|
||||
])
|
||||
|
||||
// Name label
|
||||
let nameLabel = UILabel()
|
||||
nameLabel.text = profileName
|
||||
nameLabel.font = UIFont.systemFont(ofSize: 18, weight: .semibold)
|
||||
nameLabel.font = UIFont.preferredFont(forTextStyle: .headline)
|
||||
nameLabel.textColor = .label
|
||||
nameLabel.numberOfLines = 2
|
||||
nameLabel.textAlignment = .center
|
||||
nameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
var profileViews = [profileImage, nameLabel]
|
||||
|
||||
// Full name label
|
||||
if !profileFullName.isEmpty && profileFullName != profileName {
|
||||
let fullNameLabel = UILabel()
|
||||
fullNameLabel.text = profileFullName
|
||||
fullNameLabel.font = UIFont.preferredFont(forTextStyle: .subheadline)
|
||||
fullNameLabel.textColor = .label
|
||||
fullNameLabel.numberOfLines = 2
|
||||
fullNameLabel.textAlignment = .center
|
||||
fullNameLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
profileViews.append(fullNameLabel)
|
||||
}
|
||||
|
||||
// Horizontal stack for image + name
|
||||
let hStack = UIStackView(arrangedSubviews: [profileImage, nameLabel])
|
||||
hStack.axis = .horizontal
|
||||
hStack.spacing = 12
|
||||
hStack.alignment = .center
|
||||
hStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
let stack = UIStackView(arrangedSubviews: profileViews)
|
||||
stack.axis = .vertical
|
||||
stack.spacing = 12
|
||||
stack.alignment = .center
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let topRowContainer = UIView()
|
||||
topRowContainer.translatesAutoresizingMaskIntoConstraints = false
|
||||
topRowContainer.addSubview(hStack)
|
||||
topRowContainer.addSubview(stack)
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
hStack.topAnchor.constraint(equalTo: topRowContainer.topAnchor),
|
||||
hStack.bottomAnchor.constraint(equalTo: topRowContainer.bottomAnchor),
|
||||
hStack.leadingAnchor.constraint(equalTo: topRowContainer.leadingAnchor, constant: 20),
|
||||
hStack.trailingAnchor.constraint(equalTo: topRowContainer.trailingAnchor, constant: -20)
|
||||
stack.topAnchor.constraint(equalTo: topRowContainer.topAnchor),
|
||||
stack.bottomAnchor.constraint(equalTo: topRowContainer.bottomAnchor),
|
||||
stack.leadingAnchor.constraint(equalTo: topRowContainer.leadingAnchor, constant: 20),
|
||||
stack.trailingAnchor.constraint(equalTo: topRowContainer.trailingAnchor, constant: -20)
|
||||
])
|
||||
|
||||
// Buttons
|
||||
let cancelButton = UIButton(type: .system)
|
||||
cancelButton.setTitle(cancelTitle, for: .normal)
|
||||
cancelButton.titleLabel?.font = UIFont.systemFont(ofSize: 15)
|
||||
let bodyDescr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
|
||||
cancelButton.titleLabel?.font = UIFont(descriptor: bodyDescr.withSymbolicTraits(.traitBold) ?? bodyDescr, size: 0)
|
||||
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
|
||||
|
||||
let confirmButton = UIButton(type: .system)
|
||||
confirmButton.setTitle(confirmTitle, for: .normal)
|
||||
confirmButton.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
|
||||
confirmButton.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body)
|
||||
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
|
||||
|
||||
let verticalButtons = cancelButton.intrinsicContentSize.width + 20 >= alertWidth / 2 || confirmButton.intrinsicContentSize.width + 20 >= alertWidth / 2
|
||||
|
||||
// Button stack with equal width buttons
|
||||
let buttonStack = UIStackView(arrangedSubviews: [cancelButton, confirmButton])
|
||||
buttonStack.axis = .horizontal
|
||||
let buttonStack = UIStackView(arrangedSubviews: verticalButtons ? [confirmButton, cancelButton] : [cancelButton, confirmButton])
|
||||
buttonStack.axis = verticalButtons ? .vertical : .horizontal
|
||||
buttonStack.distribution = .fillEqually
|
||||
buttonStack.spacing = 0 // no spacing, use divider instead
|
||||
buttonStack.translatesAutoresizingMaskIntoConstraints = false
|
||||
buttonStack.heightAnchor.constraint(greaterThanOrEqualToConstant: 50).isActive = true
|
||||
buttonStack.heightAnchor.constraint(greaterThanOrEqualToConstant: alertButtonHeight * (verticalButtons ? 2 : 1)).isActive = true
|
||||
|
||||
// Vertical stack containing hStack and buttonStack
|
||||
let vStack = UIStackView(arrangedSubviews: [topRowContainer, buttonStack])
|
||||
@@ -195,23 +222,38 @@ class OpenChatAlertViewController: UIViewController {
|
||||
|
||||
// Add horizontal divider above buttons
|
||||
let horizontalDivider = UIView()
|
||||
horizontalDivider.backgroundColor = UIColor(white: 0.85, alpha: 1)
|
||||
horizontalDivider.backgroundColor = UIColor.separator
|
||||
horizontalDivider.translatesAutoresizingMaskIntoConstraints = false
|
||||
containerView.addSubview(horizontalDivider)
|
||||
|
||||
// Add vertical divider between buttons
|
||||
let verticalDivider = UIView()
|
||||
verticalDivider.backgroundColor = UIColor(white: 0.85, alpha: 1)
|
||||
verticalDivider.translatesAutoresizingMaskIntoConstraints = false
|
||||
buttonStack.addSubview(verticalDivider)
|
||||
// Add divider between buttons
|
||||
let buttonDivider = UIView()
|
||||
buttonDivider.backgroundColor = UIColor.separator
|
||||
buttonDivider.translatesAutoresizingMaskIntoConstraints = false
|
||||
buttonStack.addSubview(buttonDivider)
|
||||
|
||||
// Constraints
|
||||
let buttonDividerConstraints = if verticalButtons {
|
||||
[
|
||||
buttonDivider.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
||||
buttonDivider.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||
buttonDivider.centerYAnchor.constraint(equalTo: buttonStack.centerYAnchor),
|
||||
buttonDivider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale)
|
||||
]
|
||||
} else {
|
||||
[
|
||||
buttonDivider.topAnchor.constraint(equalTo: buttonStack.topAnchor),
|
||||
buttonDivider.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||
buttonDivider.centerXAnchor.constraint(equalTo: buttonStack.centerXAnchor),
|
||||
buttonDivider.widthAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale)
|
||||
]
|
||||
}
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
// Container view centering and fixed width
|
||||
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
|
||||
containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
containerView.widthAnchor.constraint(equalToConstant: 280),
|
||||
containerView.widthAnchor.constraint(equalToConstant: alertWidth),
|
||||
|
||||
// Vertical stack padding inside containerView
|
||||
vStack.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 20),
|
||||
@@ -220,20 +262,14 @@ class OpenChatAlertViewController: UIViewController {
|
||||
vStack.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 0),
|
||||
|
||||
// Center hStack horizontally inside vStack's padded width
|
||||
hStack.centerXAnchor.constraint(equalTo: vStack.centerXAnchor),
|
||||
stack.centerXAnchor.constraint(equalTo: vStack.centerXAnchor),
|
||||
|
||||
// Horizontal divider above buttons
|
||||
horizontalDivider.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
|
||||
horizontalDivider.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
|
||||
horizontalDivider.bottomAnchor.constraint(equalTo: buttonStack.topAnchor),
|
||||
horizontalDivider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale),
|
||||
|
||||
// Vertical divider between buttons
|
||||
verticalDivider.widthAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale),
|
||||
verticalDivider.topAnchor.constraint(equalTo: buttonStack.topAnchor),
|
||||
verticalDivider.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
|
||||
verticalDivider.centerXAnchor.constraint(equalTo: buttonStack.centerXAnchor)
|
||||
])
|
||||
horizontalDivider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale)
|
||||
] + buttonDividerConstraints)
|
||||
}
|
||||
|
||||
@objc private func cancelTapped() {
|
||||
@@ -252,6 +288,7 @@ class OpenChatAlertViewController: UIViewController {
|
||||
|
||||
func showOpenChatAlert<Content: View>(
|
||||
profileName: String,
|
||||
profileFullName: String,
|
||||
profileImage: Content,
|
||||
theme: AppTheme,
|
||||
cancelTitle: String = "Cancel",
|
||||
@@ -267,6 +304,7 @@ func showOpenChatAlert<Content: View>(
|
||||
if let topVC = getTopViewController() {
|
||||
let alertVC = OpenChatAlertViewController(
|
||||
profileName: profileName,
|
||||
profileFullName: profileFullName,
|
||||
profileImage: hostedView,
|
||||
cancelTitle: cancelTitle,
|
||||
confirmTitle: confirmTitle,
|
||||
|
||||
@@ -1014,11 +1014,12 @@ private func showPrepareContactAlert(
|
||||
) {
|
||||
showOpenChatAlert(
|
||||
profileName: contactShortLinkData.profile.displayName,
|
||||
profileFullName: contactShortLinkData.profile.fullName,
|
||||
profileImage:
|
||||
ProfileImage(
|
||||
imageStr: contactShortLinkData.profile.image,
|
||||
iconName: contactShortLinkData.business ? "briefcase.circle.fill" : "person.crop.circle.fill",
|
||||
size: 60
|
||||
size: alertProfileImageSize
|
||||
),
|
||||
theme: theme,
|
||||
cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"),
|
||||
@@ -1054,10 +1055,11 @@ private func showPrepareGroupAlert(
|
||||
) {
|
||||
showOpenChatAlert(
|
||||
profileName: groupShortLinkData.groupProfile.displayName,
|
||||
profileImage: ProfileImage(imageStr: groupShortLinkData.groupProfile.image, iconName: "person.2.circle.fill", size: 60),
|
||||
profileFullName: groupShortLinkData.groupProfile.fullName,
|
||||
profileImage: ProfileImage(imageStr: groupShortLinkData.groupProfile.image, iconName: "person.2.circle.fill", size: alertProfileImageSize),
|
||||
theme: theme,
|
||||
cancelTitle: NSLocalizedString("Cancel", comment: "new chat action"),
|
||||
confirmTitle: NSLocalizedString("Open chat", comment: "new chat action"),
|
||||
confirmTitle: NSLocalizedString("Open group", comment: "new chat action"),
|
||||
onCancel: { cleanup?() },
|
||||
onConfirm: {
|
||||
Task {
|
||||
|
||||
@@ -178,8 +178,8 @@
|
||||
64C3B0212A0D359700E19930 /* CustomTimePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64C3B0202A0D359700E19930 /* CustomTimePicker.swift */; };
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829982D54AEED006B9E89 /* libgmp.a */; };
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C829992D54AEEE006B9E89 /* libffi.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */; };
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */; };
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */; };
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64C8299C2D54AEEE006B9E89 /* libgmpxx.a */; };
|
||||
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */; };
|
||||
64D0C2C229FA57AB00B38D5F /* UserAddressLearnMore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */; };
|
||||
@@ -543,8 +543,8 @@
|
||||
64C3B0202A0D359700E19930 /* CustomTimePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomTimePicker.swift; sourceTree = "<group>"; };
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a"; sourceTree = "<group>"; };
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a"; sourceTree = "<group>"; };
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
64D0C2BF29F9688300B38D5F /* UserAddressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressView.swift; sourceTree = "<group>"; };
|
||||
64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAddressLearnMore.swift; sourceTree = "<group>"; };
|
||||
@@ -704,8 +704,8 @@
|
||||
64C8299D2D54AEEE006B9E89 /* libgmp.a in Frameworks */,
|
||||
64C8299E2D54AEEE006B9E89 /* libffi.a in Frameworks */,
|
||||
64C829A12D54AEEE006B9E89 /* libgmpxx.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a in Frameworks */,
|
||||
64C8299F2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a in Frameworks */,
|
||||
64C829A02D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a in Frameworks */,
|
||||
CE38A29C2C3FCD72005ED185 /* SwiftyGif in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -790,8 +790,8 @@
|
||||
64C829992D54AEEE006B9E89 /* libffi.a */,
|
||||
64C829982D54AEED006B9E89 /* libgmp.a */,
|
||||
64C8299C2D54AEEE006B9E89 /* libgmpxx.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-1Rh0qJZPahP19NRjkSYFiJ.a */,
|
||||
64C8299A2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy-ghc9.6.3.a */,
|
||||
64C8299B2D54AEEE006B9E89 /* libHSsimplex-chat-6.4.0.4-DWeDI2Xa9F86P3Q5uUF2Wy.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
|
||||
@@ -819,7 +819,7 @@ public enum SQLiteError: Decodable, Hashable {
|
||||
|
||||
public enum AgentErrorType: Decodable, Hashable {
|
||||
case CMD(cmdErr: CommandErrorType, errContext: String)
|
||||
case CONN(connErr: ConnectionErrorType)
|
||||
case CONN(connErr: ConnectionErrorType, errContext: String)
|
||||
case SMP(serverAddress: String, smpErr: ProtocolErrorType)
|
||||
case NTF(ntfErr: ProtocolErrorType)
|
||||
case XFTP(xftpErr: XFTPErrorType)
|
||||
|
||||
@@ -1372,6 +1372,8 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
case .some(.memberSupport(groupMember_: .none)):
|
||||
return nil
|
||||
}
|
||||
} else if groupInfo.nextConnectPrepared {
|
||||
return nil
|
||||
} else {
|
||||
switch groupInfo.membership.memberStatus {
|
||||
case .memRejected: return ("request to join rejected", nil)
|
||||
@@ -1421,7 +1423,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var groupInfo: GroupInfo? {
|
||||
switch self {
|
||||
case let .group(groupInfo, _): return groupInfo
|
||||
@@ -1531,7 +1533,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
public func ntfsEnabled(chatItem: ChatItem) -> Bool {
|
||||
ntfsEnabled(chatItem.meta.userMention)
|
||||
}
|
||||
|
||||
|
||||
public func ntfsEnabled(_ userMention: Bool) -> Bool {
|
||||
switch self.chatSettings?.enableNtfs {
|
||||
case .all: true
|
||||
@@ -1547,7 +1549,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
default: return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var nextNtfMode: MsgFilter? {
|
||||
self.chatSettings?.enableNtfs.nextMode(mentions: hasMentions)
|
||||
}
|
||||
@@ -1596,7 +1598,7 @@ public enum ChatInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
case .invalidJSON: return .now
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func ttl(_ globalTTL: ChatItemTTL) -> ChatTTL {
|
||||
switch self {
|
||||
case let .direct(contact):
|
||||
@@ -1644,7 +1646,7 @@ public struct ChatData: Decodable, Identifiable, Hashable, ChatLike {
|
||||
self.chatItems = chatItems
|
||||
self.chatStats = chatStats
|
||||
}
|
||||
|
||||
|
||||
public static func invalidJSON(_ json: Data?) -> ChatData {
|
||||
ChatData(
|
||||
chatInfo: .invalidJSON(json: json),
|
||||
@@ -2085,15 +2087,14 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
var chatTs: Date?
|
||||
public var connLinkToConnect: CreatedConnLink?
|
||||
public var connLinkStartedConnection: Bool
|
||||
public var preparedGroup: PreparedGroup?
|
||||
public var uiThemes: ThemeModeOverrides?
|
||||
public var membersRequireAttention: Int
|
||||
|
||||
public var id: ChatId { get { "#\(groupId)" } }
|
||||
public var apiId: Int64 { get { groupId } }
|
||||
public var ready: Bool { get { true } }
|
||||
public var nextConnectPrepared: Bool { connLinkToConnect != nil && !connLinkStartedConnection }
|
||||
public var nextConnectPrepared: Bool { if let preparedGroup { !preparedGroup.connLinkStartedConnection } else { false } }
|
||||
public var displayName: String { localAlias == "" ? groupProfile.displayName : localAlias }
|
||||
public var fullName: String { get { groupProfile.fullName } }
|
||||
public var image: String? { get { groupProfile.image } }
|
||||
@@ -2134,13 +2135,17 @@ public struct GroupInfo: Identifiable, Decodable, NamedChat, Hashable {
|
||||
chatSettings: ChatSettings.defaults,
|
||||
createdAt: .now,
|
||||
updatedAt: .now,
|
||||
connLinkStartedConnection: false,
|
||||
membersRequireAttention: 0,
|
||||
chatTags: [],
|
||||
localAlias: ""
|
||||
)
|
||||
}
|
||||
|
||||
public struct PreparedGroup: Decodable, Hashable {
|
||||
public var connLinkToConnect: CreatedConnLink?
|
||||
public var connLinkStartedConnection: Bool
|
||||
}
|
||||
|
||||
public struct GroupRef: Decodable, Hashable {
|
||||
public var groupId: Int64
|
||||
var localDisplayName: GroupName
|
||||
@@ -2298,7 +2303,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
|
||||
? String.localizedStringWithFormat(NSLocalizedString("Past member %@", comment: "past/unknown group member"), name)
|
||||
: name
|
||||
}
|
||||
|
||||
|
||||
public var localAliasAndFullName: String {
|
||||
get {
|
||||
let p = memberProfile
|
||||
@@ -2378,7 +2383,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
|
||||
return memberStatus != .memRemoved && memberStatus != .memLeft && memberRole < .admin
|
||||
&& userRole >= .admin && userRole >= memberRole && groupInfo.membership.memberActive
|
||||
}
|
||||
|
||||
|
||||
public var canReceiveReports: Bool {
|
||||
memberRole >= .moderator && versionRange.maxVersion >= REPORTS_VERSION
|
||||
}
|
||||
@@ -2390,7 +2395,7 @@ public struct GroupMember: Identifiable, Decodable, Hashable {
|
||||
memberChatVRange
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var memberIncognito: Bool {
|
||||
memberProfile.profileId != memberContactProfileId
|
||||
}
|
||||
@@ -2446,7 +2451,7 @@ public enum GroupMemberRole: String, Identifiable, CaseIterable, Comparable, Cod
|
||||
public var id: Self { self }
|
||||
|
||||
public static var supportedRoles: [GroupMemberRole] = [.observer, .member, .admin, .owner]
|
||||
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case .observer: return NSLocalizedString("observer", comment: "member role")
|
||||
@@ -2602,7 +2607,7 @@ public enum ConnectionEntity: Decodable, Hashable {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// public var localDisplayName: String? {
|
||||
// switch self {
|
||||
// case let .rcvDirectMsgConnection(conn, contact):
|
||||
@@ -2643,7 +2648,7 @@ public struct NtfMsgInfo: Decodable, Hashable {
|
||||
public enum RcvNtfMsgInfo: Decodable {
|
||||
case info(ntfMsgInfo: NtfMsgInfo?)
|
||||
case error(ntfMsgError: AgentErrorType)
|
||||
|
||||
|
||||
@inline(__always)
|
||||
public var noMsg: Bool {
|
||||
if case let .info(msg) = self { msg == nil } else { true }
|
||||
@@ -2703,7 +2708,7 @@ public struct CIMentionMember: Decodable, Hashable {
|
||||
public struct CIMention: Decodable, Hashable {
|
||||
public var memberId: String
|
||||
public var memberRef: CIMentionMember?
|
||||
|
||||
|
||||
public init(groupMember m: GroupMember) {
|
||||
self.memberId = m.memberId
|
||||
self.memberRef = CIMentionMember(
|
||||
@@ -2954,7 +2959,7 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
|
||||
default: return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var isReport: Bool {
|
||||
switch content {
|
||||
case let .sndMsgContent(msgContent), let .rcvMsgContent(msgContent):
|
||||
@@ -3054,14 +3059,14 @@ public struct ChatItem: Identifiable, Decodable, Hashable {
|
||||
file: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
public static func getReportSample(text: String, reason: ReportReason, item: ChatItem, sender: GroupMember? = nil) -> ChatItem {
|
||||
let chatDir = if let sender = sender {
|
||||
CIDirection.groupRcv(groupMember: sender)
|
||||
} else {
|
||||
CIDirection.groupSnd
|
||||
}
|
||||
|
||||
|
||||
return ChatItem(
|
||||
chatDir: chatDir,
|
||||
meta: CIMeta(
|
||||
@@ -3175,7 +3180,7 @@ public enum CIDirection: Decodable, Hashable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func sameDirection(_ dir: CIDirection) -> Bool {
|
||||
switch (self, dir) {
|
||||
case let (.groupRcv(m1), .groupRcv(m2)): m1.groupMemberId == m2.groupMemberId
|
||||
@@ -3311,7 +3316,7 @@ public enum CIStatus: Decodable, Hashable {
|
||||
case .invalid: return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var sent: Bool {
|
||||
switch self {
|
||||
case .sndNew: true
|
||||
@@ -4124,7 +4129,7 @@ public enum FileError: Decodable, Equatable, Hashable {
|
||||
case let .other(fileError): String.localizedStringWithFormat(NSLocalizedString("Error: %@", comment: "file error text"), fileError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var moreInfoButton: (label: LocalizedStringKey, link: URL)? {
|
||||
switch self {
|
||||
case .blocked: ("How it works", contentModerationPostLink)
|
||||
@@ -4426,7 +4431,7 @@ public enum ReportReason: Hashable {
|
||||
case profile
|
||||
case other
|
||||
case unknown(type: String)
|
||||
|
||||
|
||||
public static var supportedReasons: [ReportReason] = [.spam, .illegal, .community, .profile, .other]
|
||||
|
||||
public var text: String {
|
||||
@@ -4439,7 +4444,7 @@ public enum ReportReason: Hashable {
|
||||
case let .unknown(type): return type
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var attrString: NSAttributedString {
|
||||
let descr = UIFontDescriptor.preferredFontDescriptor(withTextStyle: .body)
|
||||
return NSAttributedString(string: text.isEmpty ? self.text : "\(self.text): ", attributes: [
|
||||
@@ -4486,7 +4491,7 @@ public struct LinkPreview: Codable, Equatable, Hashable {
|
||||
self.description = description
|
||||
self.image = image
|
||||
}
|
||||
|
||||
|
||||
public var uri: URL
|
||||
public var title: String
|
||||
// TODO remove once optional in haskell
|
||||
@@ -4535,7 +4540,7 @@ public enum NtfTknStatus: String, Decodable, Hashable {
|
||||
case .expired: NSLocalizedString("Expired", comment: "token status text")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func info(register: Bool) -> String {
|
||||
switch self {
|
||||
case .new: return NSLocalizedString("Please wait for token to be registered.", comment: "token info")
|
||||
@@ -4913,9 +4918,9 @@ public enum ChatItemTTL: Identifiable, Comparable, Hashable {
|
||||
public enum ChatTTL: Identifiable, Hashable {
|
||||
case userDefault(ChatItemTTL)
|
||||
case chat(ChatItemTTL)
|
||||
|
||||
|
||||
public var id: Self { self }
|
||||
|
||||
|
||||
public var text: String {
|
||||
switch self {
|
||||
case let .chat(ttl): return ttl.deleteAfterText
|
||||
@@ -4924,21 +4929,21 @@ public enum ChatTTL: Identifiable, Hashable {
|
||||
ttl.deleteAfterText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var neverExpires: Bool {
|
||||
switch self {
|
||||
case let .chat(ttl): return ttl.seconds == 0
|
||||
case let .userDefault(ttl): return ttl.seconds == 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var value: Int64? {
|
||||
switch self {
|
||||
case let .chat(ttl): return ttl.seconds
|
||||
case .userDefault: return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public var usingDefault: Bool {
|
||||
switch self {
|
||||
case .userDefault: return true
|
||||
@@ -4951,9 +4956,9 @@ public struct ChatTag: Decodable, Hashable {
|
||||
public var chatTagId: Int64
|
||||
public var chatTagText: String
|
||||
public var chatTagEmoji: String?
|
||||
|
||||
|
||||
public var id: Int64 { chatTagId }
|
||||
|
||||
|
||||
public init(chatTagId: Int64, chatTagText: String, chatTagEmoji: String?) {
|
||||
self.chatTagId = chatTagId
|
||||
self.chatTagText = chatTagText
|
||||
|
||||
+2
-2
@@ -7055,7 +7055,7 @@ sealed class SQLiteError {
|
||||
sealed class AgentErrorType {
|
||||
val string: String get() = when (this) {
|
||||
is CMD -> "CMD ${cmdErr.string} $errContext"
|
||||
is CONN -> "CONN ${connErr.string}"
|
||||
is CONN -> "CONN ${connErr.string} $errContext"
|
||||
is SMP -> "SMP ${smpErr.string}"
|
||||
// is NTF -> "NTF ${ntfErr.string}"
|
||||
is XFTP -> "XFTP ${xftpErr.string}"
|
||||
@@ -7068,7 +7068,7 @@ sealed class AgentErrorType {
|
||||
is INACTIVE -> "INACTIVE"
|
||||
}
|
||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType, val errContext: String): AgentErrorType()
|
||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType, val errContext: String): AgentErrorType()
|
||||
@Serializable @SerialName("SMP") class SMP(val serverAddress: String, val smpErr: SMPErrorType): AgentErrorType()
|
||||
// @Serializable @SerialName("NTF") class NTF(val ntfErr: SMPErrorType): AgentErrorType()
|
||||
@Serializable @SerialName("XFTP") class XFTP(val xftpErr: XFTPErrorType): AgentErrorType()
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 976bd3a389aaded78b4285541e6dddd6b2766149
|
||||
tag: b4bcfd325b43caefd9b649653c1b3ee6920bad61
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."976bd3a389aaded78b4285541e6dddd6b2766149" = "06mijsfnb9q9wa0lj49a24ajnw45qash7sc9ah95cd517bj6rnki";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."b4bcfd325b43caefd9b649653c1b3ee6920bad61" = "1mg01aj2cafrkiz2pdjp39x8rdbqip4jyn5p1vwbi5rj0fsdifkm";
|
||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||
|
||||
@@ -670,7 +670,7 @@ receiveFileEvt' user ft userApprovedRelays rcvInline_ filePath_ = do
|
||||
rctFileCancelled :: ChatError -> Bool
|
||||
rctFileCancelled = \case
|
||||
ChatErrorAgent (SMP _ SMP.AUTH) _ -> True
|
||||
ChatErrorAgent (CONN DUPLICATE) _ -> True
|
||||
ChatErrorAgent (CONN DUPLICATE _) _ -> True
|
||||
_ -> False
|
||||
|
||||
acceptFileReceive :: User -> RcvFileTransfer -> Bool -> Maybe Bool -> Maybe FilePath -> CM AChatItem
|
||||
|
||||
@@ -2477,7 +2477,7 @@ viewChatError isCmd logLevel testView = \case
|
||||
BROKER _ TIMEOUT | not isCmd -> []
|
||||
AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug || isCmd]
|
||||
AGENT (A_PROHIBITED e) -> [withConnEntity <> "error: AGENT A_PROHIBITED, " <> plain e | logLevel <= CLLWarning || isCmd]
|
||||
CONN NOT_FOUND -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning || isCmd]
|
||||
CONN NOT_FOUND _ -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning || isCmd]
|
||||
CRITICAL restart e -> [plain $ "critical error: " <> e] <> ["please restart the app" | restart]
|
||||
INTERNAL e -> [plain $ "internal error: " <> e]
|
||||
e -> [withConnEntity <> "smp agent error: " <> sShow e | logLevel <= CLLWarning || isCmd]
|
||||
|
||||
Reference in New Issue
Block a user