aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-12-06 13:52:20 -0800
committerDustin Mierau <dustin@mierau.me>2023-12-06 13:52:20 -0800
commitc0068f11c51bb587c16dfacb73629b3c6fb67fc8 (patch)
tree20753433f348b03bb590e0fe6a4cafbe56f4c859
parent06a2166415bca7e5fe32eac505859bdd690ab8e0 (diff)
Further work on UI organization
-rw-r--r--Hotline.xcodeproj/project.pbxproj18
-rw-r--r--Hotline/Hotline/HotlineClient.swift85
-rw-r--r--Hotline/Hotline/HotlineProtocol.swift57
-rw-r--r--Hotline/Hotline/HotlineTrackerClient.swift4
-rw-r--r--Hotline/HotlineApp.swift2
-rw-r--r--Hotline/HotlineState.swift (renamed from Hotline/Views/HotlineState.swift)0
-rw-r--r--Hotline/Utility/DataExtensions.swift11
-rw-r--r--Hotline/Views/AgreementView.swift40
-rw-r--r--Hotline/Views/ChatView.swift156
-rw-r--r--Hotline/Views/FileListView.swift26
-rw-r--r--Hotline/Views/FilesView.swift88
-rw-r--r--Hotline/Views/HotlineView.swift9
-rw-r--r--Hotline/Views/MessageBoardView.swift72
-rw-r--r--Hotline/Views/NewsView.swift70
-rw-r--r--Hotline/Views/ServerView.swift126
-rw-r--r--Hotline/Views/TrackerServerView.swift94
-rw-r--r--Hotline/Views/TrackerView.swift103
-rw-r--r--Hotline/Views/UserListView.swift30
18 files changed, 554 insertions, 437 deletions
diff --git a/Hotline.xcodeproj/project.pbxproj b/Hotline.xcodeproj/project.pbxproj
index f6aa976..863147e 100644
--- a/Hotline.xcodeproj/project.pbxproj
+++ b/Hotline.xcodeproj/project.pbxproj
@@ -11,15 +11,13 @@
DA0D698D2B1E7CF700C71DF5 /* UserListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D698C2B1E7CF700C71DF5 /* UserListView.swift */; };
DA0D698F2B1E841600C71DF5 /* MessageBoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D698E2B1E841600C71DF5 /* MessageBoardView.swift */; };
DA0D69912B1E894800C71DF5 /* FilesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA0D69902B1E894800C71DF5 /* FilesView.swift */; };
- DA161B6C2B1FA520000918F3 /* FileListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA161B6B2B1FA520000918F3 /* FileListView.swift */; };
DA43205C2B1D003B00FC8843 /* HotlineClientModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA43205B2B1D003B00FC8843 /* HotlineClientModel.swift */; };
DA43205E2B1D615600FC8843 /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA43205D2B1D615600FC8843 /* ServerView.swift */; };
DA4F2BF62B1668A700D8ADDC /* HotlineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BF52B1668A700D8ADDC /* HotlineClient.swift */; };
DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BF72B16A17200D8ADDC /* HotlineProtocol.swift */; };
- DA4F2BFB2B1A518500D8ADDC /* AgreementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BFA2B1A518500D8ADDC /* AgreementView.swift */; };
- DA4F2BFD2B1A52EF00D8ADDC /* TrackerServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BFC2B1A52EF00D8ADDC /* TrackerServerView.swift */; };
DA4F2BFF2B1A54FF00D8ADDC /* HotlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2BFE2B1A54FF00D8ADDC /* HotlineView.swift */; };
DA4F2C012B1A558E00D8ADDC /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2C002B1A558E00D8ADDC /* ChatView.swift */; };
+ DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA77253E2B21176D006C5ABB /* NewsView.swift */; };
DA9CAFBB2B126D5700CDA197 /* HotlineApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFBA2B126D5700CDA197 /* HotlineApp.swift */; };
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFBC2B126D5700CDA197 /* TrackerView.swift */; };
DA9CAFC12B126D5800CDA197 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA9CAFC02B126D5800CDA197 /* Assets.xcassets */; };
@@ -33,15 +31,13 @@
DA0D698C2B1E7CF700C71DF5 /* UserListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserListView.swift; sourceTree = "<group>"; };
DA0D698E2B1E841600C71DF5 /* MessageBoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBoardView.swift; sourceTree = "<group>"; };
DA0D69902B1E894800C71DF5 /* FilesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilesView.swift; sourceTree = "<group>"; };
- DA161B6B2B1FA520000918F3 /* FileListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileListView.swift; sourceTree = "<group>"; };
DA43205B2B1D003B00FC8843 /* HotlineClientModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineClientModel.swift; sourceTree = "<group>"; };
DA43205D2B1D615600FC8843 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
DA4F2BF52B1668A700D8ADDC /* HotlineClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineClient.swift; sourceTree = "<group>"; };
DA4F2BF72B16A17200D8ADDC /* HotlineProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineProtocol.swift; sourceTree = "<group>"; };
- DA4F2BFA2B1A518500D8ADDC /* AgreementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AgreementView.swift; sourceTree = "<group>"; };
- DA4F2BFC2B1A52EF00D8ADDC /* TrackerServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerServerView.swift; sourceTree = "<group>"; };
DA4F2BFE2B1A54FF00D8ADDC /* HotlineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineView.swift; sourceTree = "<group>"; };
DA4F2C002B1A558E00D8ADDC /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; };
+ DA77253E2B21176D006C5ABB /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; };
DA9CAFB72B126D5700CDA197 /* Hotline.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Hotline.app; sourceTree = BUILT_PRODUCTS_DIR; };
DA9CAFBA2B126D5700CDA197 /* HotlineApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineApp.swift; sourceTree = "<group>"; };
DA9CAFBC2B126D5700CDA197 /* TrackerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerView.swift; sourceTree = "<group>"; };
@@ -66,15 +62,12 @@
isa = PBXGroup;
children = (
DA4F2BFE2B1A54FF00D8ADDC /* HotlineView.swift */,
- DA0D698A2B1E5A5200C71DF5 /* HotlineState.swift */,
- DA4F2BFA2B1A518500D8ADDC /* AgreementView.swift */,
DA0D698E2B1E841600C71DF5 /* MessageBoardView.swift */,
+ DA77253E2B21176D006C5ABB /* NewsView.swift */,
DA43205D2B1D615600FC8843 /* ServerView.swift */,
DA4F2C002B1A558E00D8ADDC /* ChatView.swift */,
DA0D698C2B1E7CF700C71DF5 /* UserListView.swift */,
DA0D69902B1E894800C71DF5 /* FilesView.swift */,
- DA161B6B2B1FA520000918F3 /* FileListView.swift */,
- DA4F2BFC2B1A52EF00D8ADDC /* TrackerServerView.swift */,
DA9CAFBC2B126D5700CDA197 /* TrackerView.swift */,
);
path = Views;
@@ -100,6 +93,7 @@
isa = PBXGroup;
children = (
DA9CAFBA2B126D5700CDA197 /* HotlineApp.swift */,
+ DA0D698A2B1E5A5200C71DF5 /* HotlineState.swift */,
DA4F2BF92B1A516F00D8ADDC /* Views */,
DABFCC262B1530AE009F40D2 /* Hotline */,
DABFCC272B1530BE009F40D2 /* Utility */,
@@ -206,12 +200,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- DA161B6C2B1FA520000918F3 /* FileListView.swift in Sources */,
DA43205C2B1D003B00FC8843 /* HotlineClientModel.swift in Sources */,
- DA4F2BFB2B1A518500D8ADDC /* AgreementView.swift in Sources */,
DA43205E2B1D615600FC8843 /* ServerView.swift in Sources */,
DA0D69912B1E894800C71DF5 /* FilesView.swift in Sources */,
- DA4F2BFD2B1A52EF00D8ADDC /* TrackerServerView.swift in Sources */,
DA0D698B2B1E5A5200C71DF5 /* HotlineState.swift in Sources */,
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */,
DA9CAFCB2B126E3300CDA197 /* HotlineTrackerClient.swift in Sources */,
@@ -220,6 +211,7 @@
DA4F2BFF2B1A54FF00D8ADDC /* HotlineView.swift in Sources */,
DA4F2BF62B1668A700D8ADDC /* HotlineClient.swift in Sources */,
DABFCC292B1530DC009F40D2 /* DataExtensions.swift in Sources */,
+ DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */,
DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */,
DA0D698D2B1E7CF700C71DF5 /* UserListView.swift in Sources */,
DA4F2C012B1A558E00D8ADDC /* ChatView.swift in Sources */,
diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift
index aaf2168..f108c79 100644
--- a/Hotline/Hotline/HotlineClient.swift
+++ b/Hotline/Hotline/HotlineClient.swift
@@ -9,24 +9,32 @@ enum HotlineClientStatus: Int {
case loggedIn
}
+enum HotlineChatType: Int {
+ case message
+ case agreement
+ case status
+}
+
struct HotlineChat: Identifiable {
let id = UUID()
- let message: String
+ let text: String
let username: String
- let isTopic: Bool
+ let type: HotlineChatType
static let parser = /\s+(.+):\s+(.*)/
- init(message: String, isTopic: Bool = false) {
- self.isTopic = isTopic
+ init(text: String, type: HotlineChatType = .message) {
+ self.type = type
- if let match = message.firstMatch(of: HotlineChat.parser) {
+ if
+ type == .message,
+ let match = text.firstMatch(of: HotlineChat.parser) {
self.username = String(match.1)
- self.message = String(match.2)
+ self.text = String(match.2)
}
else {
self.username = ""
- self.message = message
+ self.text = text
}
}
}
@@ -47,7 +55,6 @@ class HotlineClient {
var users: [UInt16:HotlineUser] = [:]
var userList: [HotlineUser] = []
var chatMessages: [HotlineChat] = []
- var messageBoard: String = ""
var messageBoardMessages: [String] = []
var fileList: [HotlineFile] = []
@@ -60,7 +67,8 @@ class HotlineClient {
@ObservationIgnored private var transactionLog: [UInt32:HotlineTransactionType] = [:]
init() {
-
+// let downloadsPath = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)
+// print("DOWNLOAD TO: \(downloadsPath)")
}
// MARK: -
@@ -91,11 +99,13 @@ class HotlineClient {
DispatchQueue.main.async {
self?.connectionStatus = .disconnected
}
+ self?.reset()
case .failed(let err):
print("HotlineClient: connection error \(err)")
DispatchQueue.main.async {
self?.connectionStatus = .disconnected
}
+ self?.reset()
default:
print("HotlineClient: unhandled connection state \(newState)")
}
@@ -107,15 +117,22 @@ class HotlineClient {
self.connection?.start(queue: .global())
}
- private func disconnect() {
- self.connection?.cancel()
- self.connection = nil
-
+ func reset() {
DispatchQueue.main.async {
- self.connectionStatus = .disconnected
+ self.chatMessages = []
+ self.agreement = nil
+ self.users = [:]
+ self.userList = []
+ self.messageBoardMessages = []
+ self.fileList = []
}
}
+ func disconnect() {
+ self.connection?.cancel()
+ self.connection = nil
+ }
+
// MARK: -
private func sendTransaction(_ t: HotlineTransaction, autodisconnect disconnectOnError: Bool = true, callback: (() -> Void)? = nil) {
@@ -343,16 +360,26 @@ class HotlineClient {
self.sendTransaction(t, callback: callback)
}
- func sendGetNews(callback: (() -> Void)? = nil) {
+ func sendGetMessageBoard(callback: (() -> Void)? = nil) {
let t = HotlineTransaction(type: .getMessages)
self.sendTransaction(t, callback: callback)
}
+ func sendGetNewsCategories(callback: (() -> Void)? = nil) {
+ let t = HotlineTransaction(type: .getNewsCategoryNameList)
+ self.sendTransaction(t, callback: callback)
+ }
+
+ func sendGetNewsArticles(callback: (() -> Void)? = nil) {
+ let t = HotlineTransaction(type: .getNewsArticleNameList)
+ self.sendTransaction(t, callback: callback)
+ }
+
func sendGetFileList(path: String? = nil, callback: (() -> Void)? = nil) {
let t = HotlineTransaction(type: .getFileNameList)
- if let p = path {
+// if let p = path {
// t.setFieldString(type: .filePath)
- }
+// }
self.sendTransaction(t, callback: callback)
}
@@ -407,14 +434,19 @@ class HotlineClient {
let messageBoardRegex = /([\s\r\n]*[_\-]+[\s\r\n]+)/
let matches = text.matches(of: messageBoardRegex)
var start = text.startIndex
- for match in matches {
- let range = match.range
- messages.append(String(text[start..<range.lowerBound]))
- start = range.upperBound
+
+ 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.messageBoard = text
self.messageBoardMessages = messages
}
}
@@ -422,12 +454,16 @@ class HotlineClient {
var files: [HotlineFile] = []
for fi in transaction.getFieldList(type: .fileNameWithInfo) {
let file = fi.getFile()
-// print("GOT FILE: \(file.name) \(file.creator) \(file.type) \(file.fileSize)")
files.append(file)
}
DispatchQueue.main.async {
self.fileList = files
}
+ case .getNewsCategoryNameList:
+ for fi in transaction.getFieldList(type: .newsCategoryListData15) {
+ let c = fi.getNewsCategory()
+ print("CATEGORY: \(c)")
+ }
default:
break
}
@@ -459,7 +495,7 @@ class HotlineClient {
{
print("HotlineClient: \(chatText)")
DispatchQueue.main.async {
- self.chatMessages.append(HotlineChat(message: chatText, isTopic: false))
+ self.chatMessages.append(HotlineChat(text: chatText, type: .message))
}
}
case .notifyOfUserChange:
@@ -481,6 +517,7 @@ class HotlineClient {
print(agreementText)
print("\n--------------------------\n\n")
DispatchQueue.main.async {
+ self.chatMessages.insert(HotlineChat(text: agreementText, type: .agreement), at: 0)
self.agreement = agreementText
}
// self.sendAgree() {
diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift
index a7392ec..0cf3d06 100644
--- a/Hotline/Hotline/HotlineProtocol.swift
+++ b/Hotline/Hotline/HotlineProtocol.swift
@@ -29,6 +29,51 @@ extension UInt32 {
}
}
+struct HotlineNewsCategory: Identifiable, Hashable {
+ let id = UUID()
+ let type: UInt16
+ let count: UInt16
+ let name: String
+
+ static func == (lhs: HotlineNewsCategory, rhs: HotlineNewsCategory) -> Bool {
+ return lhs.id == rhs.id
+ }
+
+ func hash(into hasher: inout Hasher) {
+ hasher.combine(self.id)
+ }
+
+ init(type: UInt16, count: UInt16, name: String) {
+ self.type = type
+ self.count = count
+ self.name = name
+ }
+
+ init(from data: Data) {
+ self.type = data.readUInt16(at: 0)!
+
+ if self.type == 2 {
+ // Read bundle properties
+ self.count = data.readUInt16(at: 2)!
+
+ let nameSize = data.readUInt8(at: 4)!
+ self.name = data.readString(at: 5, length: Int(nameSize))!
+ }
+ else if self.type == 3 {
+ // Read category properties
+ self.count = data.readUInt16(at: 2)!
+
+ let nameSize = data.readUInt8(at: 2 + 2 + 4 + 4)!
+ self.name = data.readString(at: 2 + 2 + 4 + 4 + 1, length: Int(nameSize))!
+ }
+ else {
+ self.count = 0
+ self.name = ""
+ }
+ }
+}
+
+
struct HotlineFile: Identifiable, Hashable {
let id = UUID()
let type: String
@@ -72,7 +117,7 @@ struct HotlineFile: Identifiable, Hashable {
// let nameScript = data.readUInt16(at: 16)! // name script
let nameLength = data.readUInt16(at: 18)!
- self.name = data.readString(at: 20, length: Int(nameLength), encoding: .ascii)!
+ self.name = data.readString(at: 20, length: Int(nameLength))!
}
}
@@ -107,7 +152,7 @@ struct HotlineUser: Identifiable, Hashable {
self.status = data.readUInt16(at: 4)!
let userNameLength = Int(data.readUInt16(at: 6)!)
- self.name = data.readString(at: 8, length: userNameLength, encoding: .ascii)!
+ self.name = data.readString(at: 8, length: userNameLength)!
}
func hash(into hasher: inout Hasher) {
@@ -210,8 +255,8 @@ struct HotlineTransactionField {
return nil
}
- func getString(encoding: String.Encoding = .ascii) -> String? {
- return String(data: self.data, encoding: encoding)
+ func getString() -> String? {
+ return String(data: self.data, encoding: .utf8) ?? String(data: self.data, encoding: .ascii)
}
func getUser() -> HotlineUser {
@@ -221,6 +266,10 @@ struct HotlineTransactionField {
func getFile() -> HotlineFile {
return HotlineFile(from: self.data)
}
+
+ func getNewsCategory() -> HotlineNewsCategory {
+ return HotlineNewsCategory(from: self.data)
+ }
}
struct HotlineTransaction {
diff --git a/Hotline/Hotline/HotlineTrackerClient.swift b/Hotline/Hotline/HotlineTrackerClient.swift
index cff3888..4ae252c 100644
--- a/Hotline/Hotline/HotlineTrackerClient.swift
+++ b/Hotline/Hotline/HotlineTrackerClient.swift
@@ -250,10 +250,10 @@ class HotlineTrackerClient {
let nameLengthByte = self.bytes.readUInt8(at: cursor + 10) {
let nameLength = Int(nameLengthByte)
- if let name = self.bytes.readString(at: cursor + 11, length: nameLength, encoding: .utf8) ?? self.bytes.readString(at: cursor + 11, length: nameLength, encoding: .ascii) {
+ if let name = self.bytes.readString(at: cursor + 11, length: nameLength) {
if let descLengthByte = self.bytes.readUInt8(at: cursor + 11 + nameLength) {
let descLength = Int(descLengthByte)
- if let desc = self.bytes.readString(at: cursor + 11 + nameLength + 1, length: descLength, encoding: .utf8) ?? self.bytes.readString(at: cursor + 11 + nameLength + 1, length: descLength, encoding: .ascii) {
+ if let desc = self.bytes.readString(at: cursor + 11 + nameLength + 1, length: descLength) {
let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc)
print("SERVER: \(server)")
diff --git a/Hotline/HotlineApp.swift b/Hotline/HotlineApp.swift
index d999e89..3e714d8 100644
--- a/Hotline/HotlineApp.swift
+++ b/Hotline/HotlineApp.swift
@@ -23,7 +23,7 @@ struct HotlineApp: App {
var body: some Scene {
WindowGroup {
- HotlineView()
+ TrackerView()
.environment(appState)
.environment(hotline)
.environment(tracker)
diff --git a/Hotline/Views/HotlineState.swift b/Hotline/HotlineState.swift
index 9ae4940..9ae4940 100644
--- a/Hotline/Views/HotlineState.swift
+++ b/Hotline/HotlineState.swift
diff --git a/Hotline/Utility/DataExtensions.swift b/Hotline/Utility/DataExtensions.swift
index 9f4c967..aefd204 100644
--- a/Hotline/Utility/DataExtensions.swift
+++ b/Hotline/Utility/DataExtensions.swift
@@ -51,8 +51,15 @@ extension Data {
return self.subdata(in: offset..<(offset + length))
}
- func readString(at offset: Int, length: Int, encoding: String.Encoding) -> String? {
- return String(data: self[offset..<(offset + length)], encoding: encoding)
+ func readString(at offset: Int, length: Int) -> String? {
+ var str: String?
+
+ str = String(data: self[offset..<(offset + length)], encoding: .utf8)
+ if str == nil {
+ str = String(data: self[offset..<(offset + length)], encoding: .ascii)
+ }
+
+ return str
}
diff --git a/Hotline/Views/AgreementView.swift b/Hotline/Views/AgreementView.swift
deleted file mode 100644
index 39730ce..0000000
--- a/Hotline/Views/AgreementView.swift
+++ /dev/null
@@ -1,40 +0,0 @@
-import SwiftUI
-
-struct AgreementView: View {
- @Environment(HotlineState.self) private var appState
-
- let text: String
-
- var body: some View {
-// @Bindable var config = appState
-
- VStack(alignment: .leading) {
- ScrollView {
- VStack(alignment: .leading) {
- Text(text)
- .fontDesign(.monospaced)
- .padding()
- .dynamicTypeSize(.small)
- .textSelection(.enabled)
- }
- }
- Button("OK") {
- print("DONE")
- appState.dismissAgreement()
- }
- .bold()
- .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
- .frame(maxWidth: .infinity)
- }
- .presentationDetents([.fraction(0.6)])
- .presentationDragIndicator(.visible)
- }
-}
-
-#Preview {
- AgreementView(text: """
-Welcome!
-
-Take it on real one.
-""")
-}
diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift
index e28e076..dc6e1d4 100644
--- a/Hotline/Views/ChatView.swift
+++ b/Hotline/Views/ChatView.swift
@@ -7,63 +7,117 @@ struct ChatView: View {
@State private var scrollPos: Int?
@State private var contentHeight: CGFloat = 0
+ @Namespace var bottomID
+
var body: some View {
- VStack(spacing: 0) {
- List(hotline.chatMessages) { msg in
- HStack(alignment: .firstTextBaseline) {
- if !msg.username.isEmpty {
- Text("\(msg.username):").bold().fontDesign(.monospaced).font(.system(size: 12))
+ NavigationStack {
+ VStack(spacing: 0) {
+ ScrollView {
+ ScrollViewReader { reader in
+ LazyVStack(alignment: .leading) {
+ ForEach(hotline.chatMessages) { msg in
+ if msg.type == .agreement {
+
+ VStack(alignment: .leading) {
+ Text(msg.text)
+ .padding()
+ .opacity(0.75)
+ .background(Color(white: 0.96))
+ .cornerRadius(20)
+ }
+ .padding()
+ }
+ else {
+ HStack(alignment: .firstTextBaseline) {
+ if !msg.username.isEmpty {
+ Text("\(msg.username):").bold()
+ }
+ Text(msg.text)
+ .textSelection(.enabled)
+ Spacer()
+ }
+ .padding()
+ }
+ }
+ Text("").id(bottomID)
+ }
+ .onChange(of: hotline.chatMessages.count) {
+ withAnimation {
+ reader.scrollTo(bottomID, anchor: .bottom)
+ }
+ print("SCROLLED TO BOTTOM")
+ }
+ .onAppear {
+ withAnimation {
+ reader.scrollTo("bottom view", anchor: .bottom)
+ }
+ }
}
- Text(msg.message)
- .fontDesign(.monospaced)
- .textSelection(.enabled)
- .font(.system(size: 12))
}
- .listRowSeparator(.hidden)
+
+ Divider()
+
+ HStack(alignment: .top) {
+ Image(systemName: "chevron.right")
+ TextField("", text: $input, axis: .vertical)
+ .lineLimit(1...5)
+ .onSubmit {
+ hotline.sendChat(message: self.input)
+ // HotlineClient.shared.sendChat(message: self.input)
+ self.input = ""
+ }
+ }.padding()
}
- .listStyle(.plain)
-
-// 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)
-// // }
-// }
-// }
-// }
-
- Divider()
-
- HStack(alignment: .top) {
- Image(systemName: "chevron.right")
- TextField("", text: $input, axis: .vertical)
- .lineLimit(1...5)
- .onSubmit {
- hotline.sendChat(message: self.input)
- // HotlineClient.shared.sendChat(message: self.input)
- self.input = ""
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text(hotline.server?.name ?? "")
+ .font(.headline)
+ }
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button {
+ hotline.disconnect()
+ } label: {
+ Image(systemName: "xmark.circle.fill")
+ .symbolRenderingMode(.hierarchical)
+ .foregroundColor(.secondary)
}
- }.padding()
+
+ }
+ }
+
}
+
+
+ // 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/FileListView.swift b/Hotline/Views/FileListView.swift
deleted file mode 100644
index a315757..0000000
--- a/Hotline/Views/FileListView.swift
+++ /dev/null
@@ -1,26 +0,0 @@
-import SwiftUI
-
-struct FileListView: View {
- @State var files: [HotlineFile]
-
- var body: some View {
- Text("HI")
-// @Bindable var fls = files
-// List {
-// ForEach($fls, id: \.self) { f in
-// if f.isFolder {
-// DisclosureGroup(f.name, isExpanded: false)
-// }
-// else {
-// Text("HELLO")
-// }
-// }
-// }
- }
-}
-
-#Preview {
- FileListView(files: [
- HotlineFile(type: "fldr", creator: "", fileSize: 0, fileName: "Folder")
- ])
-}
diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift
index 7bacbce..1b35695 100644
--- a/Hotline/Views/FilesView.swift
+++ b/Hotline/Views/FilesView.swift
@@ -1,4 +1,5 @@
import SwiftUI
+import UniformTypeIdentifiers
struct FilesView: View {
@Environment(HotlineClient.self) private var hotline
@@ -14,28 +15,79 @@ struct FilesView: View {
return FilesView.byteFormatter.string(fromByteCount: Int64(fileSize))
}
+ private func fileIcon(name: String) -> UIImage {
+// func utTypeForFilename(_ filename: String) -> UTType? {
+ let fileExtension = (name as NSString).pathExtension
+ if let fileType = UTType(filenameExtension: fileExtension) {
+ print("\(name) \(fileExtension) = \(fileType)")
+
+ if fileType.isSubtype(of: .movie) {
+ return UIImage(systemName: "play.rectangle")!
+ }
+ else if fileType.isSubtype(of: .image) {
+ return UIImage(systemName: "photo")!
+ }
+ else if fileType.isSubtype(of: .archive) {
+ return UIImage(systemName: "doc.zipper")!
+ }
+ else if fileType.isSubtype(of: .text) {
+ return UIImage(systemName: "doc.text")!
+ }
+ else {
+ return UIImage(systemName: "doc")!
+ }
+ }
+
+ return UIImage(systemName: "doc")!
+ }
+
var body: some View {
- List(hotline.fileList, id: \.self, children: \.files) { tree in
- HStack {
- if tree.isFolder {
- Image(systemName: "folder")
- Text(tree.name).bold()
- Spacer()
- Text("\(tree.fileSize)").foregroundStyle(.gray)
+ NavigationStack {
+ List(hotline.fileList, id: \.self, children: \.files) { tree in
+ HStack {
+ if tree.isFolder {
+ HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) {
+ Image(systemName: "folder.fill")
+ }
+ .frame(minWidth: 25)
+ Text(tree.name).bold()
+ Spacer()
+ Text("\(tree.fileSize)").foregroundStyle(.gray)
+ }
+ else {
+ HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) {
+ Image(uiImage: fileIcon(name: tree.name))
+ }
+ .frame(minWidth: 25)
+ Text(tree.name).bold()
+ Spacer()
+ Text(formattedFileSize(tree.fileSize)).foregroundStyle(.gray)
+ }
}
- else {
- Image(systemName: "doc")
- Text(tree.name).bold()
- Spacer()
- Text(formattedFileSize(tree.fileSize)).foregroundStyle(.gray)
+ }
+ .listStyle(.plain)
+ .task {
+ if !fetched {
+ hotline.sendGetFileList() {
+ fetched = true
+ }
}
}
- }
- .listStyle(.plain)
- .task {
- if !fetched {
- hotline.sendGetFileList() {
- fetched = true
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text(hotline.server?.name ?? "")
+ .font(.headline)
+ }
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button {
+ hotline.disconnect()
+ } label: {
+ Image(systemName: "xmark.circle.fill")
+ .symbolRenderingMode(.hierarchical)
+ .foregroundColor(.secondary)
+ }
+
}
}
}
diff --git a/Hotline/Views/HotlineView.swift b/Hotline/Views/HotlineView.swift
index 713691f..f950dd7 100644
--- a/Hotline/Views/HotlineView.swift
+++ b/Hotline/Views/HotlineView.swift
@@ -6,14 +6,7 @@ struct HotlineView: View {
@Environment(HotlineTrackerClient.self) private var tracker
var body: some View {
- @Bindable var config = appState
-
- NavigationStack {
- TrackerView()
- }
- .sheet(isPresented: $config.agreementPresented) {
- AgreementView(text: hotline.agreement!)
- }
+ TrackerView()
}
}
diff --git a/Hotline/Views/MessageBoardView.swift b/Hotline/Views/MessageBoardView.swift
index 8b1a30c..35b4282 100644
--- a/Hotline/Views/MessageBoardView.swift
+++ b/Hotline/Views/MessageBoardView.swift
@@ -8,36 +8,58 @@ struct MessageBoardView: View {
var body: some View {
// @Bindable var config = appState
-
- ScrollView {
- LazyVStack(alignment: .leading) {
- ForEach(hotline.messageBoardMessages, id: \.self) {
- Text($0)
- .lineLimit(100)
- .padding()
- Divider()
+ NavigationStack {
+ ScrollView {
+ LazyVStack(alignment: .leading) {
+ ForEach(hotline.messageBoardMessages, id: \.self) {
+ Text($0)
+ .lineLimit(100)
+ .padding()
+ .textSelection(.enabled)
+ Divider()
+ }
}
+ Spacer()
}
- }
-// List(hotline.messageBoardMessages, id: \.self) {
-// Text($0)
-// .lineLimit(100)
-// .padding()
-// Divider()
-// .listRowSeparator(.hidden)
-// .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
-// }
-// .listStyle(.plain)
- .task {
- if !fetched {
- hotline.sendGetNews() {
- fetched = true
+ .task {
+ if !fetched {
+ hotline.sendGetMessageBoard() {
+ fetched = true
+ }
+ }
+ }
+ .refreshable {
+ hotline.sendGetMessageBoard()
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text(hotline.server?.name ?? "")
+ .font(.headline)
+ }
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button {
+ hotline.disconnect()
+ } label: {
+ Image(systemName: "xmark.circle.fill")
+ .symbolRenderingMode(.hierarchical)
+ .foregroundColor(.secondary)
+ }
+
+ }
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+
+ } label: {
+ Image(systemName: "square.and.pencil")
+// .symbolRenderingMode(.hierarchical)
+// .foregroundColor(.secondary)
+ }
+
}
}
}
- .refreshable {
- hotline.sendGetNews()
- }
+
}
}
diff --git a/Hotline/Views/NewsView.swift b/Hotline/Views/NewsView.swift
new file mode 100644
index 0000000..5f2dbe9
--- /dev/null
+++ b/Hotline/Views/NewsView.swift
@@ -0,0 +1,70 @@
+import SwiftUI
+
+struct NewsView: View {
+ @Environment(HotlineState.self) private var appState
+ @Environment(HotlineClient.self) private var hotline
+
+ @State private var fetched = false
+
+ var body: some View {
+// @Bindable var config = appState
+ NavigationStack {
+ ScrollView {
+ LazyVStack(alignment: .leading) {
+// ForEach(hotline.messageBoardMessages, id: \.self) {
+// Text($0)
+// .lineLimit(100)
+// .padding()
+// .textSelection(.enabled)
+// Divider()
+// }
+ }
+ Spacer()
+ }
+ .task {
+ if !fetched {
+ hotline.sendGetNewsCategories() {
+ fetched = true
+ }
+ }
+ }
+ .refreshable {
+ hotline.sendGetNewsCategories()
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text(hotline.server?.name ?? "")
+ .font(.headline)
+ }
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button {
+ hotline.disconnect()
+ } label: {
+ Image(systemName: "xmark.circle.fill")
+ .symbolRenderingMode(.hierarchical)
+ .foregroundColor(.secondary)
+ }
+
+ }
+ ToolbarItem(placement: .navigationBarTrailing) {
+ Button {
+
+ } label: {
+ Image(systemName: "square.and.pencil")
+// .symbolRenderingMode(.hierarchical)
+// .foregroundColor(.secondary)
+ }
+
+ }
+ }
+ }
+
+ }
+}
+
+#Preview {
+ MessageBoardView()
+ .environment(HotlineState())
+ .environment(HotlineClient())
+}
diff --git a/Hotline/Views/ServerView.swift b/Hotline/Views/ServerView.swift
index ecb13a1..8e50d83 100644
--- a/Hotline/Views/ServerView.swift
+++ b/Hotline/Views/ServerView.swift
@@ -1,11 +1,8 @@
import SwiftUI
struct ServerView: View {
- @Environment(HotlineState.self) private var appState
@Environment(HotlineClient.self) private var hotline
- @Environment(HotlineTrackerClient.self) private var tracker
-
- let server: HotlineServer
+ @Environment(\.colorScheme) var colorScheme
func connectionStatusTitle(status: HotlineClientStatus) -> String {
switch(status) {
@@ -26,102 +23,47 @@ struct ServerView: View {
return Double(status.rawValue) / Double(HotlineClientStatus.loggedIn.rawValue)
}
+ enum Tab {
+ case chat, users, news, messageBoard, files
+ }
+
var body: some View {
- @Bindable var config = appState
-
- NavigationStack {
- if hotline.connectionStatus != .loggedIn {
- VStack {
- Spacer()
- VStack(alignment: .center) {
- Text("🌎").font(.largeTitle)
- Text(server.name!).font(.title3).fontWeight(.medium)
- Text(server.description!).opacity(0.6).font(.title3)
- Text(server.address).opacity(0.6).font(.title3)
- }
- Spacer()
- HStack(alignment: .center) {
- if hotline.connectionStatus == .disconnected {
- ProgressView(connectionStatusTitle(status: hotline.connectionStatus), value: connectionProgress(status: hotline.connectionStatus))
- Button("Connect") {
- hotline.connect(to: server)
- // config.dismissTracker()
- }
- .bold()
- .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
- .frame(maxWidth: .infinity)
- .foregroundColor(.black)
- .background(LinearGradient(gradient: Gradient(colors: [Color(white: 0.95), Color(white: 0.91)]), startPoint: .top, endPoint: .bottom))
- .overlay(
- RoundedRectangle(cornerRadius: 10.0).stroke(.black, lineWidth: 3).opacity(0.4)
- )
- .cornerRadius(10.0)
- }
- else {
- ProgressView("", value: Double(hotline.connectionStatus.rawValue / HotlineClientStatus.loggedIn.rawValue))
- }
- }
+ TabView {
+ ChatView()
+ .tabItem {
+ Image(systemName: "message")
}
- .padding()
- }
- else {
- TabView {
- ChatView()
- .navigationTitle(hotline.server?.name ?? "Hotline")
- .navigationBarTitleDisplayMode(.inline)
- .navigationBarItems(
- leading: Button(action: {
- appState.presentTracker()
- }) {
- Image(systemName: "globe.americas.fill") // Hamburger icon or similar
- .imageScale(.large)
- }
- )
- // .toolbarBackground(.visible, for: .navigationBar)
- // .toolbarBackground(.red, for: .navigationBar)
- .tabItem {
- Image(systemName: "message")
- }
-
- UserListView()
- .navigationTitle("User List")
- .navigationBarTitleDisplayMode(.inline)
- .tabItem {
- Image(systemName: "person.fill")
- }
-
- Text("News")
- .tabItem {
- Image(systemName: "newspaper")
- }
-
- MessageBoardView()
- .navigationTitle("Message Board")
- .navigationBarTitleDisplayMode(.inline)
- .tabItem {
- Image(systemName: "pin")
- }
-
- FilesView()
- .navigationTitle("Files")
- .navigationBarTitleDisplayMode(.inline)
- .tabItem {
- Image(systemName: "folder").tint(.black)
- }
+ .tag(Tab.chat)
+
+ UserListView()
+ .tabItem {
+ Image(systemName: "person.fill")
}
- .accentColor(.black)
- }
+ .tag(Tab.users)
- // .sheet(isPresented: Binding(get: { hotline.connectionStatus != .loggedIn }, set: { _ in })) {
- // TrackerView()
- // }
+ NewsView()
+ .tabItem {
+ Image(systemName: "newspaper")
+ }
+ .tag(Tab.news)
+
+ MessageBoardView()
+ .tabItem {
+ Image(systemName: "pin")
+ }
+ .tag(Tab.messageBoard)
+
+ FilesView()
+ .tabItem {
+ Image(systemName: "folder").tint(.black)
+ }
+ .tag(Tab.files)
}
+ .accentColor(colorScheme == .dark ? .white : .black)
}
}
#Preview {
- ServerView(server: HotlineServer(address: "192.168.1.1", port: 5050, users: 5, name: "Ye Olde Server", description: "This is a server"))
+ ServerView()
.environment(HotlineClient())
- .environment(HotlineTrackerClient(tracker: HotlineTracker("hltracker.com")))
- .environment(HotlineState())
}
diff --git a/Hotline/Views/TrackerServerView.swift b/Hotline/Views/TrackerServerView.swift
deleted file mode 100644
index 5288496..0000000
--- a/Hotline/Views/TrackerServerView.swift
+++ /dev/null
@@ -1,94 +0,0 @@
-import SwiftUI
-
-struct TrackerServerView: View {
- @Environment(HotlineState.self) private var appState
- @Environment(HotlineClient.self) private var hotline
-
- let server: HotlineServer
-
- @State private var expanded = false
-
- func shouldDisplayDescription(server: HotlineServer) -> Bool {
- guard let name = server.name, let desc = server.description else {
- return false
- }
-
- return desc.count > 0 && desc != name && !desc.contains(/^-+/)
- }
-
- var body: some View {
- @Bindable var config = appState
-
- VStack(alignment: .leading) {
- HStack(alignment: .firstTextBaseline) {
- Text("🌎").font(.title3)
- VStack(alignment: .leading) {
- Text(server.name!).font(.title3).fontWeight(.medium)
- if shouldDisplayDescription(server: server) {
- Text(server.description!).opacity(0.6).font(.title3)
- }
- }
- }
- .padding()
- .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
- .listRowSeparator(.hidden)
- .listRowBackground(Color(white: 0.96))
-
- // .padding(EdgeInsets(top: 0, leading: 0, bottom: 8.0, trailing: 0))
-// ProgressView(value: 0.5)
-//
-// HStack(alignment: .center) {
-// Button("Connect") {
-// hotline.connect(to: server)
-// config.dismissTracker()
-// // dismiss()
-// }
-// .bold()
-// .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
-// .frame(maxWidth: .infinity)
-// .foregroundColor(.black)
-// .background(LinearGradient(gradient: Gradient(colors: [Color(white: 0.95), Color(white: 0.91)]), startPoint: .top, endPoint: .bottom))
-// .overlay(
-// RoundedRectangle(cornerRadius: 10.0).stroke(.black, lineWidth: 3).opacity(0.4)
-// )
-// .cornerRadius(10.0)
-// }
- }
-// label: {
-// HStack(alignment: .firstTextBaseline) {
-// Text("🌎").font(.title3)
-// VStack(alignment: .leading) {
-// Text(server.name!).font(.title3).fontWeight(.medium)
-// if shouldDisplayDescription(server: server) {
-// Spacer()
-// Text(server.description!).opacity(0.6).font(.title3)
-// }
-// }
-// }
-// .padding()
-// .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
-// .listRowSeparator(.hidden)
-// .listRowBackground(Color(white: 0.96))
-// }
- .popover(isPresented: $expanded) {
- Text("Popover Content")
- .padding()
- }
- .background(Color(white: 0.96))
- .padding(EdgeInsets(top: 8.0, leading: 24.0, bottom: 8.0, trailing: 24.0))
- .presentationDetents([.fraction(0.4)])
- .presentationDragIndicator(.visible)
- .onTapGesture {
- expanded = true
-// withAnimation {
-// expanded.toggle()
-// }
- }
- }
-}
-
-#Preview {
- TrackerServerView(server: HotlineServer(address: "192.168.1.1", port: 5050, users: 5, name: "Ye Olde Server", description: "This is a server"))
- .environment(HotlineClient())
- .environment(HotlineState())
-}
diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift
index 83a4ebf..3ed66dd 100644
--- a/Hotline/Views/TrackerView.swift
+++ b/Hotline/Views/TrackerView.swift
@@ -8,6 +8,7 @@ struct TrackerView: View {
@Environment(HotlineState.self) private var appState
@Environment(HotlineClient.self) private var hotline
@Environment(HotlineTrackerClient.self) private var tracker
+ @Environment(\.colorScheme) var colorScheme
@State private var selectedServer: HotlineServer?
@@ -19,37 +20,90 @@ struct TrackerView: View {
return desc.count > 0 && desc != name && !desc.contains(/^-+/)
}
+ func connectionStatusToProgress(status: HotlineClientStatus) -> Double {
+ switch status {
+ case .disconnected:
+ return 0.0
+ case .connecting:
+ return 0.1
+ case .connected:
+ return 0.25
+ case .loggingIn:
+ return 0.5
+ case .loggedIn:
+
+ return 1.0
+ }
+ }
+
var body: some View {
@Bindable var config = appState
+ @Bindable var client = hotline
- List(selection: $selectedServer) {
- ForEach(tracker.servers) { server in
- NavigationLink {
- ServerView(server: server)
- } label: {
- HStack(alignment: .firstTextBaseline) {
- Text("🌎").font(.title3)
- VStack(alignment: .leading) {
- Text(server.name!).font(.title3).fontWeight(.medium)
- if shouldDisplayDescription(server: server) {
- Text(server.description!).opacity(0.6).font(.title3)
+ ScrollView {
+ LazyVStack(alignment: .leading) {
+ ForEach(tracker.servers) { server in
+ VStack(alignment: .leading) {
+ HStack(alignment: .firstTextBaseline) {
+ Image(systemName: "globe.americas.fill").font(.title3)
+ VStack(alignment: .leading) {
+ Text(server.name!).font(.title3).fontWeight(.medium)
+ if shouldDisplayDescription(server: server) {
+ Spacer()
+ Text(server.description!).opacity(0.4).font(.system(size: 16))
+ }
+ Spacer()
+ Text("\(server.address)").opacity(0.2).font(.system(size: 13))
+ }
+ Spacer()
+ Text("\(server.users)").opacity(0.2).font(.system(size: 16)).fontWeight(.medium)
+ }
+ if server == selectedServer {
+ Spacer(minLength: 16)
+
+ if hotline.server == server && hotline.connectionStatus != .disconnected {
+ ProgressView("", value: connectionStatusToProgress(status: hotline.connectionStatus))
+ }
+ else {
+ Button("Connect") {
+ hotline.connect(to: server)
+ }
+ .bold()
+ .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
+ .frame(maxWidth: .infinity)
+ .foregroundColor(.black)
+ .background(LinearGradient(gradient: Gradient(colors: [Color(white: 0.95), Color(white: 0.91)]), startPoint: .top, endPoint: .bottom))
+ .overlay(
+ RoundedRectangle(cornerRadius: 10.0).stroke(.black, lineWidth: 3).opacity(0.4)
+ )
+ .cornerRadius(10.0)
}
}
}
+ .multilineTextAlignment(.leading)
+ .padding()
+ .background(colorScheme == .dark ? Color(white: 0.1) : .white)
+ .cornerRadius(20)
+ .shadow(color: Color(white: 0.0, opacity: 0.1), radius: 16, x: 0, y: 10)
+ .onTapGesture {
+ withAnimation(.bouncy(duration: 0.25, extraBounce: 0.2)) {
+ selectedServer = server
+ }
+ }
}
+ .padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
}
}
- .background(Color.white)
- .listStyle(.plain)
- .listRowSpacing(1)
+ .fullScreenCover(isPresented: Binding(get: { return hotline.connectionStatus == .loggedIn }, set: { _ in }), onDismiss: {
+ hotline.disconnect()
+ }) {
+ ServerView()
+ }
+ .background(colorScheme == .dark ? .black : Color(white: 0.95))
.frame(maxWidth: .infinity)
.task {
tracker.fetch()
}
- // .sheet(item: $selectedServer) { item in
- //// Text("HELLO")
- // TrackerServerView(server: item)
- // }
.refreshable {
await withCheckedContinuation { continuation in
tracker.fetch() {
@@ -59,19 +113,6 @@ struct TrackerView: View {
}
.navigationTitle("Tracker")
.navigationBarTitleDisplayMode(.inline)
- .toolbar {
- ToolbarItem(placement: .topBarTrailing) {
- Button {
- config.dismissTracker()
- } label: {
- Image(systemName: "xmark.circle.fill")
- .symbolRenderingMode(.hierarchical)
- .foregroundColor(.gray)
- }
- }
- }
-
- // .interactiveDismissDisabled()
}
}
diff --git a/Hotline/Views/UserListView.swift b/Hotline/Views/UserListView.swift
index 70b58b9..cae1d0c 100644
--- a/Hotline/Views/UserListView.swift
+++ b/Hotline/Views/UserListView.swift
@@ -4,14 +4,32 @@ struct UserListView: View {
@Environment(HotlineClient.self) private var hotline
var body: some View {
- VStack(spacing: 0) {
- List(hotline.userList) { u in
- HStack(alignment: .firstTextBaseline) {
- Text(u.name).bold().foregroundStyle(u.isAdmin ? Color.red : Color.black).opacity(u.isIdle ? 0.5 : 1.0)
+ NavigationStack {
+ VStack(spacing: 0) {
+ List(hotline.userList) { u in
+ HStack(alignment: .firstTextBaseline) {
+ Text(u.name).bold().foregroundStyle(u.isAdmin ? Color.red : Color.black).opacity(u.isIdle ? 0.5 : 1.0)
+ }
+ }
+ .listStyle(.plain)
+ .padding()
+ }
+ .navigationBarTitleDisplayMode(.inline)
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text(hotline.server?.name ?? "")
+ .font(.headline)
+ }
+ ToolbarItem(placement: .navigationBarLeading) {
+ Button {
+ hotline.disconnect()
+ } label: {
+ Image(systemName: "xmark.circle.fill")
+ .symbolRenderingMode(.hierarchical)
+ .foregroundColor(.secondary)
+ }
}
}
- .listStyle(.plain)
- .padding()
}
}
}