diff options
| author | Dustin Mierau <dustin@mierau.me> | 2023-12-06 22:52:09 -0800 |
|---|---|---|
| committer | Dustin Mierau <dustin@mierau.me> | 2023-12-06 22:52:09 -0800 |
| commit | e3392bb948ad53889ad334140952f2acf614668e (patch) | |
| tree | 74937a9202b3bb71f28ef9d8e0b3560bdd195b49 /Hotline | |
| parent | 195af38221d1a4358455ba8501b87e4098da101b (diff) | |
Some work on news. More UI tweaks across the board.
Diffstat (limited to 'Hotline')
| -rw-r--r-- | Hotline/Assets.xcassets/AppIcon.appiconset/App Icon.png | bin | 8399 -> 8190 bytes | |||
| -rw-r--r-- | Hotline/Assets.xcassets/Hotline.imageset/Contents.json | 55 | ||||
| -rw-r--r-- | Hotline/Assets.xcassets/Hotline.imageset/hotline-black.svg | 3 | ||||
| -rw-r--r-- | Hotline/Assets.xcassets/Hotline.imageset/hotline-white.svg | 3 | ||||
| -rw-r--r-- | Hotline/Assets.xcassets/Image.imageset/Contents.json | 20 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineClient.swift | 26 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineProtocol.swift | 49 | ||||
| -rw-r--r-- | Hotline/Hotline/HotlineTrackerClient.swift | 48 | ||||
| -rw-r--r-- | Hotline/Utility/FoundationExtensions.swift (renamed from Hotline/Utility/DataExtensions.swift) | 10 | ||||
| -rw-r--r-- | Hotline/Utility/ObservableScrollView.swift | 38 | ||||
| -rw-r--r-- | Hotline/Utility/SwiftUIExtensions.swift | 7 | ||||
| -rw-r--r-- | Hotline/Views/ChatView.swift | 6 | ||||
| -rw-r--r-- | Hotline/Views/FilesView.swift | 2 | ||||
| -rw-r--r-- | Hotline/Views/NewsView.swift | 89 | ||||
| -rw-r--r-- | Hotline/Views/TrackerView.swift | 148 | ||||
| -rw-r--r-- | Hotline/Views/UserListView.swift | 16 |
16 files changed, 413 insertions, 107 deletions
diff --git a/Hotline/Assets.xcassets/AppIcon.appiconset/App Icon.png b/Hotline/Assets.xcassets/AppIcon.appiconset/App Icon.png Binary files differindex 02d1c69..c81665b 100644 --- a/Hotline/Assets.xcassets/AppIcon.appiconset/App Icon.png +++ b/Hotline/Assets.xcassets/AppIcon.appiconset/App Icon.png diff --git a/Hotline/Assets.xcassets/Hotline.imageset/Contents.json b/Hotline/Assets.xcassets/Hotline.imageset/Contents.json new file mode 100644 index 0000000..ebc37a0 --- /dev/null +++ b/Hotline/Assets.xcassets/Hotline.imageset/Contents.json @@ -0,0 +1,55 @@ +{ + "images" : [ + { + "filename" : "hotline-black.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "hotline-white.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/Hotline/Assets.xcassets/Hotline.imageset/hotline-black.svg b/Hotline/Assets.xcassets/Hotline.imageset/hotline-black.svg new file mode 100644 index 0000000..5c98c36 --- /dev/null +++ b/Hotline/Assets.xcassets/Hotline.imageset/hotline-black.svg @@ -0,0 +1,3 @@ +<svg width="80" height="160" viewBox="0 0 80 160" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 23L30 0V64H50V23L80 23L80 136L50 160V95.1256L30 95.0595V137H0V23Z" fill="black"/> +</svg> diff --git a/Hotline/Assets.xcassets/Hotline.imageset/hotline-white.svg b/Hotline/Assets.xcassets/Hotline.imageset/hotline-white.svg new file mode 100644 index 0000000..967ea62 --- /dev/null +++ b/Hotline/Assets.xcassets/Hotline.imageset/hotline-white.svg @@ -0,0 +1,3 @@ +<svg width="80" height="160" viewBox="0 0 80 160" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 23L30 0V64H50V23L80 23L80 136L50 160V95.1256L30 95.0595V137H0V23Z" fill="white"/> +</svg> diff --git a/Hotline/Assets.xcassets/Image.imageset/Contents.json b/Hotline/Assets.xcassets/Image.imageset/Contents.json new file mode 100644 index 0000000..a19a549 --- /dev/null +++ b/Hotline/Assets.xcassets/Image.imageset/Contents.json @@ -0,0 +1,20 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Hotline/Hotline/HotlineClient.swift b/Hotline/Hotline/HotlineClient.swift index f108c79..9d62811 100644 --- a/Hotline/Hotline/HotlineClient.swift +++ b/Hotline/Hotline/HotlineClient.swift @@ -51,12 +51,12 @@ class HotlineClient { ]) var connectionStatus: HotlineClientStatus = .disconnected - var agreement: String? var users: [UInt16:HotlineUser] = [:] var userList: [HotlineUser] = [] var chatMessages: [HotlineChat] = [] var messageBoardMessages: [String] = [] var fileList: [HotlineFile] = [] + var newsCategories: [HotlineNewsCategory] = [] var userName: String = "bolt" var userIconID: UInt16 = 128 @@ -118,13 +118,14 @@ class HotlineClient { } func reset() { + self.transactionLog = [:] DispatchQueue.main.async { self.chatMessages = [] - self.agreement = nil self.users = [:] self.userList = [] self.messageBoardMessages = [] self.fileList = [] + self.newsCategories = [] } } @@ -370,8 +371,11 @@ class HotlineClient { self.sendTransaction(t, callback: callback) } - func sendGetNewsArticles(callback: (() -> Void)? = nil) { - let t = HotlineTransaction(type: .getNewsArticleNameList) + func sendGetNewsArticles(path: [String]? = nil, callback: (() -> Void)? = nil) { + var t = HotlineTransaction(type: .getNewsArticleNameList) + if path != nil { + t.setFieldPath(type: .newsPath, val: path!) + } self.sendTransaction(t, callback: callback) } @@ -383,6 +387,11 @@ class HotlineClient { self.sendTransaction(t, callback: callback) } +// func sendGetNews(callback: (() -> Void)? = nil) { +// let t = HotlineTransaction(type: .getNewsFile) +// self.sendTransaction(t, callback: callback) +// } + // MARK: - Incoming private func processReply(_ transaction: HotlineTransaction) { @@ -460,10 +469,15 @@ class HotlineClient { 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 } @@ -508,7 +522,7 @@ class HotlineClient { print("HotlineClient ❌") self.disconnect() case .showAgreement: - if let noAgreementField = transaction.getField(type: .noServerAgreement) { + if let _ = transaction.getField(type: .noServerAgreement) { print("NO AGREEMENT?") } if let agreementParam = transaction.getField(type: .data) { @@ -518,7 +532,7 @@ class HotlineClient { print("\n--------------------------\n\n") DispatchQueue.main.async { self.chatMessages.insert(HotlineChat(text: agreementText, type: .agreement), at: 0) - self.agreement = agreementText +// self.agreement = agreementText } // self.sendAgree() { // self.sendGetUserList() diff --git a/Hotline/Hotline/HotlineProtocol.swift b/Hotline/Hotline/HotlineProtocol.swift index 0cf3d06..a0e129e 100644 --- a/Hotline/Hotline/HotlineProtocol.swift +++ b/Hotline/Hotline/HotlineProtocol.swift @@ -55,16 +55,18 @@ struct HotlineNewsCategory: Identifiable, Hashable { 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))! + let (n, _) = data.readPString(at: 4) + self.name = n! } 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))! +// let guid = data.readUInt32(at: 4)! +// print("CATEGORY GUID: \(guid)") +// let addSN = data.readUInt32(at: 20)! +// let removeSN = data.readUInt32(at: 24)! + let (n, _) = data.readPString(at: 28) + self.name = n! } else { self.count = 0 @@ -221,6 +223,37 @@ struct HotlineTransactionField { init(type: HotlineTransactionFieldType, string: String, encrypt: Bool) { self.init(type: type, string: string, encoding: .ascii, encrypt: encrypt) } + + init(type: HotlineTransactionFieldType, path: String) { + var components: [String] = [] + + for component in path.components(separatedBy: "/") { + if !component.isEmpty { + components.append(component) + } + } + + self.init(type: type, pathComponents: components) + } + + init(type: HotlineTransactionFieldType, pathComponents: [String]) { + var pathData = Data() + + pathData.appendUInt16(UInt16(pathComponents.count)) + for name in pathComponents { + pathData.appendUInt16(0) + + var nameData = name.data(using: .ascii, allowLossyConversion: true) + if nameData == nil { + nameData = Data() + } + + pathData.appendUInt8(UInt8(nameData!.count)) + pathData.append(nameData!) + } + + self.init(type: type, dataSize: UInt16(pathData.count), data: pathData) + } func getUInt8() -> UInt8? { return self.data.readUInt8(at: 0) @@ -325,6 +358,10 @@ struct HotlineTransaction { self.fields.append(HotlineTransactionField(type: type, string: val)) } + mutating func setFieldPath(type: HotlineTransactionFieldType, val: [String]) { + self.fields.append(HotlineTransactionField(type: type, pathComponents: val)) + } + func getField(type: HotlineTransactionFieldType) -> HotlineTransactionField? { return self.fields.first { p in p.type == type diff --git a/Hotline/Hotline/HotlineTrackerClient.swift b/Hotline/Hotline/HotlineTrackerClient.swift index 4ae252c..78f681f 100644 --- a/Hotline/Hotline/HotlineTrackerClient.swift +++ b/Hotline/Hotline/HotlineTrackerClient.swift @@ -246,24 +246,40 @@ class HotlineTrackerClient { let ip_3 = self.bytes.readUInt8(at: cursor + 2), let ip_4 = self.bytes.readUInt8(at: cursor + 3), let port = self.bytes.readUInt16(at: cursor + 4), - let userCount = self.bytes.readUInt16(at: cursor + 6), - let nameLengthByte = self.bytes.readUInt8(at: cursor + 10) { + let userCount = self.bytes.readUInt16(at: cursor + 6) { +// let nameLengthByte = self.bytes.readUInt8(at: cursor + 10) { - let nameLength = Int(nameLengthByte) - 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) { - let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) - - print("SERVER: \(server)") - - foundServers.append(server) - - cursor += 11 + nameLength + 1 + descLength - } - } + let (serverName, nameByteCount) = self.bytes.readPString(at: cursor + 10) + let (serverDescription, descByteCount) = self.bytes.readPString(at: cursor + 10 + nameByteCount) + + if let name = serverName, + let desc = serverDescription { + let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) + + print("SERVER: \(server)") + + foundServers.append(server) + + } + + cursor += 10 + nameByteCount + descByteCount + +// let nameLength = Int(nameLengthByte) +// 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) { +// let server = HotlineServer(address: "\(ip_1).\(ip_2).\(ip_3).\(ip_4)", port: port, users: userCount, name: name, description: desc) +// +// print("SERVER: \(server)") +// +// foundServers.append(server) +// +// cursor += 11 + nameLength + 1 + descLength +// } +// } +// } } print(cursor) diff --git a/Hotline/Utility/DataExtensions.swift b/Hotline/Utility/FoundationExtensions.swift index aefd204..c6ca251 100644 --- a/Hotline/Utility/DataExtensions.swift +++ b/Hotline/Utility/FoundationExtensions.swift @@ -62,6 +62,16 @@ extension Data { return str } + func readPString(at offset: Int) -> (String?, Int) { + let len = Int(self.readUInt8(at: offset)!) + return (self.readString(at: offset+1, length: len), 1 + len) + } + + func readLongPString(at offset: Int) -> (String?, Int) { + let len = Int(self.readUInt16(at: offset)!) + return (self.readString(at: offset+2, length: len), 2 + len) + } + mutating func appendUInt8(_ value: UInt8, endianness: Endianness = .big) { var val = endianness == .big ? value.bigEndian : value.littleEndian diff --git a/Hotline/Utility/ObservableScrollView.swift b/Hotline/Utility/ObservableScrollView.swift new file mode 100644 index 0000000..a6f46cc --- /dev/null +++ b/Hotline/Utility/ObservableScrollView.swift @@ -0,0 +1,38 @@ +import SwiftUI + +struct ScrollViewOffsetPreferenceKey: PreferenceKey { + typealias Value = CGFloat + static var defaultValue = CGFloat.zero + static func reduce(value: inout Value, nextValue: () -> Value) { + value += nextValue() + } +} + +struct ObservableScrollView<Content>: View where Content : View { + @Namespace var scrollSpace + @Binding var scrollOffset: CGFloat + let content: () -> Content + + init(scrollOffset: Binding<CGFloat>, + @ViewBuilder content: @escaping () -> Content) { + _scrollOffset = scrollOffset + self.content = content + } + + var body: some View { + ScrollView { + content() + .background(GeometryReader { geo in + let offset = -geo.frame(in: .named(scrollSpace)).minY + Color.clear + .preference(key: ScrollViewOffsetPreferenceKey.self, + value: offset) + }) + + } + .coordinateSpace(name: scrollSpace) + .onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in + scrollOffset = value + } + } +} diff --git a/Hotline/Utility/SwiftUIExtensions.swift b/Hotline/Utility/SwiftUIExtensions.swift new file mode 100644 index 0000000..3b850e0 --- /dev/null +++ b/Hotline/Utility/SwiftUIExtensions.swift @@ -0,0 +1,7 @@ +import SwiftUI + +extension Color { + init(hex: Int, opacity: Double = 1.0) { + self.init(red: Double((hex >> 16) & 0xFF) / 255.0, green: Double((hex >> 8) & 0xFF) / 255.0, blue: Double(hex & 0xFF) / 255.0, opacity: opacity) + } +} diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift index dc6e1d4..1aee6b2 100644 --- a/Hotline/Views/ChatView.swift +++ b/Hotline/Views/ChatView.swift @@ -2,6 +2,7 @@ import SwiftUI struct ChatView: View { @Environment(HotlineClient.self) private var hotline + @Environment(\.colorScheme) var colorScheme @State var input: String = "" @State private var scrollPos: Int? @@ -17,13 +18,12 @@ struct ChatView: View { 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) + .background(colorScheme == .dark ? Color(white: 0.1) : Color(white: 0.96)) + .cornerRadius(16) } .padding() } diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift index 1b35695..3b2b236 100644 --- a/Hotline/Views/FilesView.swift +++ b/Hotline/Views/FilesView.swift @@ -57,6 +57,8 @@ struct FilesView: View { else { HStack(alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/) { Image(uiImage: fileIcon(name: tree.name)) + .renderingMode(.template) + .foregroundColor(.accentColor) } .frame(minWidth: 25) Text(tree.name).bold() diff --git a/Hotline/Views/NewsView.swift b/Hotline/Views/NewsView.swift index 5f2dbe9..9c4d34a 100644 --- a/Hotline/Views/NewsView.swift +++ b/Hotline/Views/NewsView.swift @@ -5,32 +5,89 @@ struct NewsView: View { @Environment(HotlineClient.self) private var hotline @State private var fetched = false + @State private var selectedCategory: HotlineNewsCategory? = nil + @State private var topListHeight: CGFloat = 200 + @State private var dividerHeight: CGFloat = 30 + + var topList: some View { + + // Your list content goes here + List { + ForEach(hotline.newsCategories, id: \.self) { cat in + DisclosureGroup { + ProgressView(value: 0.4) + .task { + print("EXPANDED?", cat.name) + hotline.sendGetNewsArticles(path: [cat.name]) { + print("OK") + } + } + } label: { + Text(cat.name) + .fontWeight(.medium) + .lineLimit(1) + .truncationMode(.tail) + } + } + } +// .listStyle(.plain) + } + + var bottomList: some View { + // Your list content goes here + ScrollView(.vertical) { + HStack(alignment: .top, spacing: 0) { + Text("HELLO") + .multilineTextAlignment(.leading) + Spacer() + } + .padding() + } + } 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() -// } + VStack(spacing: 0) { + topList + .frame(height: topListHeight) + VStack(alignment: .center) { + Divider() + Spacer() + HStack(alignment: .center) { + Image(systemName: "line.3.horizontal") + .opacity(0.2) + .font(.system(size: 14)) + } + Spacer() +// Divider() } - Spacer() + .background(Color(uiColor: UIColor.systemBackground)) + .frame(maxWidth: .infinity) + .frame(height: dividerHeight) + .gesture( + DragGesture() + .onChanged { gesture in + let delta = gesture.translation.height + topListHeight = max(min(topListHeight + delta, 500), 50) + // bottomListHeight = max(min(bottomListHeight - delta, 400), 0) + } + ) + bottomList } .task { if !fetched { hotline.sendGetNewsCategories() { fetched = true } + + // hotline.sendGetNewsArticles(path: ["News"]) { + // print("GOT ARTICLES?") + // } } } - .refreshable { - hotline.sendGetNewsCategories() - } + // .refreshable { + // hotline.sendGetNewsCategories() + // } .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { @@ -52,8 +109,8 @@ struct NewsView: View { } label: { Image(systemName: "square.and.pencil") -// .symbolRenderingMode(.hierarchical) -// .foregroundColor(.secondary) + // .symbolRenderingMode(.hierarchical) + // .foregroundColor(.secondary) } } diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift index 3ed66dd..7f59118 100644 --- a/Hotline/Views/TrackerView.swift +++ b/Hotline/Views/TrackerView.swift @@ -11,6 +11,7 @@ struct TrackerView: View { @Environment(\.colorScheme) var colorScheme @State private var selectedServer: HotlineServer? + @State var scrollOffset: CGFloat = CGFloat.zero func shouldDisplayDescription(server: HotlineServer) -> Bool { guard let name = server.name, let desc = server.description else { @@ -36,83 +37,126 @@ struct TrackerView: View { } } + func inverseLerp(lower: Double, upper: Double, v: Double) -> Double { + return (v - lower) / (upper - lower) + } + var body: some View { @Bindable var config = appState @Bindable var client = hotline - 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) { + ZStack(alignment: .center) { + VStack(alignment: .center) { + ZStack(alignment: .top) { + Image("Hotline") + .resizable() + .renderingMode(.template) + .foregroundColor(Color(hex: 0xE10000)) + .scaledToFit() + .frame(width: 40.0, height: 40.0) + HStack(alignment: .center) { + Spacer() + Button { + // hotline.disconnect() + } label: { + Text(Image(systemName: "point.3.connected.trianglepath.dotted")) + .symbolRenderingMode(.hierarchical) + .foregroundColor(.primary) + .font(.title2) + .padding(.trailing, 16) + } + } + .frame(height: 40.0) + } + .padding() + .opacity(scrollOffset > 80 ? 0 : 1.0) +// .padding(.top, 5) + .opacity(inverseLerp(lower: -80, upper: 0, v: scrollOffset)) +// .opacity(inverseLerp(lower: 20, upper: 0, v: scrollOffset)) + + Spacer() + } + ObservableScrollView(scrollOffset: $scrollOffset) { + 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.5).font(.system(size: 16)) + } Spacer() - Text(server.description!).opacity(0.4).font(.system(size: 16)) + Text("\(server.address)").opacity(0.3).font(.system(size: 13)) } 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)) + Text("\(server.users)").opacity(0.3).font(.system(size: 16)).fontWeight(.medium) } - else { - Button("Connect") { - hotline.connect(to: server) + if server == selectedServer { + Spacer(minLength: 16) + + if hotline.server == server && hotline.connectionStatus != .disconnected { + ProgressView(value: connectionStatusToProgress(status: hotline.connectionStatus)) + .frame(minHeight: 10) + .accentColor(colorScheme == .dark ? .white : .black) + } + else { + Button("Connect") { + hotline.connect(to: server) + } + .bold() + .padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24)) + .frame(maxWidth: .infinity) + .foregroundColor(colorScheme == .dark ? .white : .black) + .background( + colorScheme == .dark ? + LinearGradient(gradient: Gradient(colors: [Color(white: 0.4), Color(white: 0.3)]), startPoint: .top, endPoint: .bottom) + : + 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(colorScheme == .dark ? 0.0 : 0.2) + ) + .cornerRadius(10.0) } - .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 + .multilineTextAlignment(.leading) + .padding() + .background(colorScheme == .dark ? Color(white: 0.12) : .white) + .cornerRadius(16) + .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)) + } + .padding(EdgeInsets(top: 75, leading: 0, bottom: 0, trailing: 0)) + } + .refreshable { + await withCheckedContinuation { continuation in + tracker.fetch() { + continuation.resume() + } } - .padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16)) } + } .fullScreenCover(isPresented: Binding(get: { return hotline.connectionStatus == .loggedIn }, set: { _ in }), onDismiss: { hotline.disconnect() }) { ServerView() } - .background(colorScheme == .dark ? .black : Color(white: 0.95)) + .background(Color(uiColor: UIColor.systemGroupedBackground)) .frame(maxWidth: .infinity) .task { tracker.fetch() } - .refreshable { - await withCheckedContinuation { continuation in - tracker.fetch() { - continuation.resume() - } - } - } - .navigationTitle("Tracker") - .navigationBarTitleDisplayMode(.inline) } } diff --git a/Hotline/Views/UserListView.swift b/Hotline/Views/UserListView.swift index cae1d0c..e0fa7fb 100644 --- a/Hotline/Views/UserListView.swift +++ b/Hotline/Views/UserListView.swift @@ -5,15 +5,15 @@ struct UserListView: View { var body: some View { 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() + List(hotline.userList) { u in + Text(u.name) + .fontWeight(.medium) + .lineLimit(1) + .truncationMode(.tail) + .foregroundStyle(u.isAdmin ? Color(hex: 0xE10000) : Color.accentColor) + .opacity(u.isIdle ? 0.5 : 1.0) } +// .listStyle(.grouped) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .principal) { |