aboutsummaryrefslogtreecommitdiff
path: root/Hotline
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-12-12 15:27:00 -0800
committerDustin Mierau <dustin@mierau.me>2023-12-12 15:27:00 -0800
commit248fcbbd5dca84747e6f3fd9d1d52c41ab8640c3 (patch)
treef8f76c7b071aff2fe56c4e900c72dcbaddd3c62a /Hotline
parentf71c4cae3b21db506573bcdfa9fdb6cde41cc0ca (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.swift174
-rw-r--r--Hotline/Hotline/HotlineProtocol.swift58
-rw-r--r--Hotline/Models/Hotline.swift15
-rw-r--r--Hotline/Utility/FoundationExtensions.swift30
-rw-r--r--Hotline/Views/ChatView.swift71
-rw-r--r--Hotline/Views/FilesView.swift19
-rw-r--r--Hotline/Views/NewsView.swift10
-rw-r--r--Hotline/Views/TrackerView.swift22
-rw-r--r--Hotline/Views/UsersView.swift2
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) {