diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/project.pbxproj b/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/project.pbxproj index a7ca08d..d453e83 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/project.pbxproj +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/project.pbxproj @@ -9,27 +9,27 @@ /* Begin PBXBuildFile section */ 2E6E06E42711BD880028F77D /* SwordRPC in Frameworks */ = {isa = PBXBuildFile; productRef = 2E6E06E32711BD880028F77D /* SwordRPC */; }; 2EA352FD26D5201900271909 /* MusicScriptingBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EA352FC26D5201900271909 /* MusicScriptingBridge.swift */; }; - 2EEA817B26D74993009360AF /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEA817A26D74993009360AF /* SettingsView.swift */; }; - 2EEA817D26D74D21009360AF /* SettingsEnums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEA817C26D74D21009360AF /* SettingsEnums.swift */; }; + 2EEA817B26D74993009360AF /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EEA817A26D74993009360AF /* PreferencesView.swift */; }; + 2EF0AC312813E5F600EEDC5F /* RootNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF0AC302813E5F600EEDC5F /* RootNavigationView.swift */; }; 2EF39E0D268D912200299C7F /* AMDiscordRPCApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF39E0C268D912200299C7F /* AMDiscordRPCApp.swift */; }; - 2EF39E0F268D912200299C7F /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF39E0E268D912200299C7F /* ContentView.swift */; }; + 2EF39E0F268D912200299C7F /* RPCStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF39E0E268D912200299C7F /* RPCStatusView.swift */; }; 2EF39E11268D912300299C7F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2EF39E10268D912300299C7F /* Assets.xcassets */; }; 2EF39E14268D912300299C7F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2EF39E13268D912300299C7F /* Preview Assets.xcassets */; }; - 2EF39E1D268D913A00299C7F /* DiscordRichPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF39E1C268D913A00299C7F /* DiscordRichPresence.swift */; }; + 2EF39E1D268D913A00299C7F /* DiscordRPCObservable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2EF39E1C268D913A00299C7F /* DiscordRPCObservable.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 2EA352FC26D5201900271909 /* MusicScriptingBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicScriptingBridge.swift; sourceTree = ""; }; - 2EEA817A26D74993009360AF /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 2EEA817C26D74D21009360AF /* SettingsEnums.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsEnums.swift; sourceTree = ""; }; - 2EF39E09268D912200299C7F /* AppleMusicDiscordRPC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AppleMusicDiscordRPC.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 2EEA817A26D74993009360AF /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; + 2EF0AC302813E5F600EEDC5F /* RootNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootNavigationView.swift; sourceTree = ""; }; + 2EF39E09268D912200299C7F /* Apple Music Discord RPC.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Apple Music Discord RPC.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2EF39E0C268D912200299C7F /* AMDiscordRPCApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AMDiscordRPCApp.swift; sourceTree = ""; }; - 2EF39E0E268D912200299C7F /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; + 2EF39E0E268D912200299C7F /* RPCStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RPCStatusView.swift; sourceTree = ""; }; 2EF39E10268D912300299C7F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 2EF39E13268D912300299C7F /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 2EF39E15268D912300299C7F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 2EF39E16268D912300299C7F /* AppleMusicDiscordRPC.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = AppleMusicDiscordRPC.entitlements; sourceTree = ""; }; - 2EF39E1C268D913A00299C7F /* DiscordRichPresence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordRichPresence.swift; sourceTree = ""; }; + 2EF39E1C268D913A00299C7F /* DiscordRPCObservable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DiscordRPCObservable.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -44,6 +44,25 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 2EF0AC322813EBB100EEDC5F /* Modals */ = { + isa = PBXGroup; + children = ( + 2EA352FC26D5201900271909 /* MusicScriptingBridge.swift */, + 2EF39E1C268D913A00299C7F /* DiscordRPCObservable.swift */, + ); + path = Modals; + sourceTree = ""; + }; + 2EF0AC332813EBC000EEDC5F /* Views */ = { + isa = PBXGroup; + children = ( + 2EF39E0E268D912200299C7F /* RPCStatusView.swift */, + 2EEA817A26D74993009360AF /* PreferencesView.swift */, + 2EF0AC302813E5F600EEDC5F /* RootNavigationView.swift */, + ); + path = Views; + sourceTree = ""; + }; 2EF39E00268D912200299C7F = { isa = PBXGroup; children = ( @@ -55,7 +74,7 @@ 2EF39E0A268D912200299C7F /* Products */ = { isa = PBXGroup; children = ( - 2EF39E09268D912200299C7F /* AppleMusicDiscordRPC.app */, + 2EF39E09268D912200299C7F /* Apple Music Discord RPC.app */, ); name = Products; sourceTree = ""; @@ -63,12 +82,9 @@ 2EF39E0B268D912200299C7F /* AppleMusicDiscordRPC */ = { isa = PBXGroup; children = ( - 2EA352FC26D5201900271909 /* MusicScriptingBridge.swift */, - 2EEA817C26D74D21009360AF /* SettingsEnums.swift */, - 2EF39E1C268D913A00299C7F /* DiscordRichPresence.swift */, + 2EF0AC322813EBB100EEDC5F /* Modals */, + 2EF0AC332813EBC000EEDC5F /* Views */, 2EF39E0C268D912200299C7F /* AMDiscordRPCApp.swift */, - 2EF39E0E268D912200299C7F /* ContentView.swift */, - 2EEA817A26D74993009360AF /* SettingsView.swift */, 2EF39E10268D912300299C7F /* Assets.xcassets */, 2EF39E15268D912300299C7F /* Info.plist */, 2EF39E16268D912300299C7F /* AppleMusicDiscordRPC.entitlements */, @@ -88,9 +104,9 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ - 2EF39E08268D912200299C7F /* AppleMusicDiscordRPC */ = { + 2EF39E08268D912200299C7F /* Apple Music Discord RPC */ = { isa = PBXNativeTarget; - buildConfigurationList = 2EF39E19268D912300299C7F /* Build configuration list for PBXNativeTarget "AppleMusicDiscordRPC" */; + buildConfigurationList = 2EF39E19268D912300299C7F /* Build configuration list for PBXNativeTarget "Apple Music Discord RPC" */; buildPhases = ( 2EF39E05268D912200299C7F /* Sources */, 2EF39E06268D912200299C7F /* Frameworks */, @@ -100,12 +116,12 @@ ); dependencies = ( ); - name = AppleMusicDiscordRPC; + name = "Apple Music Discord RPC"; packageProductDependencies = ( 2E6E06E32711BD880028F77D /* SwordRPC */, ); productName = AppleMusicRichPresence; - productReference = 2EF39E09268D912200299C7F /* AppleMusicDiscordRPC.app */; + productReference = 2EF39E09268D912200299C7F /* Apple Music Discord RPC.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -138,7 +154,7 @@ projectDirPath = ""; projectRoot = ""; targets = ( - 2EF39E08268D912200299C7F /* AppleMusicDiscordRPC */, + 2EF39E08268D912200299C7F /* Apple Music Discord RPC */, ); }; /* End PBXProject section */ @@ -160,10 +176,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 2EEA817B26D74993009360AF /* SettingsView.swift in Sources */, - 2EF39E0F268D912200299C7F /* ContentView.swift in Sources */, - 2EF39E1D268D913A00299C7F /* DiscordRichPresence.swift in Sources */, - 2EEA817D26D74D21009360AF /* SettingsEnums.swift in Sources */, + 2EF0AC312813E5F600EEDC5F /* RootNavigationView.swift in Sources */, + 2EEA817B26D74993009360AF /* PreferencesView.swift in Sources */, + 2EF39E0F268D912200299C7F /* RPCStatusView.swift in Sources */, + 2EF39E1D268D913A00299C7F /* DiscordRPCObservable.swift in Sources */, 2EA352FD26D5201900271909 /* MusicScriptingBridge.swift in Sources */, 2EF39E0D268D912200299C7F /* AMDiscordRPCApp.swift in Sources */, ); @@ -296,6 +312,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"AppleMusicDiscordRPC/Preview Content\""; DEVELOPMENT_TEAM = 2VZNUT7D2E; ENABLE_HARDENED_RUNTIME = YES; @@ -306,7 +323,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.3; - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = me.jkelol111.AppleMusicDiscordRPC; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -322,6 +339,7 @@ CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"AppleMusicDiscordRPC/Preview Content\""; DEVELOPMENT_TEAM = 2VZNUT7D2E; ENABLE_HARDENED_RUNTIME = YES; @@ -332,7 +350,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 11.3; - MARKETING_VERSION = 1.0.0; + MARKETING_VERSION = 1.1.0; PRODUCT_BUNDLE_IDENTIFIER = me.jkelol111.AppleMusicDiscordRPC; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 5.0; @@ -351,7 +369,7 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 2EF39E19268D912300299C7F /* Build configuration list for PBXNativeTarget "AppleMusicDiscordRPC" */ = { + 2EF39E19268D912300299C7F /* Build configuration list for PBXNativeTarget "Apple Music Discord RPC" */ = { isa = XCConfigurationList; buildConfigurations = ( 2EF39E1A268D912300299C7F /* Debug */, diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/xcshareddata/xcschemes/AppleMusicDiscordRPC.xcscheme b/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/xcshareddata/xcschemes/AppleMusicDiscordRPC.xcscheme index b63c7db..a596e1d 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/xcshareddata/xcschemes/AppleMusicDiscordRPC.xcscheme +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC.xcodeproj/xcshareddata/xcschemes/AppleMusicDiscordRPC.xcscheme @@ -16,7 +16,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "2EF39E08268D912200299C7F" BuildableName = "AppleMusicDiscordRPC.app" - BlueprintName = "AppleMusicDiscordRPC" + BlueprintName = "Apple Music Discord RPC" ReferencedContainer = "container:AppleMusicDiscordRPC.xcodeproj"> @@ -46,7 +46,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "2EF39E08268D912200299C7F" BuildableName = "AppleMusicDiscordRPC.app" - BlueprintName = "AppleMusicDiscordRPC" + BlueprintName = "Apple Music Discord RPC" ReferencedContainer = "container:AppleMusicDiscordRPC.xcodeproj"> @@ -63,7 +63,7 @@ BuildableIdentifier = "primary" BlueprintIdentifier = "2EF39E08268D912200299C7F" BuildableName = "AppleMusicDiscordRPC.app" - BlueprintName = "AppleMusicDiscordRPC" + BlueprintName = "Apple Music Discord RPC" ReferencedContainer = "container:AppleMusicDiscordRPC.xcodeproj"> diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/AMDiscordRPCApp.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/AMDiscordRPCApp.swift index 167041c..686fbc7 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/AMDiscordRPCApp.swift +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/AMDiscordRPCApp.swift @@ -1,19 +1,33 @@ import SwiftUI +enum AppView { + case rpcStatus + case preferences +} + @main struct AMDiscordRPCApp: App { - @StateObject var rpcObservable: DiscordRPCObservable = DiscordRPCObservable() + @StateObject private var rpcObservable: DiscordRPCObservable = DiscordRPCObservable() + + @State private var selectedView: AppView? = .rpcStatus var body: some Scene { WindowGroup { - ContentView().environmentObject(self.rpcObservable) - }.commands { + RootNavigationView(selectedView: self.$selectedView) + .environmentObject(self.rpcObservable) + .onReceive(NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification)) { output in + self.rpcObservable.disconnectFromDiscord() + } + } + .commands { + CommandGroup(replacing: .appSettings) { + Button("Preferences...") { + self.selectedView = .preferences + } + .keyboardShortcut(",") + } CommandGroup(replacing: .newItem) {} CommandGroup(replacing: .help) {} } - - Settings { - SettingsView().environmentObject(self.rpcObservable) - } } } diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AccentColor.colorset/Contents.json b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..0d4b752 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,15 @@ { "colors" : [ { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x44", + "green" : "0x3C", + "red" : "0xFC" + } + }, "idiom" : "universal" } ], diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/Contents.json b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/Contents.json index 3f00db4..64dc11e 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,51 +1,61 @@ { "images" : [ { + "filename" : "icon_16x16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { + "filename" : "icon_16x16@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { + "filename" : "icon_32x32.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { + "filename" : "icon_32x32@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { + "filename" : "icon_128x128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { + "filename" : "icon_128x128@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { + "filename" : "icon_256x256.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { + "filename" : "icon_256x256@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { + "filename" : "icon_512x512.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { + "filename" : "icon_512x512@2x.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128.png new file mode 100644 index 0000000..5a480ba Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png new file mode 100644 index 0000000..e0a5eb7 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16.png new file mode 100644 index 0000000..89b368b Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png new file mode 100644 index 0000000..a594876 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256.png new file mode 100644 index 0000000..e0a5eb7 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png new file mode 100644 index 0000000..6a519a9 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32.png new file mode 100644 index 0000000..a594876 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png new file mode 100644 index 0000000..ef45e9e Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512.png new file mode 100644 index 0000000..6a519a9 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png new file mode 100644 index 0000000..fb7a0bf Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/Contents.json b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/Contents.json new file mode 100644 index 0000000..b2d85dd --- /dev/null +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "No Album Artwork.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/No Album Artwork.png b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/No Album Artwork.png new file mode 100644 index 0000000..aae6a46 Binary files /dev/null and b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Assets.xcassets/NoArtwork.imageset/No Album Artwork.png differ diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/ContentView.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/ContentView.swift deleted file mode 100644 index 0cf82c2..0000000 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/ContentView.swift +++ /dev/null @@ -1,63 +0,0 @@ -import SwiftUI - -struct ContentView: View { - @EnvironmentObject var rpcObservable: DiscordRPCObservable - - var body: some View { - VStack { - HStack { - Text("Track") - .bold() - Spacer() - Text(verbatim: rpcObservable.rpcData.name ?? "") - } - HStack { - Text("Artist") - .bold() - Spacer() - Text(verbatim: rpcObservable.rpcData.artist ?? "") - } - HStack { - Text("Album") - .bold() - Spacer() - Text(verbatim: rpcObservable.rpcData.album ?? "") - } - HStack { - Button(action: { - if self.rpcObservable.isDiscordConnected { - self.rpcObservable.disconnectFromDiscord() - } else { - self.rpcObservable.connectToDiscord() - } - }) { - if self.rpcObservable.isDiscordConnected { - Text("Disconnect from Discord") - } else { - Text("Connect to Discord") - } - }.disabled(self.rpcObservable.isChangingConnectionStatus) - Spacer() - switch rpcObservable.rpcData.state { - case .playing: - Image(systemName: "play.fill") - case .paused: - Image(systemName: "pause.fill") - case .stopped: - Image(systemName: "stop.fill") - } - if !self.rpcObservable.isDiscordConnected { - Image(systemName: "wifi.exclamationmark") - } - } - }.onReceive(NotificationCenter.default.publisher(for: NSApplication.willTerminateNotification)) { output in - self.rpcObservable.disconnectFromDiscord() - }.padding() - } -} - -struct ContentView_Previews: PreviewProvider { - static var previews: some View { - ContentView() - } -} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/DiscordRichPresence.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/DiscordRichPresence.swift deleted file mode 100644 index 854fa79..0000000 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/DiscordRichPresence.swift +++ /dev/null @@ -1,230 +0,0 @@ -import SwiftUI -import ScriptingBridge -import SwordRPC -import os - -enum AMPlayerStates: String { - case playing = "playing" - case paused = "paused" - case stopped = "stopped" -} - -struct DiscordRPCData { - var name: String? - var artist: String? - var album: String? - var totalTime: Double? - var state: AMPlayerStates -} - -class DiscordRPCObservable: ObservableObject { - @Published var rpcData: DiscordRPCData = DiscordRPCData(state: .stopped) - @Published var isDiscordConnected: Bool = false - @Published var isChangingConnectionStatus: Bool = true - - @AppStorage("TopText") var topText: TextSetting = .name - @AppStorage("BottomText") var bottomText: TextSetting = .artist - @AppStorage("LargeImageHoverText") var largeImageHoverText: TextSetting = .album - @AppStorage("ShowLargeImage") var showLargeImage: Bool = true - @AppStorage("ShowVersionOnLargeImageHover") var showVersionOnLargeImageHover: Bool = true - @AppStorage("ShowPlaybackState") var showPlaybackState: Bool = true - @AppStorage("ShowRemainingTime") var showRemainingTime: Bool = true - - private let nc: DistributedNotificationCenter = DistributedNotificationCenter.default() - private var ncObserver: NSObjectProtocol = NSObject() - private let AMApp: MusicApplication? = SBApplication(bundleIdentifier: "com.apple.Music") - private var AMAppVersion: String? - - private var rpc: SwordRPC = SwordRPC(appId: "785053859915366401") - - private let logger: Logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AMRPCObservable") - - private func checkNilEmptyString(_ suspect: String?) -> Bool { - return !(suspect ?? "").isEmpty - } - - func setRPC() { - if isDiscordConnected { - var presence: RichPresence = RichPresence() - - switch self.topText { - case .name: - if self.checkNilEmptyString(self.rpcData.name) { - presence.details = self.rpcData.name - } - case .artist: - if self.checkNilEmptyString(self.rpcData.artist) { - presence.details = self.rpcData.artist - } - case .album: - if checkNilEmptyString(self.rpcData.album) { - presence.details = self.rpcData.album - } - case .none: - break - } - - switch self.bottomText { - case .name: - if self.checkNilEmptyString(self.rpcData.name) { - presence.state = self.rpcData.name - } - case .artist: - if self.checkNilEmptyString(self.rpcData.artist) { - presence.state = self.rpcData.artist - } - case .album: - if self.checkNilEmptyString(self.rpcData.album) { - presence.state = self.rpcData.album - } - case .none: - break - } - - if self.showRemainingTime && - self.rpcData.state == .playing && - self.AMApp?.playerPosition != nil && - self.rpcData.totalTime != nil - { - let currentTime: Date = Date() - presence.timestamps.start = currentTime - presence.timestamps.end = currentTime + (self.rpcData.totalTime! - self.AMApp!.playerPosition!) - } - - if self.showLargeImage { - var actualLargeImageHoverText: String = "" - - switch self.largeImageHoverText { - case .name: - if self.checkNilEmptyString(self.rpcData.name) { - actualLargeImageHoverText = self.rpcData.name! - } - case .artist: - if self.checkNilEmptyString(self.rpcData.artist) { - actualLargeImageHoverText = self.rpcData.artist! - } - case .album: - if self.checkNilEmptyString(self.rpcData.album) { - actualLargeImageHoverText = self.rpcData.album! - } - case .none: - break - } - - if self.showVersionOnLargeImageHover && - self.checkNilEmptyString(self.AMAppVersion) { - if self.largeImageHoverText != .none { - actualLargeImageHoverText += ", " - } - actualLargeImageHoverText += "Apple Music \(self.AMAppVersion!)" - } - - if self.showVersionOnLargeImageHover || self.largeImageHoverText != .none { - presence.assets.largeText = actualLargeImageHoverText - } - - presence.assets.largeImage = "applemusic_large" - - if self.showPlaybackState { - presence.assets.smallText = self.rpcData.state.rawValue.capitalized - presence.assets.smallImage = self.rpcData.state.rawValue - } - } - - self.rpc.setPresence(presence) - } - } - - func manuallyUpdateRPCData () { - let currentAMTrack: MusicTrack? = self.AMApp?.currentTrack - self.rpcData.name = currentAMTrack?.name - self.rpcData.artist = currentAMTrack?.artist - self.rpcData.album = currentAMTrack?.album - - switch self.AMApp?.playerState { - case .playing?, - .fastForwarding?, - .rewinding?: - self.rpcData.state = .playing - case .paused?: - self.rpcData.state = .paused - case .stopped?: - self.rpcData.state = .stopped - default: - self.rpcData.state = .stopped - } - - self.rpcData.totalTime = currentAMTrack?.finish - - self.AMAppVersion = self.AMApp?.version - } - - func listenAMNotifications() { - self.ncObserver = nc.addObserver( - forName: NSNotification.Name("com.apple.Music.playerInfo"), - object: nil, - queue: nil - ) { notification in - self.logger.log("Received Apple Music notification: \(notification, privacy: .public)") - self.rpcData.name = notification.userInfo?[AnyHashable("Name")] as? String - self.rpcData.artist = notification.userInfo?[AnyHashable("Artist")] as? String - self.rpcData.album = notification.userInfo?[AnyHashable("Album")] as? String - self.rpcData.state = AMPlayerStates(rawValue: (notification.userInfo?[AnyHashable("Player State")] as? String)?.lowercased() ?? "stopped") ?? .stopped - self.rpcData.totalTime = self.AMApp?.currentTrack?.finish - self.setRPC() - } - self.logger.log("Listening for Apple Music notifications.") - } - - func unsubAMNotifications() { - nc.removeObserver( - self.ncObserver, - name: NSNotification.Name("com.apple.Music.playerInfo"), - object: nil - ) - } - - func newSwordRPC() { - self.rpc = SwordRPC(appId: "785053859915366401") - self.rpc.onConnect { _ in - DispatchQueue.main.async { - self.isDiscordConnected = true - // Manually get the player states at first start before notifications init. - self.manuallyUpdateRPCData() - self.setRPC() - self.listenAMNotifications() - self.isChangingConnectionStatus = false - } - self.logger.log("Connected to Discord RPC.") - } - self.rpc.onDisconnect { _,_,_ in - DispatchQueue.main.async { - self.unsubAMNotifications() - self.isDiscordConnected = false - self.isChangingConnectionStatus = false - } - self.logger.log("Disconnected from Discord RPC.") - } - } - - func connectToDiscord() { - if !isDiscordConnected { - self.isChangingConnectionStatus = true - self.newSwordRPC() - self.isDiscordConnected = self.rpc.connect() - } - } - - func disconnectFromDiscord() { - if isDiscordConnected { - self.isChangingConnectionStatus = true - self.rpc.disconnect() - } - } - - init() { - self.logger.log("Initialize.") - self.newSwordRPC() - self.isDiscordConnected = self.rpc.connect() - } -} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Info.plist b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Info.plist index 2dedc82..8970a98 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Info.plist +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Info.plist @@ -17,12 +17,16 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSAppleEventsUsageDescription Used to retrieve currently Apple Music track information. + NSHumanReadableCopyright + © 2022-present Nam Thành Nguyễn (jkelol111) et. al. + +Licensed under the GPL v3. A copy of the license can be found in the source code. diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/DiscordRPCObservable.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/DiscordRPCObservable.swift new file mode 100644 index 0000000..8a4542f --- /dev/null +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/DiscordRPCObservable.swift @@ -0,0 +1,230 @@ +import SwiftUI +import ScriptingBridge +import SwordRPC +import os + +fileprivate struct iTunesQueryResults: Decodable { + let artworkUrl100: String +} + +fileprivate struct iTunesQueryResponse: Decodable { + let resultCount: Int + let results: [iTunesQueryResults] +} + +enum AMPlayerStates: String { + case playing = "playing" + case paused = "paused" + case stopped = "stopped" +} + +struct DiscordRPCData { + var name: String? + var artist: String? + var album: String? + var totalTime: Double? + var state: AMPlayerStates +} + +struct AMArtwork { + var album: String? + var url: String? +} + +class DiscordRPCObservable: ObservableObject { + @Published var rpcData: DiscordRPCData = DiscordRPCData(state: .stopped) + @Published var artwork: AMArtwork = AMArtwork() + + @Published var isDiscordConnected: Bool = false + @Published var isChangingConnectionStatus: Bool = true + + @AppStorage("showAlbumArt") var showAlbumArt: Bool = true + + private let nc: DistributedNotificationCenter = DistributedNotificationCenter.default() + private var ncObserver: NSObjectProtocol = NSObject() + private let AMApp: MusicApplication? = SBApplication(bundleIdentifier: "com.apple.Music") + + private var rpc: SwordRPC = SwordRPC(appId: "785053859915366401") + + private let logger: Logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "DiscordRPCObservable") + private let jsond: JSONDecoder = JSONDecoder() + + func setRPC() { + if isDiscordConnected { + var presence: RichPresence = RichPresence() + + presence.details = self.rpcData.name + presence.state = self.rpcData.artist + + if self.rpcData.state == .playing, + let playerPosition: Double = self.AMApp?.playerPosition, + let totalTime: Double = self.rpcData.totalTime { + let currentTime: Date = Date() + presence.timestamps.start = currentTime + presence.timestamps.end = currentTime + (totalTime - playerPosition) + } + + presence.assets.smallText = self.rpcData.state.rawValue.capitalized + presence.assets.smallImage = self.rpcData.state.rawValue + + presence.assets.largeText = self.rpcData.album + if self.showAlbumArt, + let name: String = self.rpcData.name, + let album: String = self.rpcData.album, + let artist: String = self.rpcData.artist { + if self.artwork.album == album { + self.logger.info("Album identical, not replacing artwork URL.") + presence.assets.largeImage = self.artwork.url + self.rpc.setPresence(presence) + return + } + self.logger.info("Fetching artwork for: \(name, privacy: .public), \(album, privacy: .public), \(artist, privacy: .public)") + let encodedTerms: String = "\(name) \(album) \(artist)".addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)! + if let url: URL = URL(string: "https://itunes.apple.com/search?term=\(encodedTerms)&media=music&entity=song&limit=1") { + var request: URLRequest = URLRequest(url: url) + request.timeoutInterval = 2 + URLSession.shared.dataTask( + with: request, + completionHandler: { data, response, error in + if let error = error { + self.logger.error("Unable to fetch artwork: \(error.localizedDescription, privacy: .public)") + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + self.rpc.setPresence(presence) + } + if let data = data { + if let iTunesResponse: iTunesQueryResponse = try? self.jsond.decode(iTunesQueryResponse.self, from: data) { + if iTunesResponse.resultCount > 0 { + let artworkURL: String = iTunesResponse.results.first!.artworkUrl100.replacingOccurrences(of: "100x100bb", with: "512x512") + self.logger.info("Fetched artwork: \(artworkURL, privacy: .public)") + presence.assets.largeImage = artworkURL + DispatchQueue.main.async { + self.artwork.album = album + self.artwork.url = artworkURL + } + } else { + self.logger.warning("No artwork found. Setting default image.") + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + } + self.rpc.setPresence(presence) + } else { + self.logger.warning("Could not parse iTunes response. Setting default image.") + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + self.rpc.setPresence(presence) + } + } else { + self.logger.warning("No artwork found. Setting default image.") + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + self.rpc.setPresence(presence) + } + } + ) + .resume() + } else { + self.logger.warning("Can't form iTunes search URL. Setting default image.") + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + self.rpc.setPresence(presence) + } + } else { + presence.assets.largeImage = "applemusic_large" + self.artwork.url = nil + self.rpc.setPresence(presence) + } + } + } + + func manuallyUpdateRPCData () { + let currentAMTrack: MusicTrack? = self.AMApp?.currentTrack + self.rpcData.name = currentAMTrack?.name + self.rpcData.artist = currentAMTrack?.artist + self.rpcData.album = currentAMTrack?.album + + switch self.AMApp?.playerState { + case .playing?, + .fastForwarding?, + .rewinding?: + self.rpcData.state = .playing + case .paused?: + self.rpcData.state = .paused + case .stopped?: + self.rpcData.state = .stopped + default: + self.rpcData.state = .stopped + } + + self.rpcData.totalTime = currentAMTrack?.finish + } + + func listenAMNotifications() { + self.ncObserver = nc.addObserver( + forName: NSNotification.Name("com.apple.Music.playerInfo"), + object: nil, + queue: nil + ) { notification in + self.logger.log("Received Apple Music notification: \(notification, privacy: .public)") + self.rpcData.name = notification.userInfo?[AnyHashable("Name")] as? String + self.rpcData.artist = notification.userInfo?[AnyHashable("Artist")] as? String + self.rpcData.album = notification.userInfo?[AnyHashable("Album")] as? String + self.rpcData.state = AMPlayerStates(rawValue: (notification.userInfo?[AnyHashable("Player State")] as? String)?.lowercased() ?? "stopped") ?? .stopped + self.rpcData.totalTime = self.AMApp?.currentTrack?.finish + self.setRPC() + } + self.logger.log("Listening for Apple Music notifications.") + } + + func unsubAMNotifications() { + nc.removeObserver( + self.ncObserver, + name: NSNotification.Name("com.apple.Music.playerInfo"), + object: nil + ) + } + + func newSwordRPC() { + self.rpc = SwordRPC(appId: "785053859915366401") + self.rpc.onConnect { _ in + DispatchQueue.main.async { + self.isDiscordConnected = true + // Manually get the player states at first start before notifications init. + self.manuallyUpdateRPCData() + self.setRPC() + self.listenAMNotifications() + self.isChangingConnectionStatus = false + } + self.logger.log("Connected to Discord RPC.") + } + self.rpc.onDisconnect { _,_,_ in + DispatchQueue.main.async { + self.unsubAMNotifications() + self.isDiscordConnected = false + self.isChangingConnectionStatus = false + } + self.logger.log("Disconnected from Discord RPC.") + } + } + + func connectToDiscord() { + if !isDiscordConnected { + self.isChangingConnectionStatus = true + self.newSwordRPC() + self.isDiscordConnected = self.rpc.connect() + } + } + + func disconnectFromDiscord() { + if isDiscordConnected { + self.isChangingConnectionStatus = true + self.rpc.disconnect() + } + } + + init() { + self.logger.log("Initialize.") + self.newSwordRPC() + self.isDiscordConnected = self.rpc.connect() + } +} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/MusicScriptingBridge.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/MusicScriptingBridge.swift similarity index 95% rename from AppleMusicDiscordRPC/AppleMusicDiscordRPC/MusicScriptingBridge.swift rename to AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/MusicScriptingBridge.swift index 6480e23..3ccd68e 100644 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/MusicScriptingBridge.swift +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Modals/MusicScriptingBridge.swift @@ -42,6 +42,5 @@ extension SBObject: MusicTrack {} @objc optional var currentTrack: MusicTrack { get } // the current targeted track @objc optional var playerState: MusicEPlS { get } // is the player stopped, paused, or playing? @objc optional var playerPosition: Double { get } // the player’s position within the currently playing track in seconds. - @objc optional var version: String { get } // the version of the application } extension SBApplication: MusicApplication {} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsEnums.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsEnums.swift deleted file mode 100644 index bd534b7..0000000 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsEnums.swift +++ /dev/null @@ -1,6 +0,0 @@ -enum TextSetting: String, Codable, CaseIterable { - case name = "Name" - case artist = "Artist" - case album = "Album" - case none = "None" -} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsView.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsView.swift deleted file mode 100644 index 784b4a6..0000000 --- a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/SettingsView.swift +++ /dev/null @@ -1,81 +0,0 @@ -import SwiftUI - -struct SettingsView: View { - @EnvironmentObject var rpcObservable: DiscordRPCObservable - - @AppStorage("TopText") var topText: TextSetting = .name - @AppStorage("BottomText") var bottomText: TextSetting = .artist - @AppStorage("LargeImageHoverText") var largeImageHoverText: TextSetting = .album - @AppStorage("ShowLargeImage") var showLargeImage: Bool = true - @AppStorage("ShowVersionOnLargeImageHover") var showVersionOnLargeImageHover: Bool = true - @AppStorage("ShowPlaybackState") var showPlaybackState: Bool = true - @AppStorage("ShowRemainingTime") var showRemainingTime: Bool = true - - var pickerItems: some View { - ForEach(TextSetting.allCases, id: \.self, content: { item in - Text(item.rawValue).tag(item) - }) - } - - var body: some View { - TabView { - Form { - Picker("Top text: ", selection: self.$topText) { - self.pickerItems - } - Picker("Bottom text: ", selection: self.$bottomText) { - self.pickerItems - } - Picker("Apple Music logo hover text: ", selection: self.$largeImageHoverText) { - self.pickerItems - }.disabled(!self.showLargeImage) - }.tabItem { - Label("Text displays", systemImage: "text.justifyleft") - } - Form { - Toggle(isOn: self.$showLargeImage) { - Text("Show Apple Music logo") - } - Toggle(isOn: self.$showVersionOnLargeImageHover) { - Text("Show version on Apple Music logo hover") - }.disabled(!self.showLargeImage) - Toggle(isOn: self.$showPlaybackState) { - Text("Show playback state") - }.disabled(!self.showLargeImage) - Toggle(isOn: self.$showRemainingTime) { - Text("Show remaining time") - } - }.tabItem { - Label("Other displays", systemImage: "rectangle.badge.checkmark") - } - }.padding(20) - .frame(width: 420, height: 150) - .onChange(of: self.topText) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.bottomText) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.largeImageHoverText) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.showLargeImage) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.showVersionOnLargeImageHover) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.showPlaybackState) { _ in - self.rpcObservable.setRPC() - } - .onChange(of: self.showRemainingTime) { _ in - self.rpcObservable.setRPC() - } - } -} - -struct SettingsView_Previews: PreviewProvider { - static var previews: some View { - SettingsView() - } -} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/PreferencesView.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/PreferencesView.swift new file mode 100644 index 0000000..eb29bda --- /dev/null +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/PreferencesView.swift @@ -0,0 +1,35 @@ +import SwiftUI + +struct PreferencesView: View { + @EnvironmentObject var rpcObservable: DiscordRPCObservable + + @AppStorage("showAlbumArt") var showAlbumArt: Bool = true + + var body: some View { + Form { + Toggle(isOn: self.$showAlbumArt) { + Text("Show album art") + } + Text("Disabling this will display Apple Music's logo instead.") + Text("We recommend disabling if you have a slow network connection.") + } + .padding(20) + .onChange(of: self.showAlbumArt) { _ in + self.rpcObservable.setRPC() + } + .toolbar { + ToolbarItem { + Button("Reset") { + self.showAlbumArt = true + } + } + } + .navigationSubtitle("Preferences") + } +} + +struct PreferencesView_Previews: PreviewProvider { + static var previews: some View { + PreferencesView() + } +} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RPCStatusView.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RPCStatusView.swift new file mode 100644 index 0000000..00f3359 --- /dev/null +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RPCStatusView.swift @@ -0,0 +1,80 @@ +import SwiftUI + +struct RPCStatusView: View { + @EnvironmentObject var rpcObservable: DiscordRPCObservable + + var noArtwork: some View { + Image("NoArtwork") + .resizable() + .frame(width: 256, height: 256) + } + + var body: some View { + VStack { + if #available(macOS 12.0, *) { + if let artworkURL: String = self.rpcObservable.artwork.url { + AsyncImage(url: URL(string: artworkURL)) { image in + image + .resizable() + } placeholder: { + self.noArtwork + } + .frame(width: 256, height: 256) + .contextMenu { + Button("Copy artwork link...") { + NSPasteboard.general.setString(artworkURL, forType: .URL) + } + } + } else { + self.noArtwork + } + } else { + self.noArtwork + } + Text(verbatim: rpcObservable.rpcData.name ?? "Unknown track") + .bold() + Text(verbatim: rpcObservable.rpcData.album ?? "Unknown album") + Text(verbatim: rpcObservable.rpcData.artist ?? "Unknown artist") + + } + .toolbar { + ToolbarItem { + Button(action: { + if self.rpcObservable.isDiscordConnected { + self.rpcObservable.disconnectFromDiscord() + } else { + self.rpcObservable.connectToDiscord() + } + }) { + if self.rpcObservable.isDiscordConnected { + Label("Disconnect from Discord", systemImage: "network") + } else { + Label("Connect to Discord", systemImage: "bolt.horizontal.circle") + } + } + .disabled(self.rpcObservable.isChangingConnectionStatus) + } + ToolbarItem { + Button(action: {}) { + switch self.rpcObservable.rpcData.state { + case .playing: + Label("Playing", systemImage: "play.fill") + case .paused: + Label("Paused", systemImage: "pause.fill") + case .stopped: + Label("Stopped", systemImage: "stop.fill") + } + } + .disabled(true) + } + } + .navigationSubtitle("Status") + .padding() + } +} + +struct RPCStatusView_Previews: PreviewProvider { + static var previews: some View { + RPCStatusView() + } +} diff --git a/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RootNavigationView.swift b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RootNavigationView.swift new file mode 100644 index 0000000..d68fa9f --- /dev/null +++ b/AppleMusicDiscordRPC/AppleMusicDiscordRPC/Views/RootNavigationView.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct RootNavigationView: View { + @Binding var selectedView: AppView? + + var body: some View { + NavigationView { + List { + NavigationLink( + tag: AppView.rpcStatus, + selection: self.$selectedView, + destination: { + RPCStatusView() + } + ) { + Label("Status", systemImage: "info.circle") + } + NavigationLink( + tag: AppView.preferences, + selection: self.$selectedView, + destination: { + PreferencesView() + } + ) { + Label("Preferences", systemImage: "gearshape") + } + } + EmptyView() + } + .navigationTitle("Apple Music Discord RPC") + .frame(width: 430, height: 350) + } +} + +struct RootNavigationView_Previews: PreviewProvider { + static var previews: some View { + RootNavigationView(selectedView: .constant(.rpcStatus)) + } +}