Files
simplex-chat/apps/ios/SimpleXChat/ErrorAlert.swift
Evgeny Poberezkin 6865515f43 ios: share extension (#4466)
* ios: share extension (#4414)

* ios: add share extension target

* ios: Add UI

* ios: send file from share-sheet

* image utils

* ShareError

* error handling; ui-cleanup

* progress bar; completion for direct chat

* cleanup

* cleanup

* ios: unify filter and sort between forward and share sheets

* ios: match share sheet styling with the main app

* ios: fix text input stroke width

* ios: align compose views

* more of the same...

* ShareAPI

* remove combine

* minor

* Better error descriptions

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: enable file sending workers in share extension (#4474)

* ios: align compose background, row height and fallback images for share-sheet (#4467)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: coordinate database access between share extension, the app and notifications extension (#4472)

* ios: database management proposal

* Add SEState

* Global event loop

* minor

* reset state

* use apiCreateItem for local chats

* simplify waiting for suspension

* loading bar

* Dismiss share sheet with error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* send image message (#4481)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: improve share extension completion handling (#4486)

* improve completion handling

* minor

* show only spinner for group send

* rework event loop, errorAlert

* group chat timeout loading bar

* state machine WIP

* event loop actor

* alert

* errors text

* default

* file error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: add remaining share types; process attachment in background on launch (#4510)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: share extension - additional alerts and media previews (#4521)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

* media previews

* network timeout alert

* revert framework compiler optimisation flag

* suspend chat after sheet dismiss

* activate chat

* update

* fix search

* sendMessageColor, file preview, chat deselect, simplify error action

* cleanup

* interupt database closing when sheet is reopened quickly

* cleanup redundant alert check

* restore package

* refactor previews, remove link preview

* show link preview when becomes available

* comment

* dont fail on invalid image

* suspend

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: descriptive database errors (#4527)

* ios: set share extension as inactive when suspending chat

---------

Co-authored-by: Arturs Krumins <auth@levitatingpineapple.com>
2024-07-28 17:54:58 +01:00

160 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(LocalizedStringKey(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 {
if let alert = errorAlert.wrappedValue {
self.alert(
alert.title,
isPresented: Binding<Bool>(
get: { errorAlert.wrappedValue != nil },
set: { if !$0 { errorAlert.wrappedValue = nil } }
),
actions: {
if let actions_ = alert.actions {
actions_()
} else {
actions(alert)
}
},
message: {
if let message = alert.message { Text(message) }
}
)
} else { self }
}
}
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)"
}
}