diff options
| -rw-r--r-- | Hotline.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | Hotline/Application.swift | 12 | ||||
| -rw-r--r-- | Hotline/Utility/TextDocument.swift | 29 | ||||
| -rw-r--r-- | Hotline/macOS/ChatView.swift | 85 | ||||
| -rw-r--r-- | Hotline/macOS/ServerView.swift | 42 |
5 files changed, 115 insertions, 57 deletions
diff --git a/Hotline.xcodeproj/project.pbxproj b/Hotline.xcodeproj/project.pbxproj index fb6cc35..f0e6116 100644 --- a/Hotline.xcodeproj/project.pbxproj +++ b/Hotline.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ DA4F2C012B1A558E00D8ADDC /* ChatView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F2C002B1A558E00D8ADDC /* ChatView.swift */; platformFilter = ios; }; DA5753682B33E88A00FAC277 /* HotlineFileClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5753672B33E88A00FAC277 /* HotlineFileClient.swift */; }; DA57536A2B34A60D00FAC277 /* FileImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA5753692B34A60D00FAC277 /* FileImageView.swift */; }; + DA57536C2B36BA1D00FAC277 /* TextDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA57536B2B36BA1D00FAC277 /* TextDocument.swift */; }; DA6300972B24036B0034CBFD /* HotlineClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6300962B24036B0034CBFD /* HotlineClient.swift */; }; DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA77253E2B21176D006C5ABB /* NewsView.swift */; platformFilter = ios; }; DA7725412B21435B006C5ABB /* ObservableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA7725402B21435B006C5ABB /* ObservableScrollView.swift */; }; @@ -57,6 +58,7 @@ DA4F2C002B1A558E00D8ADDC /* ChatView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatView.swift; sourceTree = "<group>"; }; DA5753672B33E88A00FAC277 /* HotlineFileClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineFileClient.swift; sourceTree = "<group>"; }; DA5753692B34A60D00FAC277 /* FileImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileImageView.swift; sourceTree = "<group>"; }; + DA57536B2B36BA1D00FAC277 /* TextDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDocument.swift; sourceTree = "<group>"; }; DA6300962B24036B0034CBFD /* HotlineClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlineClient.swift; sourceTree = "<group>"; }; DA77253E2B21176D006C5ABB /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; }; DA7725402B21435B006C5ABB /* ObservableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObservableScrollView.swift; sourceTree = "<group>"; }; @@ -166,6 +168,7 @@ DA7725402B21435B006C5ABB /* ObservableScrollView.swift */, DAE735062B3251B3000C56F6 /* Utilities.swift */, DAE735082B329810000C56F6 /* VisualEffectView.swift */, + DA57536B2B36BA1D00FAC277 /* TextDocument.swift */, ); path = Utility; sourceTree = "<group>"; @@ -291,6 +294,7 @@ DAE735072B3251B3000C56F6 /* Utilities.swift in Sources */, DAE735012B2E71F2000C56F6 /* FilesView.swift in Sources */, DABFCC292B1530DC009F40D2 /* FoundationExtensions.swift in Sources */, + DA57536C2B36BA1D00FAC277 /* TextDocument.swift in Sources */, DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */, DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */, DA5753682B33E88A00FAC277 /* HotlineFileClient.swift in Sources */, diff --git a/Hotline/Application.swift b/Hotline/Application.swift index 2e35e3c..65833f1 100644 --- a/Hotline/Application.swift +++ b/Hotline/Application.swift @@ -36,6 +36,18 @@ struct Application: App { ServerView(server: s) .frame(minWidth: 400, minHeight: 300) .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())) + .toolbar { + ToolbarItem(placement: .navigation) { + Image(systemName: "globe.americas.fill") + .resizable() + .scaledToFit() +// .foregroundColor(.secondary) + .frame(width: 22) +// .fontWeight(.light) +// Text("") +// .font(.system(size: 22, weight: .ultraLight)) + } + } } } .defaultSize(width: 700, height: 800) diff --git a/Hotline/Utility/TextDocument.swift b/Hotline/Utility/TextDocument.swift new file mode 100644 index 0000000..3904a21 --- /dev/null +++ b/Hotline/Utility/TextDocument.swift @@ -0,0 +1,29 @@ +import Foundation +import UniformTypeIdentifiers +import SwiftUI + +struct TextFile: FileDocument { + // tell the system we support only plain text + static var readableContentTypes = [UTType.plainText, UTType.utf8PlainText] + + // by default our document is empty + var text = "" + + // a simple initializer that creates new, empty documents + init(initialText: String = "") { + text = initialText + } + + // this initializer loads data that has been saved previously + init(configuration: ReadConfiguration) throws { + if let data = configuration.file.regularFileContents { + text = String(decoding: data, as: UTF8.self) + } + } + + // this will be called when the system wants to write our data to disk + func fileWrapper(configuration: WriteConfiguration) throws -> FileWrapper { + let data = Data(text.utf8) + return FileWrapper(regularFileWithContents: data) + } +} diff --git a/Hotline/macOS/ChatView.swift b/Hotline/macOS/ChatView.swift index 2b6d10b..618fd12 100644 --- a/Hotline/macOS/ChatView.swift +++ b/Hotline/macOS/ChatView.swift @@ -16,6 +16,10 @@ struct ChatView: View { @FocusState private var focusedField: FocusedField? @Namespace var bottomID + + @State private var showingExporter: Bool = false + + @State private var chatDocument: TextFile = TextFile() var body: some View { NavigationStack { @@ -32,13 +36,13 @@ struct ChatView: View { // MARK: Agreement if msg.type == .agreement { - VStack(alignment: .center) { + VStack(alignment: .center, spacing: 16) { FileImageView() .environment(self.model) .frame(maxWidth: 468.0) - .clipShape(RoundedRectangle(cornerRadius: 8)) - + .clipShape(RoundedRectangle(cornerRadius: 3)) + VStack(spacing: 0) { ScrollView(.vertical) { HStack { @@ -52,28 +56,10 @@ struct ChatView: View { } .scrollBounceBehavior(.basedOnSize) .frame(maxHeight: 375) - -// Divider() -// -// HStack { -// Button("Disagree") { -// dismiss() -// } -// .controlSize(.large) -// -// Spacer() -// -// Button("Agree") { -// model.sendAgree() -// } -// .controlSize(.large) -// .keyboardShortcut(.defaultAction) -// } -// .padding(16) } .background(Color(white: colorScheme == .light ? 0.0 : 1.0).opacity(0.05)) .frame(maxWidth: 468.0) - .clipShape(RoundedRectangle(cornerRadius: 8)) + .clipShape(RoundedRectangle(cornerRadius: 3)) } .frame(maxWidth: .infinity) .padding() @@ -177,6 +163,61 @@ struct ChatView: View { .navigationTitle(self.model.serverTitle) .navigationSubtitle(self.model.users.count > 0 ? "^[\(self.model.users.count) user](inflect: true) online" : "") .background(Color(nsColor: .textBackgroundColor)) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button { + if prepareChatDocument() { + showingExporter = true + } + } label: { + Image(systemName: "square.and.arrow.up") + }.help("Save Chat") + } + } + .fileExporter(isPresented: $showingExporter, document: chatDocument, contentType: .utf8PlainText, defaultFilename: "\(model.serverTitle) Chat") { result in +// switch result { +// case .success(let url): +// print("Saved to \(url)") +// +// case .failure(let error): +// print(error.localizedDescription) +// } + self.chatDocument.text = "" + } + } + + private func prepareChatDocument() -> Bool { + var text: String = String() + + self.chatDocument.text = "" + + for msg in model.chat { + if msg.type == .agreement { + text.append(msg.text) + text.append("\n\n") + } + else if msg.type == .message { + if let username = msg.username { + text.append("\(username): \(msg.text)") + } + else { + text.append(msg.text) + } + text.append("\n") + } + else if msg.type == .status { + text.append(msg.text) + text.append("\n") + } + } + + if text.isEmpty { + return false + } + + self.chatDocument.text = text + + return true } } diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift index 7476b3c..7ad344e 100644 --- a/Hotline/macOS/ServerView.swift +++ b/Hotline/macOS/ServerView.swift @@ -131,32 +131,6 @@ struct ServerView: View { var body: some View { NavigationSplitView { List(selection: $selection) { - -// VStack(spacing: 0) { -// bannerImage -// .resizable() -// .aspectRatio(contentMode: .fit) -// .frame(maxWidth: .infinity, minHeight: 60, alignment: .topLeading) -// .clipped() -// } -// .frame(maxWidth: .infinity) -// } -// else { -// HStack { -// Text(server.name ?? "") -// .fontWeight(.medium) -// .lineLimit(2) -// .font(.title3) -// .multilineTextAlignment(.center) -// .padding() -// } -// .selectionDisabled() -// .frame(maxWidth: .infinity, minHeight: 60) -// .background(VisualEffectView(material: .titlebar, blendingMode: .withinWindow).cornerRadius(16)) -// .cornerRadius(10) -// .tag(MenuItem(name: "title", image: "", type: .banner)) -// .padding(.bottom, 16) -// } if model.status != .loggedIn { HStack { @@ -169,19 +143,17 @@ struct ServerView: View { } if model.status == .loggedIn { - Section(model.serverTitle) { - ForEach(ServerView.menuItems) { menuItem in - if let minServerVersion = menuItem.serverVersion { - if let v = model.serverVersion, v >= minServerVersion { - ListItemView(icon: menuItem.image, title: menuItem.name) - .tag(menuItem) - } - } - else { + ForEach(ServerView.menuItems) { menuItem in + if let minServerVersion = menuItem.serverVersion { + if let v = model.serverVersion, v >= minServerVersion { ListItemView(icon: menuItem.image, title: menuItem.name) .tag(menuItem) } } + else { + ListItemView(icon: menuItem.image, title: menuItem.name) + .tag(menuItem) + } } if model.users.count > 0 { |