aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Hotline.xcodeproj/project.pbxproj42
-rw-r--r--Hotline/Application-macOS.swift (renamed from Hotline/Application.swift)109
-rw-r--r--Hotline/Assets.xcassets/Default Banner.imageset/Contents.json23
-rw-r--r--Hotline/Assets.xcassets/Default Banner.imageset/Frame 2.pngbin0 -> 59733 bytes
-rw-r--r--Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@2x.pngbin0 -> 207508 bytes
-rw-r--r--Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@3x.pngbin0 -> 386312 bytes
-rw-r--r--Hotline/Models/ApplicationState.swift14
-rw-r--r--Hotline/Models/Hotline.swift9
-rw-r--r--Hotline/Shared/HotlinePanel.swift48
-rw-r--r--Hotline/Shared/VisualEffectView.swift (renamed from Hotline/Utility/VisualEffectView.swift)17
-rw-r--r--Hotline/Sounds/Application-iOS.swift26
-rw-r--r--Hotline/Utility/SoundEffects.swift25
-rw-r--r--Hotline/macOS/ChatView.swift17
-rw-r--r--Hotline/macOS/HotlinePanelView.swift116
-rw-r--r--Hotline/macOS/ServerView.swift8
15 files changed, 395 insertions, 59 deletions
diff --git a/Hotline.xcodeproj/project.pbxproj b/Hotline.xcodeproj/project.pbxproj
index 38c6cad..4ab9b1f 100644
--- a/Hotline.xcodeproj/project.pbxproj
+++ b/Hotline.xcodeproj/project.pbxproj
@@ -31,7 +31,9 @@
DA72A0E22B4DAA4000A0F48A /* NewsArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA72A0E12B4DAA4000A0F48A /* NewsArticle.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 */; };
- DA9CAFBB2B126D5700CDA197 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFBA2B126D5700CDA197 /* Application.swift */; };
+ DA872B132BDDBF78008B1012 /* HotlinePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA872B122BDDBF78008B1012 /* HotlinePanel.swift */; };
+ DA872B152BDDEE1A008B1012 /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA872B142BDDEE1A008B1012 /* VisualEffectView.swift */; };
+ DA9CAFBB2B126D5700CDA197 /* Application-macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFBA2B126D5700CDA197 /* Application-macOS.swift */; };
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA9CAFBC2B126D5700CDA197 /* TrackerView.swift */; platformFilter = ios; };
DA9CAFC12B126D5800CDA197 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA9CAFC02B126D5800CDA197 /* Assets.xcassets */; };
DA9CAFC42B126D5800CDA197 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA9CAFC32B126D5800CDA197 /* Preview Assets.xcassets */; };
@@ -57,9 +59,12 @@
DABE8C062B57A06100884D28 /* Bookmarks.swift in Sources */ = {isa = PBXBuildFile; fileRef = DABE8C052B57A06100884D28 /* Bookmarks.swift */; };
DABFCC292B1530DC009F40D2 /* FoundationExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DABFCC282B1530DC009F40D2 /* FoundationExtensions.swift */; };
DAC002192B21630900A6C290 /* SwiftUIExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC002182B21630900A6C290 /* SwiftUIExtensions.swift */; };
+ DAC3D97E2BC0F1ED00A727C9 /* Application-iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC3D97D2BC0F1ED00A727C9 /* Application-iOS.swift */; };
+ DAC3D9832BC33FD000A727C9 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAC3D9822BC33FD000A727C9 /* ApplicationState.swift */; };
DADDB28B2B22B31F0024040D /* Tracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADDB28A2B22B31F0024040D /* Tracker.swift */; };
DADDB28D2B22B5920024040D /* Server.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADDB28C2B22B5920024040D /* Server.swift */; };
DADDB28F2B238D850024040D /* Hotline.swift in Sources */ = {isa = PBXBuildFile; fileRef = DADDB28E2B238D850024040D /* Hotline.swift */; };
+ DAE136BA2B9D1147007D8307 /* HotlinePanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE136B92B9D1147007D8307 /* HotlinePanelView.swift */; };
DAE734F92B2E4185000C56F6 /* ServerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE734F82B2E4185000C56F6 /* ServerView.swift */; platformFilters = (macos, ); };
DAE734FB2B2E41F9000C56F6 /* TrackerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE734FA2B2E41F9000C56F6 /* TrackerView.swift */; platformFilters = (macos, ); };
DAE734FD2B2E65E9000C56F6 /* MessageBoardView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE734FC2B2E65E9000C56F6 /* MessageBoardView.swift */; platformFilters = (macos, ); };
@@ -68,7 +73,6 @@
DAE735032B30C0BB000C56F6 /* MessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE735022B30C0BB000C56F6 /* MessageView.swift */; platformFilters = (macos, ); };
DAE735052B3218D8000C56F6 /* NewsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE735042B3218D8000C56F6 /* NewsView.swift */; platformFilters = (macos, ); };
DAE735072B3251B3000C56F6 /* SoundEffects.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE735062B3251B3000C56F6 /* SoundEffects.swift */; };
- DAE735092B329810000C56F6 /* VisualEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE735082B329810000C56F6 /* VisualEffectView.swift */; platformFilters = (macos, ); };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
@@ -97,8 +101,10 @@
DA72A0E12B4DAA4000A0F48A /* NewsArticle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsArticle.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>"; };
+ DA872B122BDDBF78008B1012 /* HotlinePanel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HotlinePanel.swift; sourceTree = "<group>"; };
+ DA872B142BDDEE1A008B1012 /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = "<group>"; };
DA9CAFB72B126D5700CDA197 /* Hotline.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Hotline.app; sourceTree = BUILT_PRODUCTS_DIR; };
- DA9CAFBA2B126D5700CDA197 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
+ DA9CAFBA2B126D5700CDA197 /* Application-macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Application-macOS.swift"; sourceTree = "<group>"; };
DA9CAFBC2B126D5700CDA197 /* TrackerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerView.swift; sourceTree = "<group>"; };
DA9CAFC02B126D5800CDA197 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
DA9CAFC32B126D5800CDA197 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
@@ -123,9 +129,12 @@
DABE8C052B57A06100884D28 /* Bookmarks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmarks.swift; sourceTree = "<group>"; };
DABFCC282B1530DC009F40D2 /* FoundationExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FoundationExtensions.swift; sourceTree = "<group>"; };
DAC002182B21630900A6C290 /* SwiftUIExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIExtensions.swift; sourceTree = "<group>"; };
+ DAC3D97D2BC0F1ED00A727C9 /* Application-iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Application-iOS.swift"; path = "Sounds/Application-iOS.swift"; sourceTree = "<group>"; };
+ DAC3D9822BC33FD000A727C9 /* ApplicationState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = "<group>"; };
DADDB28A2B22B31F0024040D /* Tracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tracker.swift; sourceTree = "<group>"; };
DADDB28C2B22B5920024040D /* Server.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Server.swift; sourceTree = "<group>"; };
DADDB28E2B238D850024040D /* Hotline.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Hotline.swift; sourceTree = "<group>"; };
+ DAE136B92B9D1147007D8307 /* HotlinePanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotlinePanelView.swift; sourceTree = "<group>"; };
DAE734F82B2E4185000C56F6 /* ServerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerView.swift; sourceTree = "<group>"; };
DAE734FA2B2E41F9000C56F6 /* TrackerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackerView.swift; sourceTree = "<group>"; };
DAE734FC2B2E65E9000C56F6 /* MessageBoardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBoardView.swift; sourceTree = "<group>"; };
@@ -134,7 +143,6 @@
DAE735022B30C0BB000C56F6 /* MessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageView.swift; sourceTree = "<group>"; };
DAE735042B3218D8000C56F6 /* NewsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewsView.swift; sourceTree = "<group>"; };
DAE735062B3251B3000C56F6 /* SoundEffects.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoundEffects.swift; sourceTree = "<group>"; };
- DAE735082B329810000C56F6 /* VisualEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualEffectView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -183,10 +191,12 @@
DA9CAFB92B126D5700CDA197 /* Hotline */ = {
isa = PBXGroup;
children = (
+ DA9CAFBA2B126D5700CDA197 /* Application-macOS.swift */,
+ DAC3D97D2BC0F1ED00A727C9 /* Application-iOS.swift */,
DABE8BF22B55DBFC00884D28 /* Sounds */,
- DA9CAFBA2B126D5700CDA197 /* Application.swift */,
DA4F2BF92B1A516F00D8ADDC /* iOS */,
DAE734F72B2E4126000C56F6 /* macOS */,
+ DAC3D9812BC33F4400A727C9 /* Managers */,
DADDB2892B22B2C60024040D /* Models */,
DABFCC262B1530AE009F40D2 /* Hotline */,
DAB4D87C2B4B783A0048A05C /* Shared */,
@@ -215,6 +225,8 @@
DAB4D8832B4CABEF0048A05C /* DataAdditions.swift */,
DAB4D87F2B4C8E9A0048A05C /* URLAdditions.swift */,
DA4930BC2B4F8DD700822D0B /* NetSocket.swift */,
+ DA872B122BDDBF78008B1012 /* HotlinePanel.swift */,
+ DA872B142BDDEE1A008B1012 /* VisualEffectView.swift */,
);
path = Shared;
sourceTree = "<group>";
@@ -252,18 +264,25 @@
DAC002182B21630900A6C290 /* SwiftUIExtensions.swift */,
DA7725402B21435B006C5ABB /* ObservableScrollView.swift */,
DAE735062B3251B3000C56F6 /* SoundEffects.swift */,
- DAE735082B329810000C56F6 /* VisualEffectView.swift */,
DA57536B2B36BA1D00FAC277 /* TextDocument.swift */,
DABE8C032B57940A00884D28 /* DAKeychain.swift */,
);
path = Utility;
sourceTree = "<group>";
};
+ DAC3D9812BC33F4400A727C9 /* Managers */ = {
+ isa = PBXGroup;
+ children = (
+ );
+ path = Managers;
+ sourceTree = "<group>";
+ };
DADDB2892B22B2C60024040D /* Models */ = {
isa = PBXGroup;
children = (
11A726072BE0672A000C1DA7 /* FileDetails.swift */,
DADDB28A2B22B31F0024040D /* Tracker.swift */,
+ DAC3D9822BC33FD000A727C9 /* ApplicationState.swift */,
DABE8C052B57A06100884D28 /* Bookmarks.swift */,
DA2863D92B37BF6E00A7D050 /* Preferences.swift */,
DADDB28E2B238D850024040D /* Hotline.swift */,
@@ -295,6 +314,7 @@
DAB4D87D2B4C8BCA0048A05C /* FilePreviewTextView.swift */,
DA72A0DC2B4CD0BF00A0F48A /* NewsEditorView.swift */,
DA2863D72B37AD1C00A7D050 /* SettingsView.swift */,
+ DAE136B92B9D1147007D8307 /* HotlinePanelView.swift */,
);
path = macOS;
sourceTree = "<group>";
@@ -389,10 +409,11 @@
DA6300972B24036B0034CBFD /* HotlineClient.swift in Sources */,
DA43205E2B1D615600FC8843 /* ServerView.swift in Sources */,
DA0D69912B1E894800C71DF5 /* FilesView.swift in Sources */,
- DAE735092B329810000C56F6 /* VisualEffectView.swift in Sources */,
+ DAC3D9832BC33FD000A727C9 /* ApplicationState.swift in Sources */,
DAB4D8802B4C8E9A0048A05C /* URLAdditions.swift in Sources */,
DA2863DD2B3E8B7000A7D050 /* FilePreview.swift in Sources */,
DA2863D82B37AD1C00A7D050 /* SettingsView.swift in Sources */,
+ DAC3D97E2BC0F1ED00A727C9 /* Application-iOS.swift in Sources */,
DA7725412B21435B006C5ABB /* ObservableScrollView.swift in Sources */,
DAE734FF2B2E6750000C56F6 /* ChatView.swift in Sources */,
DA4930BD2B4F8DD700822D0B /* NetSocket.swift in Sources */,
@@ -402,6 +423,7 @@
DABE8C042B57940B00884D28 /* DAKeychain.swift in Sources */,
DA72A0E22B4DAA4000A0F48A /* NewsArticle.swift in Sources */,
DAE734FB2B2E41F9000C56F6 /* TrackerView.swift in Sources */,
+ DAE136BA2B9D1147007D8307 /* HotlinePanelView.swift in Sources */,
11A726082BE0672A000C1DA7 /* FileDetails.swift in Sources */,
DA9CAFBD2B126D5700CDA197 /* TrackerView.swift in Sources */,
DAB4D87B2B4B78310048A05C /* AnimatedImageView.swift in Sources */,
@@ -411,7 +433,7 @@
DA9CAFCB2B126E3300CDA197 /* HotlineTrackerClient.swift in Sources */,
DA2863DA2B37BF6E00A7D050 /* Preferences.swift in Sources */,
DA32CD4F2B2931CC0053B98B /* NewsInfo.swift in Sources */,
- DA9CAFBB2B126D5700CDA197 /* Application.swift in Sources */,
+ DA9CAFBB2B126D5700CDA197 /* Application-macOS.swift in Sources */,
DAAEE66B2B3FBC2100A5BA07 /* TransferInfo.swift in Sources */,
DA0D698F2B1E841600C71DF5 /* MessageBoardView.swift in Sources */,
DAE735072B3251B3000C56F6 /* SoundEffects.swift in Sources */,
@@ -421,10 +443,12 @@
DABFCC292B1530DC009F40D2 /* FoundationExtensions.swift in Sources */,
DA57536C2B36BA1D00FAC277 /* TextDocument.swift in Sources */,
DA77253F2B21176D006C5ABB /* NewsView.swift in Sources */,
+ DA872B132BDDBF78008B1012 /* HotlinePanel.swift in Sources */,
DA4F2BF82B16A17200D8ADDC /* HotlineProtocol.swift in Sources */,
DA5753682B33E88A00FAC277 /* HotlineFileClient.swift in Sources */,
DAB4D8822B4C8FED0048A05C /* FileIconView.swift in Sources */,
DAAEE66F2B47625600A5BA07 /* FilePreviewImageView.swift in Sources */,
+ DA872B152BDDEE1A008B1012 /* VisualEffectView.swift in Sources */,
DAAEE66D2B475F1400A5BA07 /* PreviewFileInfo.swift in Sources */,
DAB4D8842B4CABEF0048A05C /* DataAdditions.swift in Sources */,
DA0D698D2B1E7CF700C71DF5 /* UsersView.swift in Sources */,
@@ -587,6 +611,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
+ MACOSX_DEPLOYMENT_TARGET = 14.3;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = co.goodmake.hotline;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -626,6 +651,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
+ MACOSX_DEPLOYMENT_TARGET = 14.3;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = co.goodmake.hotline;
PRODUCT_NAME = "$(TARGET_NAME)";
diff --git a/Hotline/Application.swift b/Hotline/Application-macOS.swift
index bf484ba..34fec3e 100644
--- a/Hotline/Application.swift
+++ b/Hotline/Application-macOS.swift
@@ -2,31 +2,48 @@ import SwiftUI
import SwiftData
import UniformTypeIdentifiers
+#if os(macOS)
+
+@Observable
+class AppLaunchState {
+ static let shared = AppLaunchState()
+
+ enum LaunchState {
+ case loading
+ case launched
+ case terminated
+ }
+
+ var launchState = LaunchState.loading
+}
+
+class AppDelegate: NSObject, NSApplicationDelegate {
+ func applicationDidFinishLaunching(_ notification: Notification) {
+ AppLaunchState.shared.launchState = .launched
+ }
+
+ func applicationWillTerminate(_ notification: Notification) {
+ AppLaunchState.shared.launchState = .terminated
+ }
+}
+
@main
struct Application: App {
- #if os(iOS)
- private var model = Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())
- #endif
-
- #if os(macOS)
+ @Environment(\.scenePhase) private var scenePhase
@Environment(\.openWindow) private var openWindow
@Environment(\.openURL) private var openURL
- #endif
+
+ @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
@State private var preferences = Prefs()
@State private var soundEffects = SoundEffectPlayer()
@State private var bookmarks = Bookmarks()
-
+ @State private var hotlinePanel: HotlinePanel? = nil
+
@FocusedValue(\.activeHotlineModel) private var activeHotline: Hotline?
@FocusedValue(\.activeServerState) private var activeServerState: ServerState?
-
+
var body: some Scene {
- #if os(iOS)
- WindowGroup {
- TrackerView()
- .environment(model)
- }
- #elseif os(macOS)
// MARK: Tracker Window
Window("Servers", id: "servers") {
TrackerView()
@@ -36,7 +53,12 @@ struct Application: App {
.keyboardShortcut(.init(.init("R"), modifiers: .command))
.defaultSize(width: 700, height: 550)
.defaultPosition(.center)
-
+ .onChange(of: AppLaunchState.shared.launchState) {
+ if AppLaunchState.shared.launchState == .launched {
+ showBannerWindow()
+ }
+ }
+
// MARK: Server Window
WindowGroup(id: "server", for: Server.self) { server in
ServerView(server: server)
@@ -49,13 +71,41 @@ struct Application: App {
}
.defaultSize(width: 750, height: 700)
.defaultPosition(.center)
+ .onChange(of: activeServerState) {
+ ApplicationState.shared.activeServerState = activeServerState
+ }
+ .onChange(of: activeHotline) {
+ ApplicationState.shared.activeHotline = activeHotline
+ }
+ .onChange(of: activeHotline?.serverTitle) {
+ if let hotline = activeHotline {
+ ApplicationState.shared.activeServerName = hotline.serverTitle
+ }
+ }
+ .onChange(of: activeHotline?.bannerImage) {
+ withAnimation {
+ ApplicationState.shared.activeServerBanner = activeHotline?.bannerImage
+ }
+ }
+ .onChange(of: activeHotline) {
+ ApplicationState.shared.activeHotline = activeHotline
+ if let hotline = activeHotline {
+ ApplicationState.shared.activeServerName = hotline.serverTitle
+ }
+ }
.commands {
- CommandGroup(replacing: CommandGroupPlacement.newItem) {
+ CommandGroup(replacing: .newItem) {
Button("Connect to Server...") {
openWindow(id: "server")
}
.keyboardShortcut(.init("K"), modifiers: .command)
}
+ CommandGroup(after: .singleWindowList) {
+ Button("Toolbar") {
+ toggleBannerWindow()
+ }
+ .keyboardShortcut(.init("\\"), modifiers: [.shift, .command])
+ }
CommandGroup(after: .help) {
Divider()
Button("Request Feature...") {
@@ -139,7 +189,30 @@ struct Application: App {
.windowToolbarStyle(.unifiedCompact(showsTitle: true))
.defaultSize(width: 450, height: 550)
.defaultPosition(.center)
-
- #endif
+ }
+
+ func showBannerWindow() {
+ if hotlinePanel == nil {
+ hotlinePanel = HotlinePanel(HotlinePanelView())
+ }
+
+ if hotlinePanel?.isVisible == false {
+ hotlinePanel?.orderFront(nil)
+ }
+ }
+
+ func toggleBannerWindow() {
+ if hotlinePanel == nil {
+ hotlinePanel = HotlinePanel(HotlinePanelView())
+ }
+
+ if hotlinePanel?.isVisible == true {
+ hotlinePanel?.orderOut(nil)
+ }
+ else {
+ hotlinePanel?.orderFront(nil)
+ }
}
}
+
+#endif
diff --git a/Hotline/Assets.xcassets/Default Banner.imageset/Contents.json b/Hotline/Assets.xcassets/Default Banner.imageset/Contents.json
new file mode 100644
index 0000000..1abbabf
--- /dev/null
+++ b/Hotline/Assets.xcassets/Default Banner.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "filename" : "Frame 2.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "filename" : "Frame 2@2x.png",
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "filename" : "Frame 2@3x.png",
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2.png b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2.png
new file mode 100644
index 0000000..fae0905
--- /dev/null
+++ b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2.png
Binary files differ
diff --git a/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@2x.png b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@2x.png
new file mode 100644
index 0000000..163e634
--- /dev/null
+++ b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@2x.png
Binary files differ
diff --git a/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@3x.png b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@3x.png
new file mode 100644
index 0000000..3f2ef8f
--- /dev/null
+++ b/Hotline/Assets.xcassets/Default Banner.imageset/Frame 2@3x.png
Binary files differ
diff --git a/Hotline/Models/ApplicationState.swift b/Hotline/Models/ApplicationState.swift
new file mode 100644
index 0000000..124db6b
--- /dev/null
+++ b/Hotline/Models/ApplicationState.swift
@@ -0,0 +1,14 @@
+import SwiftUI
+
+@Observable
+class ApplicationState {
+ static let shared = ApplicationState()
+
+ var activeHotline: Hotline? = nil
+ var activeServerState: ServerState? = nil
+
+ // Frontmost server window information
+ var activeServerID: UUID? = nil
+ var activeServerBanner: NSImage? = nil
+ var activeServerName: String? = nil
+}
diff --git a/Hotline/Models/Hotline.swift b/Hotline/Models/Hotline.swift
index f1c745f..3cc8366 100644
--- a/Hotline/Models/Hotline.swift
+++ b/Hotline/Models/Hotline.swift
@@ -1,10 +1,14 @@
import SwiftUI
@Observable
-final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate {
+class Hotline: Equatable, HotlineClientDelegate, HotlineFileClientDelegate {
+ let id: UUID = UUID()
let trackerClient: HotlineTrackerClient
let client: HotlineClient
- let soundEffects: SoundEffectPlayer = SoundEffectPlayer()
+
+ static func == (lhs: Hotline, rhs: Hotline) -> Bool {
+ return lhs.id == rhs.id
+ }
#if os(macOS)
static func getClassicIcon(_ index: Int) -> NSImage? {
@@ -178,7 +182,6 @@ final class Hotline: HotlineClientDelegate, HotlineFileClientDelegate {
self.iconID = iconID
self.client.login(address: server.address, port: server.port, login: server.login, password: server.password, username: username, iconID: UInt16(iconID)) { [weak self] err, serverName, serverVersion in
- print("Server info:", serverName, serverVersion)
self?.serverVersion = serverVersion ?? 123
if serverName != nil {
self?.serverName = serverName
diff --git a/Hotline/Shared/HotlinePanel.swift b/Hotline/Shared/HotlinePanel.swift
new file mode 100644
index 0000000..60d1337
--- /dev/null
+++ b/Hotline/Shared/HotlinePanel.swift
@@ -0,0 +1,48 @@
+import Cocoa
+import SwiftUI
+
+class HotlinePanel: NSPanel {
+ init(_ view: HotlinePanelView) {
+ super.init(contentRect: .zero, styleMask: [.nonactivatingPanel, .titled, .closable, .utilityWindow, .fullSizeContentView], backing: .buffered, defer: false)
+
+ // Make sure that the panel is in front of almost all other windows
+ self.isFloatingPanel = false
+ self.level = .floating
+ self.hidesOnDeactivate = true
+ self.animationBehavior = .utilityWindow
+
+ // Allow the panel to appear in a fullscreen space
+ self.collectionBehavior.insert(.fullScreenAuxiliary)
+
+ // Don't delete panel state when it's closed.
+ self.isReleasedWhenClosed = false
+
+ self.standardWindowButton(.closeButton)?.isHidden = true
+ self.standardWindowButton(.zoomButton)?.isHidden = true
+ self.standardWindowButton(.miniaturizeButton)?.isHidden = true
+
+ // Make it transparent, the view inside will have to set the background.
+ // This is necessary because otherwise, we will have some space for the titlebar on top of the height of the view itself which we don't want.
+ self.isOpaque = false
+ self.backgroundColor = .clear
+
+ // Since we don't show a statusbar, this allows us to drag the window by its background instead of the titlebar.
+ self.isMovableByWindowBackground = true
+ self.titlebarAppearsTransparent = true
+
+ let hostingView = NSHostingView(rootView: view.edgesIgnoringSafeArea(.top))
+ hostingView.sizingOptions = [.standardBounds]
+
+ self.contentView = hostingView
+
+ self.cascadeTopLeft(from: NSMakePoint(16, 16))
+ }
+
+ override var canBecomeKey: Bool {
+ return false
+ }
+
+ override var canBecomeMain: Bool {
+ return false
+ }
+}
diff --git a/Hotline/Utility/VisualEffectView.swift b/Hotline/Shared/VisualEffectView.swift
index 4f72a6e..e595c55 100644
--- a/Hotline/Utility/VisualEffectView.swift
+++ b/Hotline/Shared/VisualEffectView.swift
@@ -1,18 +1,12 @@
import SwiftUI
-public struct VisualEffectView: NSViewRepresentable {
+struct VisualEffectView: NSViewRepresentable
+{
let material: NSVisualEffectView.Material
let blendingMode: NSVisualEffectView.BlendingMode
- public init(
- material: NSVisualEffectView.Material = .contentBackground,
- blendingMode: NSVisualEffectView.BlendingMode = .withinWindow
- ) {
- self.material = material
- self.blendingMode = blendingMode
- }
-
- public func makeNSView(context: Context) -> NSVisualEffectView {
+ func makeNSView(context: Context) -> NSVisualEffectView
+ {
let visualEffectView = NSVisualEffectView()
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
@@ -20,7 +14,8 @@ public struct VisualEffectView: NSViewRepresentable {
return visualEffectView
}
- public func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context) {
+ func updateNSView(_ visualEffectView: NSVisualEffectView, context: Context)
+ {
visualEffectView.material = material
visualEffectView.blendingMode = blendingMode
}
diff --git a/Hotline/Sounds/Application-iOS.swift b/Hotline/Sounds/Application-iOS.swift
new file mode 100644
index 0000000..5d9c56d
--- /dev/null
+++ b/Hotline/Sounds/Application-iOS.swift
@@ -0,0 +1,26 @@
+import SwiftUI
+import SwiftData
+import UniformTypeIdentifiers
+
+#if os(iOS)
+
+@main
+struct Application: App {
+ private var model = Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())
+
+ @State private var preferences = Prefs()
+ @State private var soundEffects = SoundEffectPlayer()
+ @State private var bookmarks = Bookmarks()
+
+ @FocusedValue(\.activeHotlineModel) private var activeHotline: Hotline?
+ @FocusedValue(\.activeServerState) private var activeServerState: ServerState?
+
+ var body: some Scene {
+ WindowGroup {
+ TrackerView()
+ .environment(model)
+ }
+ }
+}
+
+#endif
diff --git a/Hotline/Utility/SoundEffects.swift b/Hotline/Utility/SoundEffects.swift
index 175fa5b..931e27f 100644
--- a/Hotline/Utility/SoundEffects.swift
+++ b/Hotline/Utility/SoundEffects.swift
@@ -12,7 +12,9 @@ enum SoundEffects: String {
@Observable
class SoundEffectPlayer: NSObject, AVAudioPlayerDelegate {
- var activeSounds: [AVAudioPlayer] = []
+ static let shared = SoundEffectPlayer()
+
+ private var activeSounds: [AVAudioPlayer] = []
func playSoundEffect(_ name: SoundEffects) {
// Load a local sound file
@@ -23,18 +25,21 @@ class SoundEffectPlayer: NSObject, AVAudioPlayerDelegate {
return
}
- do {
- let soundEffect = try AVAudioPlayer(contentsOf: soundFileURL)
- soundEffect.delegate = self
-
- self.activeSounds.append(soundEffect)
+ DispatchQueue.main.async { [weak self] in
+ guard let self = self else {
+ return
+ }
- soundEffect.volume = 0.75
- soundEffect.play()
+ if let soundEffect = try? AVAudioPlayer(contentsOf: soundFileURL) {
+ soundEffect.delegate = self
+ soundEffect.volume = 0.75
+ soundEffect.play()
+
+ self.activeSounds.append(soundEffect)
+ }
}
- catch {}
}
-
+
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if let i = self.activeSounds.firstIndex(of: player) {
self.activeSounds.remove(at: i)
diff --git a/Hotline/macOS/ChatView.swift b/Hotline/macOS/ChatView.swift
index 7f7d677..1b82e02 100644
--- a/Hotline/macOS/ChatView.swift
+++ b/Hotline/macOS/ChatView.swift
@@ -38,21 +38,22 @@ struct ChatView: View {
if msg.type == .agreement {
VStack(alignment: .center, spacing: 16) {
+ #if os(iOS)
if let bannerImage = self.model.bannerImage {
- #if os(macOS)
- Image(nsImage: bannerImage)
- .resizable()
- .scaledToFit()
- .frame(maxWidth: 468.0)
- .clipShape(RoundedRectangle(cornerRadius: 3))
- #elseif os(iOS)
+// #if os(macOS)
+// Image(nsImage: bannerImage)
+// .resizable()
+// .scaledToFit()
+// .frame(maxWidth: 468.0)
+// .clipShape(RoundedRectangle(cornerRadius: 3))
+// #elseif os(iOS)
Image(uiImage: bannerImage)
.resizable()
.scaledToFit()
.frame(maxWidth: 468.0)
.clipShape(RoundedRectangle(cornerRadius: 3))
- #endif
}
+ #endif
VStack(spacing: 0) {
ScrollView(.vertical) {
diff --git a/Hotline/macOS/HotlinePanelView.swift b/Hotline/macOS/HotlinePanelView.swift
new file mode 100644
index 0000000..36204a7
--- /dev/null
+++ b/Hotline/macOS/HotlinePanelView.swift
@@ -0,0 +1,116 @@
+import SwiftUI
+
+struct HotlinePanelView: View {
+ @Environment(\.openWindow) var openWindow
+ @Environment(\.colorScheme) var colorScheme
+
+ @FocusedValue(\.activeHotlineModel) private var activeHotline: Hotline?
+ @FocusedValue(\.activeServerState) private var activeServerState: ServerState?
+
+ var body: some View {
+ VStack {
+ Image(nsImage: ApplicationState.shared.activeServerBanner ?? NSImage(named: "Default Banner")!)
+ .interpolation(.high)
+ .resizable()
+ .scaledToFit()
+ .frame(width: 468, height: 60)
+ .clipShape(RoundedRectangle(cornerRadius: 6.0))
+ .padding([.top, .leading, .trailing], 4)
+
+ HStack(spacing: 16) {
+ Button {
+ openWindow(id: "servers")
+ }
+ label: {
+ Image(systemName: "globe.americas.fill")
+ .resizable()
+ .scaledToFit()
+ }
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .help("Hotline Servers")
+
+ Button {
+ ApplicationState.shared.activeServerState?.selection = .chat
+ }
+ label: {
+ Image(systemName: "bubble.fill")
+ .resizable()
+ .scaledToFit()
+ }
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .opacity(ApplicationState.shared.activeServerState == nil ? 0.5 : 1.0)
+ .disabled(ApplicationState.shared.activeServerState == nil)
+ .help("Public Chat")
+
+ Button {
+ ApplicationState.shared.activeServerState?.selection = .news
+ }
+ label: {
+ Image(systemName: "newspaper.fill")
+ .resizable()
+ .scaledToFit()
+ }
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .opacity(ApplicationState.shared.activeServerState == nil ? 0.5 : 1.0)
+ .disabled(ApplicationState.shared.activeServerState == nil)
+ .help("News")
+
+ Button {
+ ApplicationState.shared.activeServerState?.selection = .board
+ }
+ label: {
+ Image(systemName: "pin.fill")
+ .resizable()
+ .scaledToFit()
+ }
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .opacity(ApplicationState.shared.activeServerState == nil ? 0.5 : 1.0)
+ .disabled(ApplicationState.shared.activeServerState == nil)
+ .help("Message Board")
+
+ Button {
+ ApplicationState.shared.activeServerState?.selection = .files
+ }
+ label: {
+ Image(systemName: "folder.fill")
+ .resizable()
+ .scaledToFit()
+ }
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .opacity(ApplicationState.shared.activeServerState == nil ? 0.5 : 1.0)
+ .disabled(ApplicationState.shared.activeServerState == nil)
+ .help("Files")
+
+ Spacer()
+
+ SettingsLink(label: {
+ Image(systemName: "gearshape.fill")
+ .resizable()
+ .scaledToFit()
+ })
+ .buttonStyle(.plain)
+ .frame(width: 18, height: 18)
+ .help("Settings")
+ }
+ .padding(.top, 8)
+ .padding(.bottom, 16)
+ .padding([.leading, .trailing], 16)
+ }
+// .frame(width: 468)
+// .background(colorScheme == .dark ? .black : .white)
+ .background(
+ VisualEffectView(material: .headerView, blendingMode: .behindWindow)
+ .cornerRadius(10.0)
+ )
+ }
+}
+
+#Preview {
+ HotlinePanelView()
+ .environment(Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient()))
+}
diff --git a/Hotline/macOS/ServerView.swift b/Hotline/macOS/ServerView.swift
index 8176704..1892177 100644
--- a/Hotline/macOS/ServerView.swift
+++ b/Hotline/macOS/ServerView.swift
@@ -2,12 +2,17 @@ import SwiftUI
import UniformTypeIdentifiers
@Observable
-class ServerState {
+class ServerState: Equatable {
+ var id: UUID = UUID()
var selection: ServerNavigationType
init(selection: ServerNavigationType) {
self.selection = selection
}
+
+ static func == (lhs: ServerState, rhs: ServerState) -> Bool {
+ return lhs.id == rhs.id
+ }
}
enum MenuItemType {
@@ -121,6 +126,7 @@ struct ServerView: View {
@Environment(\.dismiss) var dismiss
@Environment(\.colorScheme) private var colorScheme
@Environment(\.controlActiveState) private var controlActiveState
+ @Environment(\.scenePhase) private var scenePhase
@State private var model: Hotline = Hotline(trackerClient: HotlineTrackerClient(), client: HotlineClient())
@State private var state: ServerState = ServerState(selection: .chat)