mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1af4cb0dcf | |||
| 1d83140c1d | |||
| 14f79df72f | |||
| 55a05ebe5c | |||
| ada8dc659a | |||
| 2457fa174e | |||
| 46bc947128 | |||
| 09b2819fe6 | |||
| 792ee522b5 | |||
| b542c5d0b2 | |||
| fd11c9d0b3 | |||
| 4f2838d329 | |||
| db89757e11 | |||
| db331a7ff1 | |||
| c9c47bd1a6 | |||
| c2eb510b4a | |||
| 9d4e37a27c | |||
| c71e40cc9d |
@@ -487,6 +487,11 @@ func setUserProtoServers(_ serverProtocol: ServerProtocol, servers: [ServerCfg])
|
||||
try await sendCommandOkResp(.apiSetUserProtoServers(userId: userId, serverProtocol: serverProtocol, servers: servers))
|
||||
}
|
||||
|
||||
func addKnownProtoServer(server: String) async throws {
|
||||
let userId = try currentUserId("addKnownProtoServer")
|
||||
try await sendCommandOkResp(.apiAddKnownProtoServer(userId: userId, server: server))
|
||||
}
|
||||
|
||||
func testProtoServer(server: String) async throws -> Result<(), ProtocolTestFailure> {
|
||||
let userId = try currentUserId("testProtoServer")
|
||||
let r = await chatSendCmd(.apiTestProtoServer(userId: userId, server: server))
|
||||
@@ -541,6 +546,11 @@ func reconnectAllServers() async throws {
|
||||
try await sendCommandOkResp(.reconnectAllServers)
|
||||
}
|
||||
|
||||
func reconnectServer(smpServer: String) async throws {
|
||||
let userId = try currentUserId("reconnectServer")
|
||||
try await sendCommandOkResp(.reconnectServer(userId: userId, smpServer: smpServer))
|
||||
}
|
||||
|
||||
func apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings) async throws {
|
||||
try await sendCommandOkResp(.apiSetChatSettings(type: type, id: id, chatSettings: chatSettings))
|
||||
}
|
||||
@@ -1334,6 +1344,24 @@ func apiGetVersion() throws -> CoreVersionInfo {
|
||||
throw r
|
||||
}
|
||||
|
||||
func getAgentServersSummary() throws -> PresentedServersSummary {
|
||||
let userId = try currentUserId("getAgentServersSummary")
|
||||
let r = chatSendCmdSync(.getAgentServersSummary(userId: userId))
|
||||
if case let .agentServersSummary(_, serversSummary) = r { return serversSummary }
|
||||
throw r
|
||||
}
|
||||
|
||||
func resetAgentServersStats() async throws {
|
||||
try await sendCommandOkResp(.resetAgentServersStats)
|
||||
}
|
||||
|
||||
func getAgentSubsSummary() throws -> SMPServerSubs {
|
||||
let userId = try currentUserId("getAgentSubsSummary")
|
||||
let r = chatSendCmdSync(.getAgentSubsSummary(userId: userId))
|
||||
if case let .agentSubsSummary(_, subsSummary) = r { return subsSummary }
|
||||
throw r
|
||||
}
|
||||
|
||||
private func currentUserId(_ funcName: String) throws -> Int64 {
|
||||
if let userId = ChatModel.shared.currentUser?.userId {
|
||||
return userId
|
||||
|
||||
@@ -115,9 +115,7 @@ struct ChatListView: View {
|
||||
HStack(spacing: 4) {
|
||||
Text("Chats")
|
||||
.font(.headline)
|
||||
if chatModel.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
SubsStatusIndicator()
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .center)
|
||||
}
|
||||
@@ -131,15 +129,6 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleFilterButton() -> some View {
|
||||
Button {
|
||||
showUnreadAndFavorites = !showUnreadAndFavorites
|
||||
} label: {
|
||||
Image(systemName: "line.3.horizontal.decrease.circle" + (showUnreadAndFavorites ? ".fill" : ""))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private var chatList: some View {
|
||||
let cs = filteredChats()
|
||||
ZStack {
|
||||
@@ -274,6 +263,66 @@ struct ChatListView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct SubsStatusIndicator: View {
|
||||
@State private var subs: SMPServerSubs = SMPServerSubs(ssActive: 0, ssPending: 0)
|
||||
@State private var timer: Timer? = nil
|
||||
@State private var timerCounter = 0
|
||||
@State private var showServersSummary = false
|
||||
|
||||
// Constants for the intervals
|
||||
let initialInterval: TimeInterval = 1.0
|
||||
let regularInterval: TimeInterval = 5.0
|
||||
let initialPhaseDuration: TimeInterval = 10.0 // Duration for initial phase in seconds
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
showServersSummary = true
|
||||
} label: {
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending, variableValueAsPercentage: true)
|
||||
}
|
||||
.onAppear {
|
||||
startInitialTimer()
|
||||
}
|
||||
.onDisappear {
|
||||
stopTimer()
|
||||
}
|
||||
.sheet(isPresented: $showServersSummary) {
|
||||
ServersSummaryView()
|
||||
}
|
||||
}
|
||||
|
||||
private func startInitialTimer() {
|
||||
timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in
|
||||
getSubsSummary()
|
||||
timerCounter += 1
|
||||
// Switch to the regular timer after the initial phase
|
||||
if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) {
|
||||
switchToRegularTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func switchToRegularTimer() {
|
||||
timer?.invalidate()
|
||||
timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in
|
||||
getSubsSummary()
|
||||
}
|
||||
}
|
||||
|
||||
func stopTimer() {
|
||||
timer?.invalidate()
|
||||
timer = nil
|
||||
}
|
||||
|
||||
private func getSubsSummary() {
|
||||
do {
|
||||
subs = try getAgentSubsSummary()
|
||||
} catch let error {
|
||||
logger.error("getAgentSubsSummary error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChatListSearchBar: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@Binding var searchMode: Bool
|
||||
@@ -282,9 +331,9 @@ struct ChatListSearchBar: View {
|
||||
@Binding var searchShowingSimplexLink: Bool
|
||||
@Binding var searchChatFilteredBySimplexLink: String?
|
||||
@State private var ignoreSearchTextChange = false
|
||||
@State private var showScanCodeSheet = false
|
||||
@State private var alert: PlanAndConnectAlert?
|
||||
@State private var sheet: PlanAndConnectActionSheet?
|
||||
@AppStorage(DEFAULT_SHOW_UNREAD_AND_FAVORITES) private var showUnreadAndFavorites = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 12) {
|
||||
@@ -301,26 +350,6 @@ struct ChatListSearchBar: View {
|
||||
.onTapGesture {
|
||||
searchText = ""
|
||||
}
|
||||
} else if !searchFocussed {
|
||||
HStack(spacing: 24) {
|
||||
if m.pasteboardHasStrings {
|
||||
Image(systemName: "doc")
|
||||
.onTapGesture {
|
||||
if let str = UIPasteboard.general.string {
|
||||
searchText = str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image(systemName: "qrcode")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 20, height: 20)
|
||||
.onTapGesture {
|
||||
showScanCodeSheet = true
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 2)
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 7, leading: 7, bottom: 7, trailing: 7))
|
||||
@@ -335,14 +364,12 @@ struct ChatListSearchBar: View {
|
||||
searchText = ""
|
||||
searchFocussed = false
|
||||
}
|
||||
} else if m.chats.count > 0 {
|
||||
toggleFilterButton()
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
.sheet(isPresented: $showScanCodeSheet) {
|
||||
NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
.environment(\EnvironmentValues.refresh as! WritableKeyPath<EnvironmentValues, RefreshAction?>, nil) // fixes .refreshable in ChatListView affecting nested view
|
||||
}
|
||||
.onChange(of: searchFocussed) { sf in
|
||||
withAnimation { searchMode = sf }
|
||||
}
|
||||
@@ -376,6 +403,21 @@ struct ChatListSearchBar: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func toggleFilterButton() -> some View {
|
||||
ZStack {
|
||||
Color.clear
|
||||
.frame(width: 22, height: 22)
|
||||
Image(systemName: showUnreadAndFavorites ? "line.3.horizontal.decrease.circle.fill" : "line.3.horizontal.decrease")
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.foregroundColor(showUnreadAndFavorites ? .accentColor : .secondary)
|
||||
.frame(width: showUnreadAndFavorites ? 22 : 16, height: showUnreadAndFavorites ? 22 : 16)
|
||||
.onTapGesture {
|
||||
showUnreadAndFavorites = !showUnreadAndFavorites
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func connect(_ link: String) {
|
||||
planAndConnect(
|
||||
link,
|
||||
|
||||
@@ -0,0 +1,629 @@
|
||||
//
|
||||
// ServersSummaryView.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by spaced4ndy on 25.06.2024.
|
||||
// Copyright © 2024 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleXChat
|
||||
|
||||
struct ServersSummaryView: View {
|
||||
@State private var serversSummary: PresentedServersSummary? = nil
|
||||
@State private var selectedUserCategory: PresentedUserCategory = .allUsers
|
||||
@State private var selectedServerType: PresentedServerType = .smp
|
||||
@State private var selectedSMPServer: String? = nil
|
||||
@State private var selectedXFTPServer: String? = nil
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
enum PresentedUserCategory {
|
||||
case currentUser
|
||||
case allUsers
|
||||
}
|
||||
|
||||
enum PresentedServerType {
|
||||
case smp
|
||||
case xftp
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
viewBody()
|
||||
.navigationTitle("Servers info")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
reloadButton()
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
shareButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
getServersSummary()
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func shareButton() -> some View {
|
||||
Button {
|
||||
if let serversSummary = serversSummary {
|
||||
showShareSheet(items: [encodePrettyPrinted(serversSummary)])
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.up")
|
||||
}
|
||||
.disabled(serversSummary == nil)
|
||||
}
|
||||
|
||||
public func encodePrettyPrinted<T: Encodable>(_ value: T) -> String {
|
||||
let encoder = jsonEncoder
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
let data = try! encoder.encode(value)
|
||||
return String(decoding: data, as: UTF8.self)
|
||||
}
|
||||
|
||||
private func reloadButton() -> some View {
|
||||
Button {
|
||||
getServersSummary()
|
||||
} label: {
|
||||
Image(systemName: "arrow.counterclockwise")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func viewBody() -> some View {
|
||||
if let summ = serversSummary {
|
||||
List {
|
||||
Group {
|
||||
Picker("User selection", selection: $selectedUserCategory) {
|
||||
Text("All users").tag(PresentedUserCategory.allUsers)
|
||||
Text("Current user").tag(PresentedUserCategory.currentUser)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
Picker("Server type", selection: $selectedServerType) {
|
||||
Text("SMP").tag(PresentedServerType.smp)
|
||||
Text("XFTP").tag(PresentedServerType.xftp)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
.listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
|
||||
switch (selectedUserCategory, selectedServerType) {
|
||||
case (.allUsers, .smp):
|
||||
if summ.allUsedSMP.count > 0 || summ.allPrevSMP.count > 0 || summ.allProxSMP.count > 0 {
|
||||
if summ.allUsedSMP.count > 0 {
|
||||
smpServersListView(summ.allUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevSMP.count > 0 {
|
||||
smpServersListView(summ.allPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.allProxSMP.count > 0 {
|
||||
smpServersListView(summ.allProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.currentUser, .smp):
|
||||
if summ.userUsedSMP.count > 0 || summ.userPrevSMP.count > 0 || summ.userProxSMP.count > 0 {
|
||||
if summ.userUsedSMP.count > 0 {
|
||||
smpServersListView(summ.userUsedSMP, showReconnectButton: true, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevSMP.count > 0 {
|
||||
smpServersListView(summ.userPrevSMP, showReconnectButton: false, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
if summ.userProxSMP.count > 0 {
|
||||
smpServersListView(summ.userProxSMP, showReconnectButton: false, summ.statsStartedAt, "Proxied", "You are not connected to these servers directly.")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.allUsers, .xftp):
|
||||
if summ.allUsedXFTP.count > 0 || summ.allPrevXFTP.count > 0 {
|
||||
if summ.allUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.allUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.allPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.allPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
case (.currentUser, .xftp):
|
||||
if summ.userUsedXFTP.count > 0 || summ.userPrevXFTP.count > 0 {
|
||||
if summ.userUsedXFTP.count > 0 {
|
||||
xftpServersListView(summ.userUsedXFTP, summ.statsStartedAt, "Current session")
|
||||
}
|
||||
if summ.userPrevXFTP.count > 0 {
|
||||
xftpServersListView(summ.userPrevXFTP, summ.statsStartedAt, "Previously used")
|
||||
}
|
||||
resetStatsButtonSection()
|
||||
} else {
|
||||
noCategoryInfoText()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Text("No info, try to reload")
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder private func smpServersListView(
|
||||
_ servers: [SMPServerSummary],
|
||||
showReconnectButton: Bool,
|
||||
_ statsStartedAt: Date,
|
||||
_ header: LocalizedStringKey? = nil,
|
||||
_ footer: LocalizedStringKey? = nil
|
||||
) -> some View {
|
||||
let sortedServers = servers.sorted { serverAddress($0.smpServer).compare(serverAddress($1.smpServer)) == .orderedAscending }
|
||||
Section {
|
||||
ForEach(sortedServers) { server in
|
||||
smpServerView(server, showReconnectButton, statsStartedAt)
|
||||
}
|
||||
} header: {
|
||||
if let header = header {
|
||||
Text(header)
|
||||
}
|
||||
} footer: {
|
||||
if let footer = footer {
|
||||
Text(footer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func smpServerView(_ server: SMPServerSummary, _ showReconnectButton: Bool, _ statsStartedAt: Date) -> some View {
|
||||
NavigationLink(tag: server.id, selection: $selectedSMPServer) {
|
||||
SMPServerSummaryView(
|
||||
summary: server,
|
||||
showReconnectButton: showReconnectButton,
|
||||
statsStartedAt: statsStartedAt
|
||||
)
|
||||
.navigationBarTitle("SMP server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
HStack {
|
||||
if let subs = server.subs {
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending)
|
||||
.frame(width: 16, alignment: .center)
|
||||
.padding(.trailing, 4)
|
||||
}
|
||||
Text(serverAddress(server.smpServer))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func serverAddress(_ server: String) -> String {
|
||||
parseServerAddress(server)?.hostnames.first ?? server
|
||||
}
|
||||
|
||||
@ViewBuilder private func xftpServersListView(
|
||||
_ servers: [XFTPServerSummary],
|
||||
_ statsStartedAt: Date,
|
||||
_ header: LocalizedStringKey? = nil,
|
||||
_ footer: LocalizedStringKey? = nil
|
||||
) -> some View {
|
||||
let sortedServers = servers.sorted { serverAddress($0.xftpServer).compare(serverAddress($1.xftpServer)) == .orderedAscending }
|
||||
Section {
|
||||
ForEach(sortedServers) { server in
|
||||
xftpServerView(server, statsStartedAt)
|
||||
}
|
||||
} header: {
|
||||
if let header = header {
|
||||
Text(header)
|
||||
}
|
||||
} footer: {
|
||||
if let footer = footer {
|
||||
Text(footer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func xftpServerView(_ server: XFTPServerSummary, _ statsStartedAt: Date) -> some View {
|
||||
NavigationLink(tag: server.id, selection: $selectedXFTPServer) {
|
||||
XFTPServerSummaryView(
|
||||
summary: server,
|
||||
statsStartedAt: statsStartedAt
|
||||
)
|
||||
.navigationBarTitle("XFTP server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
Text(serverAddress(server.xftpServer))
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
|
||||
private func noCategoryInfoText() -> some View {
|
||||
ZStack {
|
||||
Rectangle()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.foregroundColor(Color.clear)
|
||||
Text("No info")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.listRowBackground(Color.clear)
|
||||
.listRowSeparator(.hidden)
|
||||
}
|
||||
|
||||
private func resetStatsButtonSection() -> some View {
|
||||
Section {
|
||||
Button {
|
||||
alert = SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Reset servers statistics?"),
|
||||
message: Text("Servers statistics will be reset - this cannot be undone!"),
|
||||
primaryButton: .destructive(Text("Reset")) {
|
||||
Task {
|
||||
do {
|
||||
try await resetAgentServersStats()
|
||||
getServersSummary()
|
||||
} catch let error {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error resetting statistics",
|
||||
message: "\(responseError(error))"
|
||||
),
|
||||
id: "error resetting statistics"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
),
|
||||
id: "reset statistics question"
|
||||
)
|
||||
} label: {
|
||||
Text("Reset statistics")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func getServersSummary() {
|
||||
do {
|
||||
serversSummary = try getAgentServersSummary()
|
||||
} catch let error {
|
||||
logger.error("getAgentServersSummary error: \(responseError(error))")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SubscriptionStatusView: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
var activeSubs: Int
|
||||
var pendingSubs: Int
|
||||
var variableValueAsPercentage: Bool = false
|
||||
|
||||
var body: some View {
|
||||
let netInfo = m.networkInfo
|
||||
if netInfo.online {
|
||||
let (image, color, variableValue, opacity) = networkOnlineImage(netInfo.networkType)
|
||||
if #available(iOS 16.0, *) {
|
||||
Image(systemName: image, variableValue: variableValue)
|
||||
.foregroundColor(color)
|
||||
} else {
|
||||
Image(systemName: image)
|
||||
.foregroundColor(color.opacity(opacity))
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "wifi.slash")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
func networkOnlineImage(_ networkType: UserNetworkType) -> (String, Color, Double, Double) {
|
||||
switch networkType {
|
||||
case .cellular:
|
||||
let (color, variableValue, opacity) = cellularbarsColor
|
||||
return ("cellularbars", color, variableValue, opacity)
|
||||
default:
|
||||
let (color, variableValue, opacity) = wifiColor
|
||||
return ("wifi", color, variableValue, opacity)
|
||||
}
|
||||
}
|
||||
|
||||
// We manipulate variableValue so all "wifi" sections are filled only with 100% active subs,
|
||||
// unless variableValueAsPercentage is true; same for cellularbarsColor
|
||||
var wifiColor: (Color, Double, Double) {
|
||||
if activeSubs > 0 {
|
||||
let wifiVariableValue = (
|
||||
variableValueAsPercentage ? activeSubsPercentage
|
||||
: ( // "wifi" has 3 sections
|
||||
activeSubsPercentage >= 1 ? 1
|
||||
: (activeSubsPercentage >= 0.5 && activeSubsPercentage < 1) ? 0.6
|
||||
: (activeSubsPercentage > 0 && activeSubsPercentage < 0.5) ? 0.3
|
||||
: 0
|
||||
)
|
||||
)
|
||||
return (.accentColor, wifiVariableValue, activeSubsPercentage)
|
||||
} else {
|
||||
return (.secondary, 1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
var cellularbarsColor: (Color, Double, Double) {
|
||||
if activeSubs > 0 {
|
||||
let wifiVariableValue = (
|
||||
variableValueAsPercentage ? activeSubsPercentage
|
||||
: ( // "cellularbars" has 4 sections
|
||||
activeSubsPercentage >= 1 ? 1
|
||||
: (activeSubsPercentage >= 0.67 && activeSubsPercentage < 1) ? 0.7
|
||||
: (activeSubsPercentage >= 0.33 && activeSubsPercentage < 0.67) ? 0.45
|
||||
: (activeSubsPercentage > 0 && activeSubsPercentage < 0.33) ? 0.2
|
||||
: 0
|
||||
)
|
||||
)
|
||||
return (.accentColor, wifiVariableValue, activeSubsPercentage)
|
||||
} else {
|
||||
return (.secondary, 1, 1)
|
||||
}
|
||||
}
|
||||
|
||||
var activeSubsPercentage: Double {
|
||||
let total = activeSubs + pendingSubs
|
||||
guard total != 0 else { return 0.0 }
|
||||
return Double(activeSubs) / Double(total)
|
||||
}
|
||||
}
|
||||
|
||||
struct SMPServerSummaryView: View {
|
||||
var summary: SMPServerSummary
|
||||
var showReconnectButton: Bool
|
||||
var statsStartedAt: Date
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Text(summary.smpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
addKnownServer()
|
||||
} label: {
|
||||
Text("Add as known")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
if let known = summary.known, known {
|
||||
Text("Server is configured in **Settings** → **Network & servers**.")
|
||||
}
|
||||
}
|
||||
|
||||
if showReconnectButton {
|
||||
reconnectButtonSection()
|
||||
}
|
||||
|
||||
if let subs = summary.subs {
|
||||
subsSection(subs)
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
}
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func reconnectButtonSection() -> some View {
|
||||
Section {
|
||||
Button {
|
||||
alert = SomeAlert(
|
||||
alert: Alert(
|
||||
title: Text("Reconnect server?"),
|
||||
message: Text("Reconnect server to force message delivery. It uses additional traffic."),
|
||||
primaryButton: .default(Text("Ok")) {
|
||||
Task {
|
||||
do {
|
||||
try await reconnectServer(smpServer: summary.smpServer)
|
||||
} catch let error {
|
||||
alert = SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error reconnecting server",
|
||||
message: "\(responseError(error))"
|
||||
),
|
||||
id: "error reconnecting server"
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
),
|
||||
id: "reconnect server question"
|
||||
)
|
||||
} label: {
|
||||
Text("Reconnect")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func subsSection(_ subs: SMPServerSubs) -> some View {
|
||||
Section {
|
||||
infoRow("Active", "\(subs.ssActive)")
|
||||
infoRow("Pending", "\(subs.ssPending)")
|
||||
} header: {
|
||||
HStack {
|
||||
Text("Subscriptions")
|
||||
SubscriptionStatusView(activeSubs: subs.ssActive, pendingSubs: subs.ssPending)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentSMPServerStatsData) -> some View {
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Messages sent directly", "\(stats._sentDirect)")
|
||||
indentedInfoRow("attempts", "\(stats._sentDirectAttempts)")
|
||||
infoRow("Messages sent via proxy", "\(stats._sentViaProxy)")
|
||||
indentedInfoRow("attempts", "\(stats._sentViaProxyAttempts)")
|
||||
infoRow("Messages sent to proxy", "\(stats._sentProxied)")
|
||||
indentedInfoRow("attempts", "\(stats._sentProxiedAttempts)")
|
||||
infoRow("Sending AUTH errors", "\(stats._sentAuthErrs)")
|
||||
indentedInfoRow("QUOTA errors", "\(stats._sentQuotaErrs)")
|
||||
indentedInfoRow("expired", "\(stats._sentExpiredErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._sentOtherErrs)")
|
||||
infoRow("Messages received", "\(stats._recvMsgs)")
|
||||
indentedInfoRow("duplicates", "\(stats._recvDuplicates)")
|
||||
indentedInfoRow("decryption errors", "\(stats._recvCryptoErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._recvErrs)")
|
||||
infoRow("Messages acknowledged", "\(stats._ackMsgs)")
|
||||
indentedInfoRow("attempts", "\(stats._ackAttempts)")
|
||||
infoRow("Connections created", "\(stats._connCreated)")
|
||||
indentedInfoRow("secured", "\(stats._connSecured)")
|
||||
indentedInfoRow("completed", "\(stats._connCompleted)")
|
||||
infoRow("Connections deleted", "\(stats._connDeleted)")
|
||||
infoRow("Connections subscribed", "\(stats._connSubscribed)")
|
||||
indentedInfoRow("attempts", "\(stats._connSubAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._connSubErrs)")
|
||||
}
|
||||
}
|
||||
|
||||
func addKnownServer() {
|
||||
Task {
|
||||
do {
|
||||
try await addKnownProtoServer(server: summary.smpServer)
|
||||
await MainActor.run {
|
||||
// TODO disable button, etc.
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
alert = errorAddingServerAlert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func indentedInfoRow(_ title: LocalizedStringKey, _ value: String) -> some View {
|
||||
HStack {
|
||||
Text(title)
|
||||
.padding(.leading, 24)
|
||||
Spacer()
|
||||
Text(value)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
private func errorAddingServerAlert(_ error: Error) -> SomeAlert {
|
||||
SomeAlert(
|
||||
alert: mkAlert(
|
||||
title: "Error adding server",
|
||||
message: "Make sure server address is in correct format (\(responseError(error)))."
|
||||
),
|
||||
id: "error saving server"
|
||||
)
|
||||
}
|
||||
|
||||
struct XFTPServerSummaryView: View {
|
||||
var summary: XFTPServerSummary
|
||||
var statsStartedAt: Date
|
||||
@State private var alert: SomeAlert?
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Section {
|
||||
Text(summary.xftpServer)
|
||||
.textSelection(.enabled)
|
||||
if let known = summary.known, !known {
|
||||
Button {
|
||||
addKnownServer()
|
||||
} label: {
|
||||
Text("Add as known")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Server address")
|
||||
} footer: {
|
||||
if let known = summary.known, known {
|
||||
Text("Server is configured in **Settings** → **Network & servers**.")
|
||||
}
|
||||
}
|
||||
|
||||
if let sess = summary.sessions {
|
||||
sessionsSection(sess)
|
||||
}
|
||||
|
||||
inProgressSection()
|
||||
|
||||
if let stats = summary.stats {
|
||||
statsSection(stats)
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { $0.alert }
|
||||
}
|
||||
|
||||
private func sessionsSection(_ sess: ServerSessions) -> some View {
|
||||
Section("Sessions") {
|
||||
infoRow("Connected", "\(sess.ssConnected)")
|
||||
infoRow("Errors", "\(sess.ssErrors)")
|
||||
infoRow("Connecting", "\(sess.ssConnecting)")
|
||||
}
|
||||
}
|
||||
|
||||
private func inProgressSection() -> some View {
|
||||
Section("In progress") {
|
||||
localizedInfoRow("Download", boolYesNo(summary.rcvInProgress))
|
||||
localizedInfoRow("Upload", boolYesNo(summary.sndInProgress))
|
||||
localizedInfoRow("Deletion", boolYesNo(summary.delInProgress))
|
||||
}
|
||||
}
|
||||
|
||||
private func boolYesNo(_ b: Bool) -> LocalizedStringKey {
|
||||
b ? "yes" : "no"
|
||||
}
|
||||
|
||||
private func statsSection(_ stats: AgentXFTPServerStatsData) -> some View {
|
||||
Section("Statistics") {
|
||||
infoRow("Starting from", localTimestamp(statsStartedAt))
|
||||
infoRow("Chunks uploaded", "\(stats._uploads)")
|
||||
indentedInfoRow("attempts", "\(stats._uploadAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._uploadErrs)")
|
||||
infoRow("Chunks downloaded", "\(stats._downloads)")
|
||||
indentedInfoRow("attempts", "\(stats._downloadAttempts)")
|
||||
indentedInfoRow("AUTH errors", "\(stats._downloadAuthErrs)")
|
||||
indentedInfoRow("other errors", "\(stats._downloadErrs)")
|
||||
infoRow("Chunks deleted", "\(stats._deletions)")
|
||||
indentedInfoRow("attempts", "\(stats._deleteAttempts)")
|
||||
indentedInfoRow("errors", "\(stats._deleteErrs)")
|
||||
}
|
||||
}
|
||||
|
||||
func addKnownServer() {
|
||||
Task {
|
||||
do {
|
||||
try await addKnownProtoServer(server: summary.xftpServer)
|
||||
await MainActor.run {
|
||||
// TODO disable button, etc.
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
alert = errorAddingServerAlert(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ServersSummaryView()
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import SwiftUI
|
||||
|
||||
enum NewChatMenuOption: Identifiable {
|
||||
case newContact
|
||||
case scanPaste
|
||||
case newGroup
|
||||
|
||||
var id: Self { self }
|
||||
@@ -25,6 +26,11 @@ struct NewChatMenuButton: View {
|
||||
} label: {
|
||||
Text("Add contact")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .scanPaste
|
||||
} label: {
|
||||
Text("Scan / Paste link")
|
||||
}
|
||||
Button {
|
||||
newChatMenuOption = .newGroup
|
||||
} label: {
|
||||
@@ -39,6 +45,7 @@ struct NewChatMenuButton: View {
|
||||
.sheet(item: $newChatMenuOption) { opt in
|
||||
switch opt {
|
||||
case .newContact: NewChatView(selection: .invite)
|
||||
case .scanPaste: NewChatView(selection: .connect, showQRCodeScanner: true)
|
||||
case .newGroup: AddGroupView()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ struct ProtocolServerView: View {
|
||||
let serverProtocol: ServerProtocol
|
||||
@Binding var server: ServerCfg
|
||||
@State var serverToEdit: ServerCfg
|
||||
@State var serverEnabled: Bool
|
||||
@State private var showTestFailure = false
|
||||
@State private var testing = false
|
||||
@State private var testFailure: ProtocolTestFailure?
|
||||
@@ -110,7 +111,10 @@ struct ProtocolServerView: View {
|
||||
Spacer()
|
||||
showTestStatus(server: serverToEdit)
|
||||
}
|
||||
Toggle("Use for new connections", isOn: $serverToEdit.enabled)
|
||||
Toggle("Use for new connections", isOn: $serverEnabled)
|
||||
.onChange(of: serverEnabled) { enabled in
|
||||
serverToEdit.enabled = enabled ? .enabled : .disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,7 +183,8 @@ struct ProtocolServerView_Previews: PreviewProvider {
|
||||
ProtocolServerView(
|
||||
serverProtocol: .smp,
|
||||
server: Binding.constant(ServerCfg.sampleData.custom),
|
||||
serverToEdit: ServerCfg.sampleData.custom
|
||||
serverToEdit: ServerCfg.sampleData.custom,
|
||||
serverEnabled: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ struct ProtocolServersView: View {
|
||||
}
|
||||
|
||||
private var allServersDisabled: Bool {
|
||||
servers.allSatisfy { !$0.enabled }
|
||||
servers.allSatisfy { $0.enabled != .enabled }
|
||||
}
|
||||
|
||||
private func protocolServerView(_ server: Binding<ServerCfg>) -> some View {
|
||||
@@ -168,7 +168,8 @@ struct ProtocolServersView: View {
|
||||
ProtocolServerView(
|
||||
serverProtocol: serverProtocol,
|
||||
server: server,
|
||||
serverToEdit: srv
|
||||
serverToEdit: srv,
|
||||
serverEnabled: srv.enabled == .enabled
|
||||
)
|
||||
.navigationBarTitle(srv.preset ? "Preset server" : "Your server")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
@@ -181,7 +182,7 @@ struct ProtocolServersView: View {
|
||||
invalidServer()
|
||||
} else if !uniqueAddress(srv, address) {
|
||||
Image(systemName: "exclamationmark.circle").foregroundColor(.red)
|
||||
} else if !srv.enabled {
|
||||
} else if srv.enabled != .enabled {
|
||||
Image(systemName: "slash.circle").foregroundColor(.secondary)
|
||||
} else {
|
||||
showTestStatus(server: srv)
|
||||
@@ -194,7 +195,7 @@ struct ProtocolServersView: View {
|
||||
.padding(.trailing, 4)
|
||||
|
||||
let v = Text(address?.hostnames.first ?? srv.server).lineLimit(1)
|
||||
if srv.enabled {
|
||||
if srv.enabled == .enabled {
|
||||
v
|
||||
} else {
|
||||
v.foregroundColor(.secondary)
|
||||
@@ -235,7 +236,7 @@ struct ProtocolServersView: View {
|
||||
private func addAllPresets() {
|
||||
for srv in presetServers {
|
||||
if !hasPreset(srv) {
|
||||
servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: true))
|
||||
servers.append(ServerCfg(server: srv, preset: true, tested: nil, enabled: .enabled))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -260,7 +261,7 @@ struct ProtocolServersView: View {
|
||||
|
||||
private func resetTestStatus() {
|
||||
for i in 0..<servers.count {
|
||||
if servers[i].enabled {
|
||||
if servers[i].enabled == .enabled {
|
||||
servers[i].tested = nil
|
||||
}
|
||||
}
|
||||
@@ -269,7 +270,7 @@ struct ProtocolServersView: View {
|
||||
private func runServersTest() async -> [String: ProtocolTestFailure] {
|
||||
var fs: [String: ProtocolTestFailure] = [:]
|
||||
for i in 0..<servers.count {
|
||||
if servers[i].enabled {
|
||||
if servers[i].enabled == .enabled {
|
||||
if let f = await testServerConnection(server: $servers[i]) {
|
||||
fs[serverHostname(servers[i].server)] = f
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ struct ScanProtocolServer: View {
|
||||
switch resp {
|
||||
case let .success(r):
|
||||
if parseServerAddress(r.string) != nil {
|
||||
servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: true))
|
||||
servers.append(ServerCfg(server: r.string, preset: false, tested: nil, enabled: .enabled))
|
||||
dismiss()
|
||||
} else {
|
||||
showAddressError = true
|
||||
|
||||
@@ -148,6 +148,12 @@
|
||||
640417CD2B29B8C200CCB412 /* NewChatMenuButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */; };
|
||||
640417CE2B29B8C200CCB412 /* NewChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 640417CC2B29B8C200CCB412 /* NewChatView.swift */; };
|
||||
6407BA83295DA85D0082BA18 /* CIInvalidJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */; };
|
||||
641753572C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
|
||||
641753582C2AC158005415B4 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753532C2AC158005415B4 /* libgmp.a */; };
|
||||
641753592C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
|
||||
6417535A2C2AC158005415B4 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753552C2AC158005415B4 /* libgmpxx.a */; };
|
||||
6417535B2C2AC158005415B4 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 641753562C2AC158005415B4 /* libffi.a */; };
|
||||
6417535D2C2ACD77005415B4 /* ServersSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */; };
|
||||
6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */; };
|
||||
6419EC582AB97507004A607A /* CIMemberCreatedContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */; };
|
||||
6432857C2925443C00FBE5C8 /* GroupPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */; };
|
||||
@@ -192,11 +198,6 @@
|
||||
D741547A29AF90B00022400A /* PushKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547929AF90B00022400A /* PushKit.framework */; };
|
||||
D77B92DC2952372200A5A1CC /* SwiftyGif in Frameworks */ = {isa = PBXBuildFile; productRef = D77B92DB2952372200A5A1CC /* SwiftyGif */; };
|
||||
D7F0E33929964E7E0068AF69 /* LZString in Frameworks */ = {isa = PBXBuildFile; productRef = D7F0E33829964E7E0068AF69 /* LZString */; };
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */; };
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3B2C22D78C00CBA347 /* libffi.a */; };
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */; };
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3D2C22D78C00CBA347 /* libgmp.a */; };
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -443,6 +444,12 @@
|
||||
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
|
||||
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
|
||||
641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; path = "Libraries/libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
641753532C2AC158005415B4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
|
||||
641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; path = "Libraries/libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
|
||||
641753552C2AC158005415B4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
|
||||
641753562C2AC158005415B4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
|
||||
6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServersSummaryView.swift; sourceTree = "<group>"; };
|
||||
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
|
||||
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
||||
@@ -487,11 +494,6 @@
|
||||
D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; };
|
||||
D7AA2C3429A936B400737B40 /* MediaEncryption.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; name = MediaEncryption.playground; path = Shared/MediaEncryption.playground; sourceTree = SOURCE_ROOT; xcLanguageSpecificationIdentifier = xcode.lang.swift; };
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a"; sourceTree = "<group>"; };
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -529,13 +531,13 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
E5D68D412C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
|
||||
6417535A2C2AC158005415B4 /* libgmpxx.a in Frameworks */,
|
||||
641753592C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
641753572C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a in Frameworks */,
|
||||
6417535B2C2AC158005415B4 /* libffi.a in Frameworks */,
|
||||
641753582C2AC158005415B4 /* libgmp.a in Frameworks */,
|
||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||
E5D68D3F2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a in Frameworks */,
|
||||
E5D68D422C22D78C00CBA347 /* libgmp.a in Frameworks */,
|
||||
E5D68D402C22D78C00CBA347 /* libffi.a in Frameworks */,
|
||||
E5D68D432C22D78C00CBA347 /* libgmpxx.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -601,11 +603,6 @@
|
||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E5D68D3B2C22D78C00CBA347 /* libffi.a */,
|
||||
E5D68D3D2C22D78C00CBA347 /* libgmp.a */,
|
||||
E5D68D3E2C22D78C00CBA347 /* libgmpxx.a */,
|
||||
E5D68D3C2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
|
||||
E5D68D3A2C22D78C00CBA347 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -672,6 +669,11 @@
|
||||
5CC2C0FA2809BF11000C35E3 /* Localizable.strings */,
|
||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */,
|
||||
5C764E5C279C70B7000C6508 /* Libraries */,
|
||||
641753562C2AC158005415B4 /* libffi.a */,
|
||||
641753532C2AC158005415B4 /* libgmp.a */,
|
||||
641753552C2AC158005415B4 /* libgmpxx.a */,
|
||||
641753522C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c-ghc9.6.3.a */,
|
||||
641753542C2AC158005415B4 /* libHSsimplex-chat-5.8.1.0-GEbUSGuGADZH0bnStuks0c.a */,
|
||||
5CA059C2279559F40002BEB4 /* Shared */,
|
||||
5CDCAD462818589900503DA2 /* SimpleX NSE */,
|
||||
5CA059DA279559F40002BEB4 /* Tests iOS */,
|
||||
@@ -802,6 +804,7 @@
|
||||
5C13730A28156D2700F43030 /* ContactConnectionView.swift */,
|
||||
5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */,
|
||||
18415835CBD939A9ABDC108A /* UserPicker.swift */,
|
||||
6417535C2C2ACD77005415B4 /* ServersSummaryView.swift */,
|
||||
);
|
||||
path = ChatList;
|
||||
sourceTree = "<group>";
|
||||
@@ -1256,6 +1259,7 @@
|
||||
5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */,
|
||||
5C9329412929248A0090FFF9 /* ScanProtocolServer.swift in Sources */,
|
||||
8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */,
|
||||
6417535D2C2ACD77005415B4 /* ServersSummaryView.swift in Sources */,
|
||||
64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */,
|
||||
5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */,
|
||||
5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */,
|
||||
@@ -1552,7 +1556,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1577,7 +1581,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES_THIN;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1601,7 +1605,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1626,7 +1630,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||
PRODUCT_NAME = SimpleX;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1687,7 +1691,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -1702,7 +1706,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1724,7 +1728,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -1739,7 +1743,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -1761,7 +1765,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1787,7 +1791,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
@@ -1812,7 +1816,7 @@
|
||||
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 225;
|
||||
CURRENT_PROJECT_VERSION = 224;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -1838,7 +1842,7 @@
|
||||
"$(PROJECT_DIR)/Libraries/sim",
|
||||
);
|
||||
LLVM_LTO = YES;
|
||||
MARKETING_VERSION = 5.8.1;
|
||||
MARKETING_VERSION = 5.8;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||
SDKROOT = iphoneos;
|
||||
|
||||
@@ -10,7 +10,7 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
public let jsonDecoder = getJSONDecoder()
|
||||
let jsonEncoder = getJSONEncoder()
|
||||
public let jsonEncoder = getJSONEncoder()
|
||||
|
||||
public enum ChatCommand {
|
||||
case showActiveUser
|
||||
@@ -71,6 +71,7 @@ public enum ChatCommand {
|
||||
case apiSendMemberContactInvitation(contactId: Int64, msg: MsgContent)
|
||||
case apiGetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol)
|
||||
case apiSetUserProtoServers(userId: Int64, serverProtocol: ServerProtocol, servers: [ServerCfg])
|
||||
case apiAddKnownProtoServer(userId: Int64, server: String)
|
||||
case apiTestProtoServer(userId: Int64, server: String)
|
||||
case apiSetChatItemTTL(userId: Int64, seconds: Int64?)
|
||||
case apiGetChatItemTTL(userId: Int64)
|
||||
@@ -78,6 +79,7 @@ public enum ChatCommand {
|
||||
case apiGetNetworkConfig
|
||||
case apiSetNetworkInfo(networkInfo: UserNetworkInfo)
|
||||
case reconnectAllServers
|
||||
case reconnectServer(userId: Int64, smpServer: String)
|
||||
case apiSetChatSettings(type: ChatType, id: Int64, chatSettings: ChatSettings)
|
||||
case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings)
|
||||
case apiContactInfo(contactId: Int64)
|
||||
@@ -122,6 +124,7 @@ public enum ChatCommand {
|
||||
case apiEndCall(contact: Contact)
|
||||
case apiGetCallInvitations
|
||||
case apiCallStatus(contact: Contact, callStatus: WebRTCCallStatus)
|
||||
// WebRTC calls /
|
||||
case apiGetNetworkStatuses
|
||||
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
||||
case apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool)
|
||||
@@ -142,6 +145,9 @@ public enum ChatCommand {
|
||||
case apiStandaloneFileInfo(url: String)
|
||||
// misc
|
||||
case showVersion
|
||||
case getAgentServersSummary(userId: Int64)
|
||||
case resetAgentServersStats
|
||||
case getAgentSubsSummary(userId: Int64)
|
||||
case string(String)
|
||||
|
||||
public var cmdString: String {
|
||||
@@ -219,6 +225,7 @@ public enum ChatCommand {
|
||||
case let .apiSendMemberContactInvitation(contactId, mc): return "/_invite member contact @\(contactId) \(mc.cmdString)"
|
||||
case let .apiGetUserProtoServers(userId, serverProtocol): return "/_servers \(userId) \(serverProtocol)"
|
||||
case let .apiSetUserProtoServers(userId, serverProtocol, servers): return "/_servers \(userId) \(serverProtocol) \(protoServersStr(servers))"
|
||||
case let .apiAddKnownProtoServer(userId, server): return "/_known server \(userId) \(server)"
|
||||
case let .apiTestProtoServer(userId, server): return "/_server test \(userId) \(server)"
|
||||
case let .apiSetChatItemTTL(userId, seconds): return "/_ttl \(userId) \(chatItemTTLStr(seconds: seconds))"
|
||||
case let .apiGetChatItemTTL(userId): return "/_ttl \(userId)"
|
||||
@@ -226,6 +233,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "/network"
|
||||
case let .apiSetNetworkInfo(networkInfo): return "/_network info \(encodeJSON(networkInfo))"
|
||||
case .reconnectAllServers: return "/reconnect"
|
||||
case let .reconnectServer(userId, smpServer): return "/reconnect \(userId) \(smpServer)"
|
||||
case let .apiSetChatSettings(type, id, chatSettings): return "/_settings \(ref(type, id)) \(encodeJSON(chatSettings))"
|
||||
case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))"
|
||||
case let .apiContactInfo(contactId): return "/_info @\(contactId)"
|
||||
@@ -301,6 +309,9 @@ public enum ChatCommand {
|
||||
case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)"
|
||||
case let .apiStandaloneFileInfo(link): return "/_download info \(link)"
|
||||
case .showVersion: return "/version"
|
||||
case let .getAgentServersSummary(userId): return "/get servers summary \(userId)"
|
||||
case .resetAgentServersStats: return "/reset servers stats"
|
||||
case let .getAgentSubsSummary(userId): return "/get subs summary \(userId)"
|
||||
case let .string(str): return str
|
||||
}
|
||||
}
|
||||
@@ -368,6 +379,7 @@ public enum ChatCommand {
|
||||
case .apiSendMemberContactInvitation: return "apiSendMemberContactInvitation"
|
||||
case .apiGetUserProtoServers: return "apiGetUserProtoServers"
|
||||
case .apiSetUserProtoServers: return "apiSetUserProtoServers"
|
||||
case .apiAddKnownProtoServer: return "apiAddKnownProtoServer"
|
||||
case .apiTestProtoServer: return "apiTestProtoServer"
|
||||
case .apiSetChatItemTTL: return "apiSetChatItemTTL"
|
||||
case .apiGetChatItemTTL: return "apiGetChatItemTTL"
|
||||
@@ -375,6 +387,7 @@ public enum ChatCommand {
|
||||
case .apiGetNetworkConfig: return "apiGetNetworkConfig"
|
||||
case .apiSetNetworkInfo: return "apiSetNetworkInfo"
|
||||
case .reconnectAllServers: return "reconnectAllServers"
|
||||
case .reconnectServer: return "reconnectServer"
|
||||
case .apiSetChatSettings: return "apiSetChatSettings"
|
||||
case .apiSetMemberSettings: return "apiSetMemberSettings"
|
||||
case .apiContactInfo: return "apiContactInfo"
|
||||
@@ -435,6 +448,9 @@ public enum ChatCommand {
|
||||
case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile"
|
||||
case .apiStandaloneFileInfo: return "apiStandaloneFileInfo"
|
||||
case .showVersion: return "showVersion"
|
||||
case .getAgentServersSummary: return "getAgentServersSummary"
|
||||
case .resetAgentServersStats: return "resetAgentServersStats"
|
||||
case .getAgentSubsSummary: return "getAgentSubsSummary"
|
||||
case .string: return "console command"
|
||||
}
|
||||
}
|
||||
@@ -663,6 +679,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
// misc
|
||||
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
|
||||
case cmdOk(user: UserRef?)
|
||||
case agentServersSummary(user: UserRef, serversSummary: PresentedServersSummary)
|
||||
case agentSubsSummary(user: UserRef, subsSummary: SMPServerSubs)
|
||||
case chatCmdError(user_: UserRef?, chatError: ChatError)
|
||||
case chatError(user_: UserRef?, chatError: ChatError)
|
||||
case archiveImported(archiveErrors: [ArchiveError])
|
||||
@@ -821,6 +839,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case .contactPQEnabled: return "contactPQEnabled"
|
||||
case .versionInfo: return "versionInfo"
|
||||
case .cmdOk: return "cmdOk"
|
||||
case .agentServersSummary: return "agentServersSummary"
|
||||
case .agentSubsSummary: return "agentSubsSummary"
|
||||
case .chatCmdError: return "chatCmdError"
|
||||
case .chatError: return "chatError"
|
||||
case .archiveImported: return "archiveImported"
|
||||
@@ -984,6 +1004,8 @@ public enum ChatResponse: Decodable, Error {
|
||||
case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)")
|
||||
case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))"
|
||||
case .cmdOk: return noDetails
|
||||
case let .agentServersSummary(u, serversSummary): return withUser(u, String(describing: serversSummary))
|
||||
case let .agentSubsSummary(u, subsSummary): return withUser(u, String(describing: subsSummary))
|
||||
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
|
||||
case let .archiveImported(archiveErrors): return String(describing: archiveErrors)
|
||||
@@ -1109,13 +1131,13 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
public var server: String
|
||||
public var preset: Bool
|
||||
public var tested: Bool?
|
||||
public var enabled: Bool
|
||||
public var enabled: ServerEnabled
|
||||
var createdAt = Date()
|
||||
// public var sendEnabled: Bool // can we potentially want to prevent sending on the servers we use to receive?
|
||||
// Even if we don't see the use case, it's probably better to allow it in the model
|
||||
// In any case, "trusted/known" servers are out of scope of this change
|
||||
|
||||
public init(server: String, preset: Bool, tested: Bool?, enabled: Bool) {
|
||||
public init(server: String, preset: Bool, tested: Bool?, enabled: ServerEnabled) {
|
||||
self.server = server
|
||||
self.preset = preset
|
||||
self.tested = tested
|
||||
@@ -1128,7 +1150,7 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
|
||||
public var id: String { "\(server) \(createdAt)" }
|
||||
|
||||
public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: true)
|
||||
public static var empty = ServerCfg(server: "", preset: false, tested: nil, enabled: .enabled)
|
||||
|
||||
public var isEmpty: Bool {
|
||||
server.trimmingCharacters(in: .whitespaces) == ""
|
||||
@@ -1145,19 +1167,19 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
server: "smp://abcd@smp8.simplex.im",
|
||||
preset: true,
|
||||
tested: true,
|
||||
enabled: true
|
||||
enabled: .enabled
|
||||
),
|
||||
custom: ServerCfg(
|
||||
server: "smp://abcd@smp9.simplex.im",
|
||||
preset: false,
|
||||
tested: false,
|
||||
enabled: false
|
||||
enabled: .enabled
|
||||
),
|
||||
untested: ServerCfg(
|
||||
server: "smp://abcd@smp10.simplex.im",
|
||||
preset: false,
|
||||
tested: nil,
|
||||
enabled: true
|
||||
enabled: .enabled
|
||||
)
|
||||
)
|
||||
|
||||
@@ -1169,6 +1191,12 @@ public struct ServerCfg: Identifiable, Equatable, Codable {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ServerEnabled: String, Codable {
|
||||
case disabled
|
||||
case enabled
|
||||
case known
|
||||
}
|
||||
|
||||
public enum ProtocolTestStep: String, Decodable, Equatable {
|
||||
case connect
|
||||
case disconnect
|
||||
@@ -2224,3 +2252,108 @@ public enum MsgType: String, Codable {
|
||||
case message
|
||||
case quota
|
||||
}
|
||||
|
||||
public struct PresentedServersSummary: Codable {
|
||||
public var statsStartedAt: Date
|
||||
public var currentUserServers: ServersSummary
|
||||
public var allUsersServers: ServersSummary
|
||||
|
||||
public var allUsedSMP: [SMPServerSummary] { self.allUsersServers.currentlyUsedSMPServers }
|
||||
public var allPrevSMP: [SMPServerSummary] { self.allUsersServers.previouslyUsedSMPServers }
|
||||
public var allProxSMP: [SMPServerSummary] { self.allUsersServers.onlyProxiedSMPServers }
|
||||
|
||||
public var userUsedSMP: [SMPServerSummary] { self.currentUserServers.currentlyUsedSMPServers }
|
||||
public var userPrevSMP: [SMPServerSummary] { self.currentUserServers.previouslyUsedSMPServers }
|
||||
public var userProxSMP: [SMPServerSummary] { self.currentUserServers.onlyProxiedSMPServers }
|
||||
|
||||
public var allUsedXFTP: [XFTPServerSummary] { self.allUsersServers.currentlyUsedXFTPServers }
|
||||
public var allPrevXFTP: [XFTPServerSummary] { self.allUsersServers.previouslyUsedXFTPServers }
|
||||
|
||||
public var userUsedXFTP: [XFTPServerSummary] { self.currentUserServers.currentlyUsedXFTPServers }
|
||||
public var userPrevXFTP: [XFTPServerSummary] { self.currentUserServers.previouslyUsedXFTPServers }
|
||||
}
|
||||
|
||||
public struct ServersSummary: Codable {
|
||||
public var currentlyUsedSMPServers: [SMPServerSummary]
|
||||
public var previouslyUsedSMPServers: [SMPServerSummary]
|
||||
public var onlyProxiedSMPServers: [SMPServerSummary]
|
||||
public var currentlyUsedXFTPServers: [XFTPServerSummary]
|
||||
public var previouslyUsedXFTPServers: [XFTPServerSummary]
|
||||
}
|
||||
|
||||
public struct SMPServerSummary: Codable, Identifiable {
|
||||
public var smpServer: String
|
||||
public var known: Bool?
|
||||
public var sessions: ServerSessions?
|
||||
public var subs: SMPServerSubs?
|
||||
public var stats: AgentSMPServerStatsData?
|
||||
|
||||
public var id: String { smpServer }
|
||||
}
|
||||
|
||||
public struct ServerSessions: Codable {
|
||||
public var ssConnected: Int
|
||||
public var ssErrors: Int
|
||||
public var ssConnecting: Int
|
||||
}
|
||||
|
||||
public struct SMPServerSubs: Codable {
|
||||
public var ssActive: Int
|
||||
public var ssPending: Int
|
||||
|
||||
public init(ssActive: Int, ssPending: Int) {
|
||||
self.ssActive = ssActive
|
||||
self.ssPending = ssPending
|
||||
}
|
||||
}
|
||||
|
||||
public struct AgentSMPServerStatsData: Codable {
|
||||
public var _sentDirect: Int
|
||||
public var _sentViaProxy: Int
|
||||
public var _sentProxied: Int
|
||||
public var _sentDirectAttempts: Int
|
||||
public var _sentViaProxyAttempts: Int
|
||||
public var _sentProxiedAttempts: Int
|
||||
public var _sentAuthErrs: Int
|
||||
public var _sentQuotaErrs: Int
|
||||
public var _sentExpiredErrs: Int
|
||||
public var _sentOtherErrs: Int
|
||||
public var _recvMsgs: Int
|
||||
public var _recvDuplicates: Int
|
||||
public var _recvCryptoErrs: Int
|
||||
public var _recvErrs: Int
|
||||
public var _ackMsgs: Int
|
||||
public var _ackAttempts: Int
|
||||
public var _connCreated: Int
|
||||
public var _connSecured: Int
|
||||
public var _connCompleted: Int
|
||||
public var _connDeleted: Int
|
||||
public var _connSubscribed: Int
|
||||
public var _connSubAttempts: Int
|
||||
public var _connSubErrs: Int
|
||||
}
|
||||
|
||||
public struct XFTPServerSummary: Codable, Identifiable {
|
||||
public var xftpServer: String
|
||||
public var known: Bool?
|
||||
public var sessions: ServerSessions?
|
||||
public var stats: AgentXFTPServerStatsData?
|
||||
public var rcvInProgress: Bool
|
||||
public var sndInProgress: Bool
|
||||
public var delInProgress: Bool
|
||||
|
||||
public var id: String { xftpServer }
|
||||
}
|
||||
|
||||
public struct AgentXFTPServerStatsData: Codable {
|
||||
public var _uploads: Int
|
||||
public var _uploadAttempts: Int
|
||||
public var _uploadErrs: Int
|
||||
public var _downloads: Int
|
||||
public var _downloadAttempts: Int
|
||||
public var _downloadAuthErrs: Int
|
||||
public var _downloadErrs: Int
|
||||
public var _deletions: Int
|
||||
public var _deleteAttempts: Int
|
||||
public var _deleteErrs: Int
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import SwiftUI
|
||||
|
||||
public struct User: Identifiable, Decodable, UserLike, NamedChat {
|
||||
public var userId: Int64
|
||||
public var agentUserId: String
|
||||
var userContactId: Int64
|
||||
var localDisplayName: ContactName
|
||||
public var profile: LocalProfile
|
||||
@@ -41,6 +42,7 @@ public struct User: Identifiable, Decodable, UserLike, NamedChat {
|
||||
|
||||
public static let sampleData = User(
|
||||
userId: 1,
|
||||
agentUserId: "abc",
|
||||
userContactId: 1,
|
||||
localDisplayName: "alice",
|
||||
profile: LocalProfile.sampleData,
|
||||
|
||||
Reference in New Issue
Block a user