mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 20:29:53 +00:00
* api types * display for mentions and replys * picking of mentions * notifications (wip) * auto tagging * show selected mention * Divider and list bg * stop keyboard dismiss on scroll from ios 16 * change notification mode in all views * icon for mentions notification mode * make unread states work in memory and chat preview * preview fixes * fix unread status when mark read manually * update library * fixed padding * fix layout * use memberName * remove ChatNtfs, show mentions in context items and in drafts, make mentions a map in ComposeState * rework mentions (WIP) * better * show mention name containing @ in quotes * editing mentions * editing * mentionColor * opacity * refactor mention counter * fix unread layout --------- Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
159 lines
6.9 KiB
Swift
159 lines
6.9 KiB
Swift
//
|
|
// ErrorAlert.swift
|
|
// SimpleXChat
|
|
//
|
|
// Created by Levitating Pineapple on 20/07/2024.
|
|
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
|
|
public struct ErrorAlert: Error {
|
|
public let title: LocalizedStringKey
|
|
public let message: LocalizedStringKey?
|
|
public let actions: Optional<() -> AnyView>
|
|
|
|
public init(
|
|
title: LocalizedStringKey,
|
|
message: LocalizedStringKey? = nil
|
|
) {
|
|
self.title = title
|
|
self.message = message
|
|
self.actions = nil
|
|
}
|
|
|
|
public init<A: View>(
|
|
title: LocalizedStringKey,
|
|
message: LocalizedStringKey? = nil,
|
|
@ViewBuilder actions: @escaping () -> A
|
|
) {
|
|
self.title = title
|
|
self.message = message
|
|
self.actions = { AnyView(actions()) }
|
|
}
|
|
|
|
public init(_ title: LocalizedStringKey) {
|
|
self = ErrorAlert(title: title)
|
|
}
|
|
|
|
public init(_ error: any Error) {
|
|
self = if let chatResponse = error as? ChatResponse {
|
|
ErrorAlert(chatResponse)
|
|
} else {
|
|
ErrorAlert("\(error.localizedDescription)")
|
|
}
|
|
}
|
|
|
|
public init(_ chatError: ChatError) {
|
|
self = ErrorAlert("\(chatErrorString(chatError))")
|
|
}
|
|
|
|
public init(_ chatResponse: ChatResponse) {
|
|
self = if let networkErrorAlert = getNetworkErrorAlert(chatResponse) {
|
|
networkErrorAlert
|
|
} else {
|
|
ErrorAlert("\(responseError(chatResponse))")
|
|
}
|
|
}
|
|
}
|
|
|
|
extension LocalizedStringKey: @unchecked Sendable { }
|
|
|
|
extension View {
|
|
/// Bridges ``ErrorAlert`` to the generic alert API.
|
|
/// - Parameters:
|
|
/// - errorAlert: Binding to the Error, which is rendered in the alert
|
|
/// - actions: View Builder containing action buttons.
|
|
/// System defaults to `Ok` dismiss error action, when no actions are provided.
|
|
/// System implicitly adds `Cancel` action, if a destructive action is present
|
|
///
|
|
/// - Returns: View, which displays ErrorAlert?, when set.
|
|
@ViewBuilder public func alert<A: View>(
|
|
_ errorAlert: Binding<ErrorAlert?>,
|
|
@ViewBuilder actions: (ErrorAlert) -> A = { _ in EmptyView() }
|
|
) -> some View {
|
|
alert(
|
|
errorAlert.wrappedValue?.title ?? "",
|
|
isPresented: Binding<Bool>(
|
|
get: { errorAlert.wrappedValue != nil },
|
|
set: { if !$0 { errorAlert.wrappedValue = nil } }
|
|
),
|
|
actions: {
|
|
if let actions_ = errorAlert.wrappedValue?.actions {
|
|
actions_()
|
|
} else {
|
|
if let alert = errorAlert.wrappedValue { actions(alert) }
|
|
}
|
|
},
|
|
message: {
|
|
if let message = errorAlert.wrappedValue?.message {
|
|
Text(message)
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
public func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? {
|
|
switch r {
|
|
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):
|
|
return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
|
|
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))):
|
|
return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
|
|
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .HOST))):
|
|
return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).")
|
|
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))):
|
|
return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).")
|
|
case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))):
|
|
return smpProxyErrorAlert(proxyErr, serverAddress)
|
|
case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr))))):
|
|
return proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer)
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func smpProxyErrorAlert(_ proxyErr: ProxyError, _ srvAddr: String) -> ErrorAlert? {
|
|
switch proxyErr {
|
|
case .BROKER(brokerErr: .TIMEOUT):
|
|
return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.")
|
|
case .BROKER(brokerErr: .NETWORK):
|
|
return ErrorAlert(title: "Private routing error", message: "Error connecting to forwarding server \(serverHostname(srvAddr)). Please try later.")
|
|
case .BROKER(brokerErr: .HOST):
|
|
return ErrorAlert(title: "Private routing error", message: "Forwarding server address is incompatible with network settings: \(serverHostname(srvAddr)).")
|
|
case .BROKER(brokerErr: .TRANSPORT(.version)):
|
|
return ErrorAlert(title: "Private routing error", message: "Forwarding server version is incompatible with network settings: \(serverHostname(srvAddr)).")
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func proxyDestinationErrorAlert(_ proxyErr: ProxyError, _ proxyServer: String, _ relayServer: String) -> ErrorAlert? {
|
|
switch proxyErr {
|
|
case .BROKER(brokerErr: .TIMEOUT):
|
|
return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.")
|
|
case .BROKER(brokerErr: .NETWORK):
|
|
return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.")
|
|
case .NO_SESSION:
|
|
return ErrorAlert(title: "Private routing error", message: "Forwarding server \(serverHostname(proxyServer)) failed to connect to destination server \(serverHostname(relayServer)). Please try later.")
|
|
case .BROKER(brokerErr: .HOST):
|
|
return ErrorAlert(title: "Private routing error", message: "Destination server address of \(serverHostname(relayServer)) is incompatible with forwarding server \(serverHostname(proxyServer)) settings.")
|
|
case .BROKER(brokerErr: .TRANSPORT(.version)):
|
|
return ErrorAlert(title: "Private routing error", message: "Destination server version of \(serverHostname(relayServer)) is incompatible with forwarding server \(serverHostname(proxyServer)).")
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
public func serverHostname(_ srv: String) -> String {
|
|
parseServerAddress(srv)?.hostnames.first ?? srv
|
|
}
|
|
|
|
public func mtrErrorDescription(_ err: MTRError) -> LocalizedStringKey {
|
|
switch err {
|
|
case let .noDown(dbMigrations):
|
|
"database version is newer than the app, but no down migration for: \(dbMigrations.joined(separator: ", "))"
|
|
case let .different(appMigration, dbMigration):
|
|
"different migration in the app/database: \(appMigration) / \(dbMigration)"
|
|
}
|
|
}
|