aboutsummaryrefslogtreecommitdiff
path: root/Hotline/Views
diff options
context:
space:
mode:
authorDustin Mierau <dustin@mierau.me>2023-12-11 23:32:28 -0800
committerDustin Mierau <dustin@mierau.me>2023-12-11 23:32:28 -0800
commit18c4b8643df09be4ce52a4b110ea21ac1ad053fc (patch)
tree335d4f0a6b430b983b6f00567b91eb1ee6b16b4b /Hotline/Views
parent84aaa9ef0a1be6986e38d7e4f58e07d3cb84979e (diff)
Move to new Hotline model for data observation and let client exist standalone.
Diffstat (limited to 'Hotline/Views')
-rw-r--r--Hotline/Views/ChatView.swift38
-rw-r--r--Hotline/Views/FilesView.swift31
-rw-r--r--Hotline/Views/MessageBoardView.swift27
-rw-r--r--Hotline/Views/ServerView.swift36
-rw-r--r--Hotline/Views/TrackerView.swift214
-rw-r--r--Hotline/Views/UsersView.swift (renamed from Hotline/Views/UserListView.swift)16
6 files changed, 269 insertions, 93 deletions
diff --git a/Hotline/Views/ChatView.swift b/Hotline/Views/ChatView.swift
index 1b3ba52..ce3942e 100644
--- a/Hotline/Views/ChatView.swift
+++ b/Hotline/Views/ChatView.swift
@@ -7,7 +7,8 @@ extension View {
}
struct ChatView: View {
- @Environment(HotlineClient.self) private var hotline
+// @Environment(HotlineClient.self) private var hotline
+ @Environment(Hotline.self) private var model: Hotline
@Environment(\.colorScheme) var colorScheme
@State var input: String = ""
@@ -22,7 +23,7 @@ struct ChatView: View {
ScrollView {
ScrollViewReader { reader in
LazyVStack(alignment: .leading) {
- ForEach(hotline.chatMessages) { msg in
+ ForEach(model.chat) { msg in
if msg.type == .agreement {
VStack(alignment: .leading) {
VStack(alignment: .leading, spacing: 0) {
@@ -32,7 +33,7 @@ struct ChatView: View {
.opacity(0.75)
HStack {
Spacer()
- Text((hotline.server?.name ?? "") + " Server Agreement")
+ Text((model.server?.name ?? "") + " Server Agreement")
.font(.caption)
.fontWeight(.medium)
.opacity(0.4)
@@ -49,10 +50,21 @@ struct ChatView: View {
}
.padding()
}
+ else if msg.type == .status {
+ HStack {
+ Spacer()
+ Text(msg.text)
+ .lineLimit(1)
+ .truncationMode(.middle)
+ .opacity(0.3)
+ Spacer()
+ }
+ .padding()
+ }
else {
HStack(alignment: .firstTextBaseline) {
- if !msg.username.isEmpty {
- Text("**\(msg.username):** \(msg.text)")
+ if let username = msg.username {
+ Text("**\(username):** \(msg.text)")
}
else {
Text(msg.text)
@@ -63,9 +75,9 @@ struct ChatView: View {
.padding()
}
}
- Text("").id(bottomID)
+ EmptyView().id(bottomID)
}
- .onChange(of: hotline.chatMessages.count) {
+ .onChange(of: model.chat.count) {
withAnimation {
reader.scrollTo(bottomID, anchor: .bottom)
}
@@ -92,14 +104,16 @@ struct ChatView: View {
.lineLimit(1...5)
.onSubmit {
if !self.input.isEmpty {
- hotline.sendChat(message: self.input)
+ model.sendChat(self.input)
+// hotline.sendChat(message: self.input)
}
self.input = ""
}
.frame(maxWidth: .infinity)
Button {
if !self.input.isEmpty {
- hotline.sendChat(message: self.input)
+ model.sendChat(self.input)
+// hotline.sendChat(message: self.input)
}
self.input = ""
} label: {
@@ -114,12 +128,12 @@ struct ChatView: View {
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
- Text(hotline.server?.name ?? "")
+ Text(model.server?.name ?? "")
.font(.headline)
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
- hotline.disconnect()
+ model.disconnect()
} label: {
Text(Image(systemName: "xmark.circle.fill"))
.symbolRenderingMode(.hierarchical)
@@ -166,5 +180,5 @@ struct ChatView: View {
#Preview {
ChatView()
- .environment(HotlineClient())
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineNewClient()))
}
diff --git a/Hotline/Views/FilesView.swift b/Hotline/Views/FilesView.swift
index 23cd914..11db93c 100644
--- a/Hotline/Views/FilesView.swift
+++ b/Hotline/Views/FilesView.swift
@@ -2,16 +2,17 @@ import SwiftUI
import UniformTypeIdentifiers
struct FileView: View {
- @Environment(HotlineClient.self) private var hotline
+// @Environment(HotlineClient.self) private var hotline
+ @Environment(Hotline.self) private var model: Hotline
@State var expanded = false
- var file: HotlineFile
+ var file: FileInfo
var body: some View {
if file.isFolder {
DisclosureGroup(isExpanded: $expanded) {
- ForEach(file.files!) { childFile in
+ ForEach(file.children!) { childFile in
FileView(file: childFile)
.frame(height: 44)
}
@@ -27,9 +28,9 @@ struct FileView: View {
}
}
.onChange(of: expanded) {
- print("EXPANDED CHANGED")
-
- hotline.sendGetFileList(path: file.path)
+ Task {
+ await model.getFileList(path: file.path)
+ }
}
}
else {
@@ -48,7 +49,7 @@ struct FileView: View {
static let byteFormatter = ByteCountFormatter()
- private func formattedFileSize(_ fileSize: UInt32) -> String {
+ private func formattedFileSize(_ fileSize: UInt) -> String {
// let bcf = ByteCountFormatter()
FileView.byteFormatter.allowedUnits = [.useAll]
FileView.byteFormatter.countStyle = .file
@@ -83,35 +84,33 @@ struct FileView: View {
}
struct FilesView: View {
- @Environment(HotlineClient.self) private var hotline
+// @Environment(HotlineClient.self) private var hotline
+ @Environment(Hotline.self) private var model: Hotline
@State var initialLoad = false
var body: some View {
NavigationStack {
- List(hotline.fileList) { file in
-// OutlineGroup(hotline.fileList, children: \.files, expanded: $expandedFolders) { file in
+ List(model.files) { file in
FileView(file: file)
.frame(height: 44)
-// }
}
.task {
if !initialLoad {
- hotline.sendGetFileList(path: []) {
- initialLoad = true
- }
+ let _ = await model.getFileList()
+ initialLoad = true
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
- Text(hotline.server?.name ?? "")
+ Text(model.server?.name ?? "")
.font(.headline)
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
- hotline.disconnect()
+ model.disconnect()
} label: {
Text(Image(systemName: "xmark.circle.fill"))
.symbolRenderingMode(.hierarchical)
diff --git a/Hotline/Views/MessageBoardView.swift b/Hotline/Views/MessageBoardView.swift
index 04f51df..473162e 100644
--- a/Hotline/Views/MessageBoardView.swift
+++ b/Hotline/Views/MessageBoardView.swift
@@ -1,8 +1,9 @@
import SwiftUI
struct MessageBoardView: View {
- @Environment(HotlineState.self) private var appState
- @Environment(HotlineClient.self) private var hotline
+ @Environment(Hotline.self) private var model: Hotline
+// @Environment(HotlineState.self) private var appState
+// @Environment(HotlineClient.self) private var hotline
@State private var fetched = false
@@ -11,7 +12,7 @@ struct MessageBoardView: View {
NavigationStack {
ScrollView {
LazyVStack(alignment: .leading) {
- ForEach(hotline.messageBoardMessages, id: \.self) {
+ ForEach(model.messageBoard, id: \.self) {
Text($0)
.lineLimit(100)
.padding()
@@ -23,23 +24,26 @@ struct MessageBoardView: View {
}
.task {
if !fetched {
- hotline.sendGetMessageBoard() {
- fetched = true
- }
+ let _ = await model.getMessageBoard()
+// hotline.sendGetMessageBoard() {
+ fetched = true
+// }
}
}
.refreshable {
- hotline.sendGetMessageBoard()
+ let _ = await model.getMessageBoard()
+// hotline.sendGetMessageBoard()
}
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
- Text(hotline.server?.name ?? "")
+ Text(model.server?.name ?? "")
.font(.headline)
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
- hotline.disconnect()
+ model.disconnect()
+// hotline.disconnect()
} label: {
Text(Image(systemName: "xmark.circle.fill"))
.symbolRenderingMode(.hierarchical)
@@ -65,6 +69,7 @@ struct MessageBoardView: View {
#Preview {
MessageBoardView()
- .environment(HotlineState())
- .environment(HotlineClient())
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineNewClient()))
+// .environment(HotlineState())
+// .environment(HotlineClient())
}
diff --git a/Hotline/Views/ServerView.swift b/Hotline/Views/ServerView.swift
index 221156a..09ab877 100644
--- a/Hotline/Views/ServerView.swift
+++ b/Hotline/Views/ServerView.swift
@@ -1,28 +1,9 @@
import SwiftUI
struct ServerView: View {
- @Environment(HotlineClient.self) private var hotline
+ @Environment(Hotline.self) private var model: Hotline
@Environment(\.colorScheme) var colorScheme
- func connectionStatusTitle(status: HotlineClientStatus) -> String {
- switch(status) {
- case .disconnected:
- return "Disconnected"
- case .connecting:
- return "Connecting"
- case .connected:
- return "Connected"
- case .loggingIn:
- return "Logging In"
- case .loggedIn:
- return "Logged In"
- }
- }
-
- func connectionProgress(status: HotlineClientStatus) -> Double {
- return Double(status.rawValue) / Double(HotlineClientStatus.loggedIn.rawValue)
- }
-
enum Tab {
case chat, users, news, messageBoard, files
}
@@ -35,17 +16,19 @@ struct ServerView: View {
}
.tag(Tab.chat)
- UserListView()
+ UsersView()
.tabItem {
Image(systemName: "person.2")
}
.tag(Tab.users)
- NewsView()
- .tabItem {
- Image(systemName: "newspaper")
- }
- .tag(Tab.news)
+ if let v = model.serverVersion, v >= 150 {
+ NewsView()
+ .tabItem {
+ Image(systemName: "newspaper")
+ }
+ .tag(Tab.news)
+ }
MessageBoardView()
.tabItem {
@@ -65,5 +48,4 @@ struct ServerView: View {
#Preview {
ServerView()
- .environment(HotlineClient())
}
diff --git a/Hotline/Views/TrackerView.swift b/Hotline/Views/TrackerView.swift
index 2d2032e..2fc27c7 100644
--- a/Hotline/Views/TrackerView.swift
+++ b/Hotline/Views/TrackerView.swift
@@ -1,20 +1,172 @@
import SwiftUI
+struct TrackerConnectView: View {
+ @Environment(Hotline.self) private var model: Hotline
+ @Environment(\.dismiss) var dismiss
+ @Environment(\.colorScheme) var colorScheme
+
+ @State private var server: Server?
+ @State private var address = ""
+ @State private var login = ""
+ @State private var password = ""
+ @State private var connecting = false
+
+ func connectionStatusToProgress(status: HotlineNewClientStatus) -> 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 {
+ VStack(alignment: .leading) {
+ if !connecting {
+ TextField("Server Address", text: $address)
+ .keyboardType(.URL)
+ .disableAutocorrection(true)
+ .frame(height: 48)
+ .textFieldStyle(.plain)
+ .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
+ .background {
+ Color.black.cornerRadius(8).blendMode(.overlay)
+ }
+ TextField("Login", text: $login)
+ .disableAutocorrection(true)
+ .frame(height: 48)
+ .textFieldStyle(.plain)
+ .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
+ .background {
+ Color.black.cornerRadius(8).blendMode(.overlay)
+ }
+ SecureField("Password", text: $password)
+ .disableAutocorrection(true)
+ .textFieldStyle(.plain)
+ .frame(height: 48)
+ .padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
+ .background {
+ Color.black.cornerRadius(8).blendMode(.overlay)
+ }
+ }
+ else {
+ ProgressView(value: connectionStatusToProgress(status: model.status))
+ .frame(minHeight: 10)
+ .accentColor(colorScheme == .dark ? .white : .black)
+ }
+
+ Spacer()
+
+ HStack {
+ Button {
+ dismiss()
+ server = nil
+ model.disconnect()
+ } label: {
+ Text("Cancel")
+ }
+ .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)
+ Button {
+ let s = Server(name: address, description: nil, address: address, port: Server.defaultPort, users: 0)
+ server = s
+ connecting = true
+ Task {
+ let loggedIn = await model.login(server: s, login: login, password: password, username: "bolt", iconID: 128)
+ if !loggedIn {
+ connecting = false
+ }
+ }
+ } label: {
+ Text("Connect")
+ }
+ .disabled(connecting)
+ .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)
+ }
+ .padding()
+ }
+ .padding()
+ .onChange(of: model.status) {
+ print("MODEL STATUS CHANGED")
+ if model.server != nil && server != nil && model.server! == server! {
+ if model.status == .loggedIn {
+ dismiss()
+ }
+ else {
+ connecting = (model.status != .disconnected)
+ }
+ }
+ }
+ .toolbar {
+ ToolbarItem(placement: .principal) {
+ Text("Connect to Server")
+ .font(.headline)
+ }
+ }
+// .presentationBackground(.regularMaterial, in: Color(uiColor: .systemGroupedBackground))
+ .presentationBackground {
+ Color.clear
+ .background(Material.regular)
+ }
+ .presentationDetents([.height(300), .large])
+ .presentationDragIndicator(.visible)
+ .presentationCornerRadius(20)
+ // .background(Color(uiColor: .systemGroupedBackground))
+ }
+}
+
struct TrackerView: View {
// @Environment(\.modelContext) private var modelContext
// @Query private var items: [Item]
- @Environment(HotlineClient.self) private var hotline
@Environment(Hotline.self) private var model: Hotline
@Environment(\.colorScheme) var colorScheme
-// @State private var tracker = Tracker(address: "hltracker.com", service: trackerService)
+ // @State private var tracker = Tracker(address: "hltracker.com", service: trackerService)
@State private var servers: [Server] = []
@State private var selectedServer: Server?
- @State var scrollOffset: CGFloat = CGFloat.zero
+ @State private var scrollOffset: CGFloat = CGFloat.zero
@State private var initialLoadComplete = false
+ @State private var refreshing = false
+ @State private var topBarOpacity: Double = 1.0
+ @State private var connectVisible = false
+ @State private var connectDismissed = true
+ @State private var serverVisible = false
func shouldDisplayDescription(server: Server) -> Bool {
guard let desc = server.description else {
@@ -24,7 +176,7 @@ struct TrackerView: View {
return desc.count > 0 && desc != server.name
}
- func connectionStatusToProgress(status: HotlineClientStatus) -> Double {
+ func connectionStatusToProgress(status: HotlineNewClientStatus) -> Double {
switch status {
case .disconnected:
return 0.0
@@ -60,7 +212,8 @@ struct TrackerView: View {
HStack(alignment: .center) {
Spacer()
Button {
- // hotline.disconnect()
+ connectVisible = true
+ connectDismissed = false
} label: {
Text(Image(systemName: "point.3.connected.trianglepath.dotted"))
.symbolRenderingMode(.hierarchical)
@@ -68,17 +221,22 @@ struct TrackerView: View {
.font(.title2)
.padding(.trailing, 16)
}
+ .sheet(isPresented: $connectVisible) {
+ connectDismissed = true
+ } content: {
+ TrackerConnectView()
+ }
}
.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()
}
+ .opacity(inverseLerp(lower: -50, upper: 0, v: scrollOffset))
+ .opacity(scrollOffset > 65 ? 0.0 : 1.0)
+ .opacity(topBarOpacity)
+ .zIndex(scrollOffset > 0 ? 1 : 3)
ObservableScrollView(scrollOffset: $scrollOffset) {
LazyVStack(alignment: .leading) {
ForEach(self.servers) { server in
@@ -102,14 +260,16 @@ struct TrackerView: View {
if server == selectedServer {
Spacer(minLength: 16)
- if hotline.connectionStatus != .disconnected && hotline.server! == server {
- ProgressView(value: connectionStatusToProgress(status: hotline.connectionStatus))
+ if model.status != .disconnected && model.server != nil && model.server! == server {
+ ProgressView(value: connectionStatusToProgress(status: model.status))
.frame(minHeight: 10)
.accentColor(colorScheme == .dark ? .white : .black)
}
else {
Button("Connect") {
- hotline.connect(to: HotlineServer(address: server.address, port: UInt16(server.port), users: UInt16(server.users), name: server.name, description: server.description))
+ Task {
+ await model.login(server: server, login: "", password: "", username: "bolt", iconID: 128)
+ }
}
.bold()
.padding(EdgeInsets(top: 16, leading: 24, bottom: 16, trailing: 24))
@@ -119,7 +279,7 @@ struct TrackerView: View {
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)
+ 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)
@@ -143,6 +303,7 @@ struct TrackerView: View {
}
.padding(EdgeInsets(top: 75, leading: 0, bottom: 0, trailing: 0))
}
+ .zIndex(2)
.overlay {
if !initialLoadComplete {
VStack {
@@ -153,29 +314,44 @@ struct TrackerView: View {
}
}
.refreshable {
- initialLoadComplete = true
+ DispatchQueue.main.async {
+ withAnimation(.easeOut(duration: 0.1)) {
+ topBarOpacity = 0.0
+ }
+ initialLoadComplete = true
+ }
+
+ model.disconnectTracker()
await updateServers()
+
+ DispatchQueue.main.async {
+ withAnimation(.easeOut(duration: 1.0).delay(0.75)) {
+ topBarOpacity = 1.0
+ }
+ }
}
+
}
- .fullScreenCover(isPresented: Binding(get: { return hotline.connectionStatus == .loggedIn }, set: { _ in }), onDismiss: {
- hotline.disconnect()
+ .fullScreenCover(isPresented: Binding(get: { return (connectDismissed && serverVisible) }, set: { _ in }), onDismiss: {
+ model.disconnect()
}) {
ServerView()
}
+ .onChange(of: model.status) {
+ serverVisible = (model.status == .loggedIn)
+ }
.background(Color(uiColor: UIColor.systemGroupedBackground))
.frame(maxWidth: .infinity)
.task {
await updateServers()
initialLoadComplete = true
-// tracker.fetch()
}
}
}
#Preview {
TrackerView()
- .environment(HotlineClient())
- .environment(HotlineState())
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineNewClient()))
// .modelContainer(for: Item.self, inMemory: true)
}
diff --git a/Hotline/Views/UserListView.swift b/Hotline/Views/UsersView.swift
index 8ea186e..0397ef7 100644
--- a/Hotline/Views/UserListView.swift
+++ b/Hotline/Views/UsersView.swift
@@ -1,28 +1,28 @@
import SwiftUI
-struct UserListView: View {
- @Environment(HotlineClient.self) private var hotline
+struct UsersView: View {
+ @Environment(Hotline.self) private var model: Hotline
var body: some View {
NavigationStack {
- List(hotline.userList) { u in
+ List(model.users) { 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)
+ .foregroundStyle(u.status.contains(.admin) ? Color(hex: 0xE10000) : Color.accentColor)
+ .opacity(u.status.contains(.idle) ? 0.5 : 1.0)
}
// .listStyle(.grouped)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .principal) {
- Text(hotline.server?.name ?? "")
+ Text(model.server?.name ?? "")
.font(.headline)
}
ToolbarItem(placement: .navigationBarLeading) {
Button {
- hotline.disconnect()
+ model.disconnect()
} label: {
Text(Image(systemName: "xmark.circle.fill"))
.symbolRenderingMode(.hierarchical)
@@ -37,5 +37,5 @@ struct UserListView: View {
#Preview {
ChatView()
- .environment(HotlineClient())
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineNewClient()))
}