diff options
| author | Dustin Mierau <dustin@mierau.me> | 2023-12-12 15:27:00 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2023-12-12 15:27:00 -0800 |
| commit | 248fcbbd5dca84747e6f3fd9d1d52c41ab8640c3 (patch) | |
| tree | f8f76c7b071aff2fe56c4e900c72dcbaddd3c62a /Hotline | |
| parent | f71c4cae3b21db506573bcdfa9fdb6cde41cc0ca (diff) | |
New category listing is working again. Added optionset for user access info. Some UI cleanup across the board.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Hotline/HotlineClient.swift | 174 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineProtocol.swift | 58 | ||||
| -rw-r--r-- | Hotline/Models/Hotline.swift | 15 | ||||
| -rw-r--r-- | Hotline/Utility/FoundationExtensions.swift | 30 | ||||
| -rw-r--r-- | Hotline/Views/ChatView.swift | 71 | ||||
| -rw-r--r-- | Hotline/Views/FilesView.swift | 19 | ||||
| -rw-r--r-- | Hotline/Views/NewsView.swift | 10 | ||||
| -rw-r--r-- | Hotline/Views/TrackerView.swift | 22 | ||||
| -rw-r--r-- | Hotline/Views/UsersView.swift | 2 |
9 files changed, 208 insertions, 193 deletions
diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift index 40096dd..a2621b2 100644 --- a/Hotline/Hotline/HotlineClient.swift +++ b/Hotline/Hotline/HotlineClient.swift @@ -11,6 +11,7 @@ enum HotlineClientStatus: Int { enum HotlineTransactionError: Error { case networkFailure + case timeout case error(UInt32, String?) case invalidMessage(UInt32, String?) } @@ -38,6 +39,7 @@ protocol HotlineClientDelegate: AnyObject { func hotlineReceivedChatMessage(message: String) func hotlineReceivedUserList(users: [HotlineUser]) func hotlineReceivedServerMessage(message: String) + func hotlineReceivedUserAccess(options: HotlineUserAccessOptions) func hotlineUserChanged(user: HotlineUser) func hotlineUserDisconnected(userID: UInt16) } @@ -48,6 +50,7 @@ extension HotlineClientDelegate { func hotlineReceivedChatMessage(message: String) {} func hotlineReceivedUserList(users: [HotlineUser]) {} func hotlineReceivedServerMessage(message: String) {} + func hotlineReceivedUserAccess(options: HotlineUserAccessOptions) {} func hotlineUserChanged(user: HotlineUser) {} func hotlineUserDisconnected(userID: UInt16) {} } @@ -66,12 +69,6 @@ class HotlineClient { var connectionStatus: HotlineClientStatus = .disconnected var connectCallback: ((Bool) -> Void)? -// var chatMessages: [HotlineChat] = [] -// var messageBoardMessages: [String] = [] -// var fileList: [HotlineFile] = [] - var newsCategories: [HotlineNewsCategory] = [] - - var serverVersion: UInt16 = 151 private var connection: NWConnection? private var transactionLog: [UInt32:(HotlineTransactionType, ((HotlineTransaction, HotlineTransactionError?) -> Void)?)] = [:] @@ -83,12 +80,12 @@ class HotlineClient { // MARK: - - func login(_ address: String, port: UInt16, login: String, password: String, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, UInt16) -> Void)?) -> Bool { + func login(_ address: String, port: UInt16, login: String, password: String, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, String?, UInt16?) -> Void)?) -> Bool { print("AWAITING CONNECT") self.connect(address: address, port: port) { [weak self] success in guard success else { DispatchQueue.main.async { - callback?(.networkFailure, 0) + callback?(.networkFailure, nil, nil) } return } @@ -97,28 +94,27 @@ class HotlineClient { self?.sendHandshake() { [weak self] success in guard success else { DispatchQueue.main.async { - callback?(.networkFailure, 0) + callback?(.networkFailure, nil, nil) } return } print("AWAITING LOGIN") - self?.sendLogin(login: login, password: password, username: username, iconID: iconID) { [weak self] err, serverVersion in + self?.sendLogin(login: login, password: password, username: username, iconID: iconID) { [weak self] err, serverName, serverVersion in guard err == nil else { DispatchQueue.main.async { - callback?(err, 0) + callback?(err, nil, nil) } return } - self?.serverVersion = serverVersion - print("SERVER VERSION: \(serverVersion)") + print("LOGGED INTO SERVER: \(serverName) \(serverVersion)") self?.sendSetClientUserInfo(username: username, iconID: iconID) self?.sendGetUserList() DispatchQueue.main.async { - callback?(nil, serverVersion) + callback?(nil, serverName, serverVersion) } } @@ -222,30 +218,24 @@ class HotlineClient { private func reset() { self.transactionLog = [:] - DispatchQueue.main.async { -// self.chatMessages = [] -// self.messageBoardMessages = [] -// self.fileList = [] - self.newsCategories = [] - } } func disconnect() { + for (_, replyInfo) in self.transactionLog { + let replyCallback = replyInfo.1 + replyCallback?(HotlineTransaction(type: replyInfo.0), .networkFailure) + } + self.transactionLog = [:] + self.connection?.cancel() self.connection = nil } - private func updateConnectionStatus(_ status: HotlineClientStatus) { - self.connectionStatus = status - DispatchQueue.main.async { [weak self] in - self?.delegate?.hotlineStatusChanged(status: status) - } - } - // MARK: - private func sendTransaction(_ t: HotlineTransaction, sent sentCallback: ((Bool) -> Void)? = nil, reply replyCallback: ((HotlineTransaction, HotlineTransactionError?) -> Void)? = nil) { guard let c = connection else { + print("NO CONNECTION?????") return } @@ -430,7 +420,7 @@ class HotlineClient { }) } - func sendLogin(login: String, password: String, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, UInt16) -> Void)?) { + func sendLogin(login: String, password: String, username: String, iconID: UInt16, callback: ((HotlineTransactionError?, String?, UInt16?) -> Void)?) { self.updateConnectionStatus(.loggingIn) var t = HotlineTransaction(type: .login) @@ -443,7 +433,7 @@ class HotlineClient { self.sendTransaction(t) { success in if !success { DispatchQueue.main.async { - callback?(.networkFailure, 0) + callback?(.networkFailure, nil, nil) } } } reply: { [weak self] replyTransaction, err in @@ -451,16 +441,24 @@ class HotlineClient { self?.updateConnectionStatus(.loggedIn) var serverVersion: UInt16? + var serverName: String? + if let serverVersionField = replyTransaction.getField(type: .versionNumber), let serverVersionValue = serverVersionField.getUInt16() { - self?.serverVersion = serverVersionValue serverVersion = serverVersionValue print("SERVER VERSION: \(serverVersionValue)") } + if + let serverNameField = replyTransaction.getField(type: .serverName), + let serverNameValue = serverNameField.getString() { + serverName = serverNameValue + print("SERVER NAME: \(serverNameValue)") + } + DispatchQueue.main.async { - callback?(err, serverVersion ?? 0) + callback?(err, serverName, serverVersion) } } } @@ -480,9 +478,9 @@ class HotlineClient { self.sendTransaction(t, sent: sent) } - func sendChat(message: String, sent sentCallback: ((Bool) -> Void)?) { + func sendChat(message: String, encoding: String.Encoding = .utf8, sent sentCallback: ((Bool) -> Void)?) { var t = HotlineTransaction(type: .sendChat) - t.setFieldString(type: .data, val: message) + t.setFieldString(type: .data, val: message, encoding: encoding) self.sendTransaction(t, sent: sentCallback) } @@ -563,7 +561,6 @@ class HotlineClient { } DispatchQueue.main.async { reply?(categories) -// self.newsCategories = categories } }) } @@ -641,87 +638,20 @@ class HotlineClient { } replyCallback?(transaction, nil) - - -// switch(replyCallbackInfo.0) { -// case .login: -// print("GOT REPLY TO LOGIN!") - -// if -// let serverVersionField = transaction.getField(type: .versionNumber), -// let serverVersion = serverVersionField.getUInt16() { -// self.serverVersion = serverVersion -// print("SERVER VERSION: \(serverVersion)") -// } -// case .getUserNameList: -// print("GOT USER LIST") -// var newUsers: [UInt16:HotlineUser] = [:] -// var newUserList: [HotlineUser] = [] -// for u in transaction.getFieldList(type: .userNameWithInfo) { -// let user = u.getUser() -// newUsers[user.id] = user -// newUserList.append(user) -// } -// DispatchQueue.main.async { [weak self] in -//// self.userList = newUserList -// -// self?.delegate?.hotlineReceivedUserList(users: newUserList) -// } -// case .getMessageBoard: -// if let textField = transaction.getField(type: .data), let text = textField.getString() { -// var messages: [String] = [] -// let messageBoardRegex = /([\s\r\n]*[_\-]+[\s\r\n]+)/ -// let matches = text.matches(of: messageBoardRegex) -// var start = text.startIndex -// -// if matches.count > 0 { -// for match in matches { -// let range = match.range -// messages.append(String(text[start..<range.lowerBound])) -// start = range.upperBound -// } -// } -// else { -// messages.append(text) -// } -// -// DispatchQueue.main.async { -// self.messageBoardMessages = messages -// } -// } - // case .getFileNameList: - // var files: [HotlineFile] = [] - // for fi in transaction.getFieldList(type: .fileNameWithInfo) { - // let file = fi.getFile() - // files.append(file) - // } - // DispatchQueue.main.async { - // self.fileList = files - // } -// case .getNewsCategoryNameList: -// var categories: [HotlineNewsCategory] = [] -// for fi in transaction.getFieldList(type: .newsCategoryListData15) { -// let c = fi.getNewsCategory() -// categories.append(c) -// print("CATEGORY: \(c)") -// } -// DispatchQueue.main.async { -// self.newsCategories = categories -// } -// default: -// break -// } - - } - + private func processTransaction(_ transaction: HotlineTransaction) { - if transaction.type == .reply { + if transaction.type == .reply || self.transactionLog[transaction.id] != nil { print("HotlineClient <= \(transaction.type) to \(transaction.id):") print(transaction) } else { - print("HotlineClient <= \(transaction.type)") + print("HotlineClient <= \(transaction.type) \(transaction.id)") + } + + if self.transactionLog[transaction.id] != nil { + self.processReply(transaction) + return } switch(transaction.type) { @@ -739,6 +669,7 @@ class HotlineClient { self?.delegate?.hotlineReceivedChatMessage(message: chatText) } } + case .notifyOfUserChange: if let usernameField = transaction.getField(type: .userName), let username = usernameField.getString(), @@ -763,9 +694,12 @@ class HotlineClient { self?.delegate?.hotlineUserDisconnected(userID: userID) } } + case .disconnectMessage: + // Server disconnected us. print("HotlineClient ❌") self.disconnect() + case .serverMessage: if let messageField = transaction.getField(type: .data), let message = messageField.getString() { @@ -773,8 +707,10 @@ class HotlineClient { self?.delegate?.hotlineReceivedServerMessage(message: message) } } + case .showAgreement: if let _ = transaction.getField(type: .noServerAgreement) { + // Server told us there is no agreement to show. return } if let agreementParam = transaction.getField(type: .data) { @@ -784,11 +720,31 @@ class HotlineClient { } } } + case .userAccess: print("HotlineClient: user access info \(transaction.getField(type: .userAccess).debugDescription)") + if let accessParam = transaction.getField(type: .userAccess) { + if let accessValue = accessParam.getUInt64() { + let accessOptions = HotlineUserAccessOptions(rawValue: accessValue) + DispatchQueue.main.async { [weak self] in + self?.delegate?.hotlineReceivedUserAccess(options: accessOptions) + } + } + } + default: print("HotlineClient: UNKNOWN transaction \(transaction.type) with \(transaction.fields.count) parameters") print(transaction.fields) } } + + // MARK: - Utility + + private func updateConnectionStatus(_ status: HotlineClientStatus) { + self.connectionStatus = status + DispatchQueue.main.async { [weak self] in + self?.delegate?.hotlineStatusChanged(status: status) + } + } + } diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift index e4eba2d..125ec98 100644 --- a/Hotline/Hotline/HotlineProtocol.swift +++ b/Hotline/Hotline/HotlineProtocol.swift @@ -1,5 +1,51 @@ import Foundation +struct HotlineUserAccessOptions: OptionSet { + let rawValue: UInt64 + + static let canRenameFolders = HotlineUserAccessOptions(rawValue: 1 << 0) + static let canDeleteFolders = HotlineUserAccessOptions(rawValue: 1 << 1) + static let canCreateFolders = HotlineUserAccessOptions(rawValue: 1 << 2) + static let canMoveFiles = HotlineUserAccessOptions(rawValue: 1 << 3) + static let canRenameFiles = HotlineUserAccessOptions(rawValue: 1 << 4) + static let canDownloadFiles = HotlineUserAccessOptions(rawValue: 1 << 5) + static let canUploadFiles = HotlineUserAccessOptions(rawValue: 1 << 6) + static let canDeleteFiles = HotlineUserAccessOptions(rawValue: 1 << 7) + + static let canDeleteUsers = HotlineUserAccessOptions(rawValue: 1 << 8) + static let canCreateUsers = HotlineUserAccessOptions(rawValue: 1 << 9) + static let canSendChat = HotlineUserAccessOptions(rawValue: 1 << 13) + static let canReadChat = HotlineUserAccessOptions(rawValue: 1 << 14) + static let canMoveFolders = HotlineUserAccessOptions(rawValue: 1 << 15) + + static let cannotBeDisconnected = HotlineUserAccessOptions(rawValue: 1 << 16) + static let canDisconnectUsers = HotlineUserAccessOptions(rawValue: 1 << 17) + static let canPostNews = HotlineUserAccessOptions(rawValue: 1 << 18) + static let canReadNews = HotlineUserAccessOptions(rawValue: 1 << 19) + static let canModifyUsers = HotlineUserAccessOptions(rawValue: 1 << 22) + static let canReadUsers = HotlineUserAccessOptions(rawValue: 1 << 23) + + static let canMakeAliases = HotlineUserAccessOptions(rawValue: 1 << 24) + static let canViewDropBoxes = HotlineUserAccessOptions(rawValue: 1 << 25) + static let canCommentFolders = HotlineUserAccessOptions(rawValue: 1 << 26) + static let canCommentFiles = HotlineUserAccessOptions(rawValue: 1 << 27) + static let dontShowAgreement = HotlineUserAccessOptions(rawValue: 1 << 28) + static let canUseAnyName = HotlineUserAccessOptions(rawValue: 1 << 29) + static let canUploadAnywhere = HotlineUserAccessOptions(rawValue: 1 << 30) + static let canGetUserInfo = HotlineUserAccessOptions(rawValue: 1 << 31) + + static let canDownloadFolders = HotlineUserAccessOptions(rawValue: 1 << 32) + static let canUploadFolders = HotlineUserAccessOptions(rawValue: 1 << 33) + static let canDeleteNewsFolders = HotlineUserAccessOptions(rawValue: 1 << 34) + static let canCreateNewsFolders = HotlineUserAccessOptions(rawValue: 1 << 35) + static let canDeleteNewsCategories = HotlineUserAccessOptions(rawValue: 1 << 36) + static let canCreateNewsCategories = HotlineUserAccessOptions(rawValue: 1 << 37) + static let canDeleteNewsArticles = HotlineUserAccessOptions(rawValue: 1 << 38) + static let canBroadcast = HotlineUserAccessOptions(rawValue: 1 << 39) + + static let canSendMessages = HotlineUserAccessOptions(rawValue: 1 << 47) +} + struct HotlineServer: Identifiable, Hashable { let id = UUID() let address: String @@ -294,6 +340,10 @@ struct HotlineTransactionField { return self.data.readUInt32(at: 0) } + func getUInt64() -> UInt64? { + return self.data.readUInt64(at: 0) + } + func getInteger() -> Int? { switch(self.data.count) { case 1: @@ -308,6 +358,10 @@ struct HotlineTransactionField { if let val = self.getUInt32() { return Int(val) } + case 8: + if let val = self.getUInt64() { + return Int(val) + } default: break } @@ -381,8 +435,8 @@ struct HotlineTransaction { self.fields.append(HotlineTransactionField(type: type, string: val, encrypt: true)) } - mutating func setFieldString(type: HotlineTransactionFieldType, val: String) { - self.fields.append(HotlineTransactionField(type: type, string: val)) + mutating func setFieldString(type: HotlineTransactionFieldType, val: String, encoding: String.Encoding = .utf8) { + self.fields.append(HotlineTransactionField(type: type, string: val, encoding: encoding, encrypt: false)) } mutating func setFieldPath(type: HotlineTransactionFieldType, val: [String]) { diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift index b2a9489..beb3a74 100644 --- a/Hotline/Models/Hotline.swift +++ b/Hotline/Models/Hotline.swift @@ -137,6 +137,7 @@ struct User: Identifiable { var server: Server? = nil var serverVersion: UInt16? = nil + var serverName: String? = nil var username: String = "bolt" var iconID: UInt = 128 @@ -180,8 +181,11 @@ struct User: Identifiable { self.iconID = iconID return await withCheckedContinuation { [weak self] continuation in - let _ = self?.client.login(server.address, port: UInt16(server.port), login: login, password: password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverVersion in + let _ = self?.client.login(server.address, port: UInt16(server.port), login: login, password: password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in self?.serverVersion = serverVersion + if serverName != nil { + self?.serverName = serverName + } continuation.resume(returning: (err != nil)) } } @@ -212,7 +216,6 @@ struct User: Identifiable { continuation.resume(returning: []) return } - // Failed to send? }, reply: { [weak self] files in let parentFile = self?.findFile(in: self?.files ?? [], at: path) @@ -222,11 +225,9 @@ struct User: Identifiable { } if let parent = parentFile { - print("FOUND PARENT AT \(path)") parent.children = newFiles } else if path.isEmpty { - print("FOUND ROOT AT \(path)") self?.files = newFiles } @@ -270,6 +271,7 @@ struct User: Identifiable { if status == .disconnected { self.serverVersion = nil + self.serverName = nil self.users = [] self.chat = [] self.messageBoard = [] @@ -334,6 +336,11 @@ struct User: Identifiable { } } + func hotlineReceivedUserAccess(options: HotlineUserAccessOptions) { + print("Hotline: got access options") + print(options, options.contains(.canSendChat), options.contains(.canBroadcast)) + } + func hotlineReceivedError(message: String) { } diff --git a/Hotline/Utility/FoundationExtensions.swift b/Hotline/Utility/FoundationExtensions.swift index 719df40..3797dea 100644 --- a/Hotline/Utility/FoundationExtensions.swift +++ b/Hotline/Utility/FoundationExtensions.swift @@ -5,24 +5,6 @@ enum Endianness { case little } -func detectStringEncoding(of data: Data) -> String.Encoding { -// var nsString: NSString? = nil -// guard case let rawValue = NSString.stringEncoding(for: data, encodingOptions: [.allowLossyKey: false], convertedString: nil, usedLossyConversion: nil) else { -// print("NO ENCODING") -// return nil -// } - - let rawValue = NSString.stringEncoding(for: data, encodingOptions: [.allowLossyKey: false], convertedString: nil, usedLossyConversion: nil) - -// let cfEnc: CFStringEncoding = CFStringConvertNSStringEncodingToEncoding(rawValue); - -// let encodingString: CFString? = CFStringGetNameOfEncoding(cfEnc) - - print("DETECTED ENCODING \(rawValue)") - - return String.Encoding(rawValue: rawValue) -} - extension Data { init(_ val: UInt8) { self.init() @@ -62,6 +44,18 @@ extension Data { return (UInt32(self[offset]) << 24) + (UInt32(self[offset + 1]) << 16) + (UInt32(self[offset + 2]) << 8) + UInt32(self[offset + 3]) } + func readUInt64(at offset: Int) -> UInt64? { + guard offset >= 0, offset + 8 <= self.count else { + return nil + } + + return withUnsafeBytes { $0.load(as: UInt64.self ) } + +// return 0 + +// return (UInt64(self[offset]) << 56) + (UInt64(self[offset + 1]) << 48) + (UInt64(self[offset + 2]) << 40) + (UInt64(self[offset + 3]) << 32) + (UInt64(self[offset + 4]) << 24) + (UInt64(self[offset + 5]) << 16) + (UInt64(self[offset + 6]) << 8) + UInt64(self[offset + 7]) + } + func readData(at offset: Int, length: Int) -> Data? { guard offset >= 0, offset + length <= self.count else { return nil diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift index 60951c3..8e5b135 100644 --- a/Hotline/Views/ChatView.swift +++ b/Hotline/Views/ChatView.swift @@ -15,12 +15,12 @@ struct ChatView: View { @State private var contentHeight: CGFloat = 0 @Namespace var bottomID - + var body: some View { NavigationStack { VStack(spacing: 0) { - ScrollView { - ScrollViewReader { reader in + ScrollViewReader { reader in + ScrollView { LazyVStack(alignment: .leading) { ForEach(model.chat) { msg in if msg.type == .agreement { @@ -71,27 +71,27 @@ struct ChatView: View { } Spacer() } - .padding() + .padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) } } EmptyView().id(bottomID) } - .onChange(of: model.chat.count) { - withAnimation { - reader.scrollTo(bottomID, anchor: .bottom) - } - print("SCROLLED TO BOTTOM") - } - .onAppear { - withAnimation { - reader.scrollTo("bottom view", anchor: .bottom) - } + .padding(.bottom, 12) + } + .onChange(of: model.chat.count) { + withAnimation { + reader.scrollTo(bottomID, anchor: .bottom) } + print("SCROLLED TO BOTTOM") + } + .onAppear { + print("SCROLLED TO BOTTOM ON APPEAR") + reader.scrollTo(bottomID, anchor: .bottom) + } + .scrollDismissesKeyboard(.interactively) + .onTapGesture { + self.endEditing() } - } - .scrollDismissesKeyboard(.interactively) - .onTapGesture { - self.endEditing() } Divider() @@ -104,7 +104,7 @@ struct ChatView: View { .onSubmit { if !self.input.isEmpty { model.sendChat(self.input) -// hotline.sendChat(message: self.input) + // hotline.sendChat(message: self.input) } self.input = "" } @@ -112,7 +112,7 @@ struct ChatView: View { Button { if !self.input.isEmpty { model.sendChat(self.input) -// hotline.sendChat(message: self.input) + // hotline.sendChat(message: self.input) } self.input = "" } label: { @@ -143,37 +143,6 @@ struct ChatView: View { } } - - - // GeometryReader { geometry in - // ScrollView(.vertical) { - // ScrollViewReader { scrollReader in - // VStack(alignment: .leading) { - // Spacer() - // List(hotline.chatMessages) { msg in - // HStack(alignment: .firstTextBaseline) { - // Text("\(msg.username):").bold().fontDesign(.monospaced) - // Text(msg.message) - // .fontDesign(.monospaced) - // .textSelection(.enabled) - // } - // } - // .padding() - // } - // // .frame(width: geometry.size.width) - // .frame(minHeight: geometry.size.height) - //// .background(Color.red) - // .onAppear() { - //// scrollReader.scrollTo("bottomScroll", anchor: .bottom) - // } - // // .onChange() { - // // scrollReader.scrollTo(10000, anchor: .bottomTrailing) - // // } - // } - // } - // } - - } } diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift index 23b19b1..5cbf0c9 100644 --- a/Hotline/Views/FilesView.swift +++ b/Hotline/Views/FilesView.swift @@ -27,6 +27,10 @@ struct FileView: View { } } .onChange(of: expanded) { + if !expanded { + return + } + Task { await model.getFileList(path: file.path) } @@ -85,7 +89,7 @@ struct FileView: View { struct FilesView: View { @Environment(Hotline.self) private var model: Hotline - @State var initialLoad = false + @State var initialLoadComplete = false var body: some View { NavigationStack { @@ -94,9 +98,18 @@ struct FilesView: View { .frame(height: 44) } .task { - if !initialLoad { + if !initialLoadComplete { let _ = await model.getFileList() - initialLoad = true + initialLoadComplete = true + } + } + .overlay { + if !initialLoadComplete { + VStack { + ProgressView() + .controlSize(.large) + } + .frame(maxWidth: .infinity) } } .listStyle(.plain) diff --git a/Hotline/Views/NewsView.swift b/Hotline/Views/NewsView.swift index 431b3ce..ad7904e 100644 --- a/Hotline/Views/NewsView.swift +++ b/Hotline/Views/NewsView.swift @@ -9,7 +9,7 @@ struct NewsView: View { @State private var topListHeight: CGFloat = 200 @State private var dividerHeight: CGFloat = 30 - var topList: some View { + var articleList: some View { // Your list content goes here List { @@ -30,10 +30,11 @@ struct NewsView: View { } } } + .scrollBounceBehavior(.basedOnSize) // .listStyle(.plain) } - var bottomList: some View { + var readerView: some View { // Your list content goes here ScrollView(.vertical) { HStack(alignment: .top, spacing: 0) { @@ -43,13 +44,14 @@ struct NewsView: View { } .padding() } + .scrollBounceBehavior(.basedOnSize) .background(colorScheme == .dark ? Color(white: 0.1) : Color(uiColor: UIColor.systemBackground)) } var body: some View { NavigationStack { VStack(spacing: 0) { - topList + articleList .frame(height: topListHeight) VStack(alignment: .center) { Divider() @@ -73,7 +75,7 @@ struct NewsView: View { // bottomListHeight = max(min(bottomListHeight - delta, 400), 0) } ) - bottomList + readerView } .task { if !fetched { diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift index 01f4311..acb2069 100644 --- a/Hotline/Views/TrackerView.swift +++ b/Hotline/Views/TrackerView.swift @@ -199,13 +199,33 @@ struct TrackerView: View { // "hotline.ubersoft.org" // "tracked.nailbat.com" // "hotline.duckdns.org" - self.servers = await model.getServers(address: "tracked.agent79.org") +// "tracked.agent79.org" + self.servers = await model.getServers(address: "hltracker.com") } var body: some View { ZStack(alignment: .center) { VStack(alignment: .center) { ZStack(alignment: .top) { + HStack(alignment: .center) { + Button { + connectVisible = true + connectDismissed = false + } label: { + Text(Image(systemName: "gearshape.fill")) + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) + .font(.title2) + .padding(.leading, 16) + } + .sheet(isPresented: $connectVisible) { + connectDismissed = true + } content: { + TrackerConnectView() + } + Spacer() + } + .frame(height: 40.0) Image("Hotline") .resizable() .renderingMode(.template) diff --git a/Hotline/Views/UsersView.swift b/Hotline/Views/UsersView.swift index ad43131..f923ff4 100644 --- a/Hotline/Views/UsersView.swift +++ b/Hotline/Views/UsersView.swift @@ -13,7 +13,7 @@ struct UsersView: View { .foregroundStyle(u.status.contains(.admin) ? Color(hex: 0xE10000) : Color.accentColor) .opacity(u.status.contains(.idle) ? 0.5 : 1.0) } -// .listStyle(.grouped) + .scrollBounceBehavior(.basedOnSize) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { |