diff --git a/Jukebox.xcodeproj/project.pbxproj b/Jukebox.xcodeproj/project.pbxproj index 5e9d06b..0acf49f 100644 --- a/Jukebox.xcodeproj/project.pbxproj +++ b/Jukebox.xcodeproj/project.pbxproj @@ -9,29 +9,17 @@ /* Begin PBXBuildFile section */ E800A8B22732811D00DBDF0E /* MenuMarqueeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E800A8B12732811D00DBDF0E /* MenuMarqueeText.swift */; }; E80AD61D27859020001FC303 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = E80AD61C27859020001FC303 /* Sparkle */; }; - E80E4CB52727F763000F9D74 /* Seeker.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E4CB42727F763000F9D74 /* Seeker.swift */; }; - E80E4CB827281297000F9D74 /* NetworkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = E80E4CB727281297000F9D74 /* NetworkManager.swift */; }; E82271BC2735514400D1795D /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82271BB2735514400D1795D /* Constants.swift */; }; E82271C3273567F000D1795D /* VisualizerStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = E82271C2273567F000D1795D /* VisualizerStyle.swift */; }; - E84A17382728E6F1005F8CB4 /* PromiseKit in Frameworks */ = {isa = PBXBuildFile; productRef = E84A17372728E6F1005F8CB4 /* PromiseKit */; }; - E84A173B2728EAC3005F8CB4 /* LyricsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84A173A2728EAC3005F8CB4 /* LyricsView.swift */; }; - E84A173D2728F490005F8CB4 /* SpotifyClientCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84A173C2728F490005F8CB4 /* SpotifyClientCredentials.swift */; }; - E84A173F2728F4CB005F8CB4 /* NetworkError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E84A173E2728F4CB005F8CB4 /* NetworkError.swift */; }; E8767C66273688FB00E39D17 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = E8767C65273688FB00E39D17 /* LaunchAtLogin */; }; E87F08BE2782E0CC00742DA1 /* OnboardingWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87F08BD2782E0CC00742DA1 /* OnboardingWindow.swift */; }; E87F08C02782E18600742DA1 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E87F08BF2782E18600742DA1 /* OnboardingView.swift */; }; E87F08C22782E2AC00742DA1 /* BaseWarp.metal in Sources */ = {isa = PBXBuildFile; fileRef = E87F08C12782E2AC00742DA1 /* BaseWarp.metal */; }; - E88441BC272D342500C7B650 /* MarqueeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88441BB272D342500C7B650 /* MarqueeText.swift */; }; E88441BE272D3A4C00C7B650 /* String+Dimensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88441BD272D3A4C00C7B650 /* String+Dimensions.swift */; }; - E88441C0272D4FA900C7B650 /* SwipeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E88441BF272D4FA900C7B650 /* SwipeView.swift */; }; E892FE0D272E878600359A64 /* StatusBarAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E892FE0C272E878600359A64 /* StatusBarAnimation.swift */; }; E8A013DD272AB20900B25B63 /* Gradient.metal in Sources */ = {isa = PBXBuildFile; fileRef = E8A013DC272AB20900B25B63 /* Gradient.metal */; }; E8A013DF272AB23500B25B63 /* MetalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8A013DE272AB23500B25B63 /* MetalView.swift */; }; - E8AF5FC027290DEF00AACF65 /* Secrets.plist in Resources */ = {isa = PBXBuildFile; fileRef = E8AF5FBF27290DEF00AACF65 /* Secrets.plist */; }; - E8AF5FC327290EFB00AACF65 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF5FC227290EFB00AACF65 /* Secrets.swift */; }; E8AF5FC5272924AC00AACF65 /* String+Base64.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF5FC4272924AC00AACF65 /* String+Base64.swift */; }; - E8AF5FC72729278F00AACF65 /* SpotifySearchResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF5FC62729278F00AACF65 /* SpotifySearchResult.swift */; }; - E8AF5FC9272927B800AACF65 /* MusixMatchLyrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8AF5FC8272927B800AACF65 /* MusixMatchLyrics.swift */; }; E8D7CCCC273AA1BC006179D7 /* SpotifyApplication.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8D7CCCB273AA1BC006179D7 /* SpotifyApplication.swift */; }; E8E00584272FFC8000D8AE1B /* NSAttributedString+Dimensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8E00583272FFC8000D8AE1B /* NSAttributedString+Dimensions.swift */; }; E8F5722B27166E0D00C5DF9C /* JukeboxApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = E8F5722A27166E0D00C5DF9C /* JukeboxApp.swift */; }; @@ -50,27 +38,16 @@ /* Begin PBXFileReference section */ E800A8B12732811D00DBDF0E /* MenuMarqueeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuMarqueeText.swift; sourceTree = ""; }; - E80E4CB42727F763000F9D74 /* Seeker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Seeker.swift; sourceTree = ""; }; - E80E4CB727281297000F9D74 /* NetworkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkManager.swift; sourceTree = ""; }; E82271BB2735514400D1795D /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; E82271C2273567F000D1795D /* VisualizerStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualizerStyle.swift; sourceTree = ""; }; - E84A173A2728EAC3005F8CB4 /* LyricsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LyricsView.swift; sourceTree = ""; }; - E84A173C2728F490005F8CB4 /* SpotifyClientCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyClientCredentials.swift; sourceTree = ""; }; - E84A173E2728F4CB005F8CB4 /* NetworkError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkError.swift; sourceTree = ""; }; E87F08BD2782E0CC00742DA1 /* OnboardingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingWindow.swift; sourceTree = ""; }; E87F08BF2782E18600742DA1 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = ""; }; E87F08C12782E2AC00742DA1 /* BaseWarp.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = BaseWarp.metal; sourceTree = ""; }; - E88441BB272D342500C7B650 /* MarqueeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarqueeText.swift; sourceTree = ""; }; E88441BD272D3A4C00C7B650 /* String+Dimensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Dimensions.swift"; sourceTree = ""; }; - E88441BF272D4FA900C7B650 /* SwipeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeView.swift; sourceTree = ""; }; E892FE0C272E878600359A64 /* StatusBarAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarAnimation.swift; sourceTree = ""; }; E8A013DC272AB20900B25B63 /* Gradient.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Gradient.metal; sourceTree = ""; }; E8A013DE272AB23500B25B63 /* MetalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetalView.swift; sourceTree = ""; }; - E8AF5FBF27290DEF00AACF65 /* Secrets.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Secrets.plist; sourceTree = ""; }; - E8AF5FC227290EFB00AACF65 /* Secrets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Secrets.swift; sourceTree = ""; }; E8AF5FC4272924AC00AACF65 /* String+Base64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Base64.swift"; sourceTree = ""; }; - E8AF5FC62729278F00AACF65 /* SpotifySearchResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifySearchResult.swift; sourceTree = ""; }; - E8AF5FC8272927B800AACF65 /* MusixMatchLyrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusixMatchLyrics.swift; sourceTree = ""; }; E8D7CCCB273AA1BC006179D7 /* SpotifyApplication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyApplication.swift; sourceTree = ""; }; E8E00583272FFC8000D8AE1B /* NSAttributedString+Dimensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSAttributedString+Dimensions.swift"; sourceTree = ""; }; E8F5722727166E0D00C5DF9C /* Jukebox.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Jukebox.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -97,33 +74,12 @@ files = ( E80AD61D27859020001FC303 /* Sparkle in Frameworks */, E8767C66273688FB00E39D17 /* LaunchAtLogin in Frameworks */, - E84A17382728E6F1005F8CB4 /* PromiseKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - E80E4CB627281274000F9D74 /* Networking */ = { - isa = PBXGroup; - children = ( - E80E4CB727281297000F9D74 /* NetworkManager.swift */, - E84A173E2728F4CB005F8CB4 /* NetworkError.swift */, - E84A17392728E82A005F8CB4 /* ResponseModels */, - ); - path = Networking; - sourceTree = ""; - }; - E84A17392728E82A005F8CB4 /* ResponseModels */ = { - isa = PBXGroup; - children = ( - E84A173C2728F490005F8CB4 /* SpotifyClientCredentials.swift */, - E8AF5FC62729278F00AACF65 /* SpotifySearchResult.swift */, - E8AF5FC8272927B800AACF65 /* MusixMatchLyrics.swift */, - ); - path = ResponseModels; - sourceTree = ""; - }; E8A013DB272AB1D900B25B63 /* Shaders */ = { isa = PBXGroup; children = ( @@ -166,13 +122,11 @@ E8F5723927166E2400C5DF9C /* Views */, E8F5723B271675EF00C5DF9C /* ViewModels */, E8F5724327167CA400C5DF9C /* Models */, - E80E4CB627281274000F9D74 /* Networking */, E8D7CCCA273AA18F006179D7 /* ScriptingBridge */, E8A013DB272AB1D900B25B63 /* Shaders */, E8F5723E2716784800C5DF9C /* Styles */, E8F57247271685EF00C5DF9C /* Utilities */, E8F5722E27166E1300C5DF9C /* Assets.xcassets */, - E8AF5FBF27290DEF00AACF65 /* Secrets.plist */, E8F5723A271670AE00C5DF9C /* Info.plist */, E8F5723327166E1300C5DF9C /* Jukebox.entitlements */, E8F5723027166E1300C5DF9C /* Preview Content */, @@ -194,12 +148,8 @@ E8FB63F7272BEF92006F28B8 /* AboutView.swift */, E8F5722C27166E0D00C5DF9C /* ContentView.swift */, E8A013DE272AB23500B25B63 /* MetalView.swift */, - E80E4CB42727F763000F9D74 /* Seeker.swift */, - E84A173A2728EAC3005F8CB4 /* LyricsView.swift */, E8FB63F3272BB97C006F28B8 /* PreferencesView.swift */, E8FB63F5272BC7BD006F28B8 /* VisualEffectView.swift */, - E88441BB272D342500C7B650 /* MarqueeText.swift */, - E88441BF272D4FA900C7B650 /* SwipeView.swift */, E892FE0C272E878600359A64 /* StatusBarAnimation.swift */, E800A8B12732811D00DBDF0E /* MenuMarqueeText.swift */, E87F08BF2782E18600742DA1 /* OnboardingView.swift */, @@ -237,7 +187,6 @@ isa = PBXGroup; children = ( E82271BB2735514400D1795D /* Constants.swift */, - E8AF5FC227290EFB00AACF65 /* Secrets.swift */, E8AF5FC4272924AC00AACF65 /* String+Base64.swift */, E88441BD272D3A4C00C7B650 /* String+Dimensions.swift */, E8E00583272FFC8000D8AE1B /* NSAttributedString+Dimensions.swift */, @@ -279,7 +228,6 @@ ); name = Jukebox; packageProductDependencies = ( - E84A17372728E6F1005F8CB4 /* PromiseKit */, E8767C65273688FB00E39D17 /* LaunchAtLogin */, E80AD61C27859020001FC303 /* Sparkle */, ); @@ -312,7 +260,6 @@ ); mainGroup = E8F5721E27166E0D00C5DF9C; packageReferences = ( - E84A17362728E6F1005F8CB4 /* XCRemoteSwiftPackageReference "PromiseKit" */, E8767C64273688FB00E39D17 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */, E80AD61B27859020001FC303 /* XCRemoteSwiftPackageReference "Sparkle" */, ); @@ -331,7 +278,6 @@ buildActionMask = 2147483647; files = ( E8F5723227166E1300C5DF9C /* Preview Assets.xcassets in Resources */, - E8AF5FC027290DEF00AACF65 /* Secrets.plist in Resources */, E8F5722F27166E1300C5DF9C /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -367,32 +313,22 @@ E8FB63F4272BB97C006F28B8 /* PreferencesView.swift in Sources */, E8FB63F8272BEF92006F28B8 /* AboutView.swift in Sources */, E82271C3273567F000D1795D /* VisualizerStyle.swift in Sources */, - E84A173D2728F490005F8CB4 /* SpotifyClientCredentials.swift in Sources */, - E8AF5FC327290EFB00AACF65 /* Secrets.swift in Sources */, E87F08BE2782E0CC00742DA1 /* OnboardingWindow.swift in Sources */, E8F57242271678C800C5DF9C /* PressButtonStyle.swift in Sources */, E8F5722D27166E0D00C5DF9C /* ContentView.swift in Sources */, E8FB63F6272BC7BD006F28B8 /* VisualEffectView.swift in Sources */, E8F572402716785700C5DF9C /* ChipStyle.swift in Sources */, E8F5722B27166E0D00C5DF9C /* JukeboxApp.swift in Sources */, - E80E4CB827281297000F9D74 /* NetworkManager.swift in Sources */, E87F08C02782E18600742DA1 /* OnboardingView.swift in Sources */, E82271BC2735514400D1795D /* Constants.swift in Sources */, E8D7CCCC273AA1BC006179D7 /* SpotifyApplication.swift in Sources */, E8E00584272FFC8000D8AE1B /* NSAttributedString+Dimensions.swift in Sources */, E88441BE272D3A4C00C7B650 /* String+Dimensions.swift in Sources */, - E80E4CB52727F763000F9D74 /* Seeker.swift in Sources */, E8F5723D2716760E00C5DF9C /* ContentViewModel.swift in Sources */, - E84A173B2728EAC3005F8CB4 /* LyricsView.swift in Sources */, - E8AF5FC9272927B800AACF65 /* MusixMatchLyrics.swift in Sources */, E8AF5FC5272924AC00AACF65 /* String+Base64.swift in Sources */, - E8AF5FC72729278F00AACF65 /* SpotifySearchResult.swift in Sources */, E8A013DD272AB20900B25B63 /* Gradient.metal in Sources */, E8A013DF272AB23500B25B63 /* MetalView.swift in Sources */, E800A8B22732811D00DBDF0E /* MenuMarqueeText.swift in Sources */, - E84A173F2728F4CB005F8CB4 /* NetworkError.swift in Sources */, - E88441BC272D342500C7B650 /* MarqueeText.swift in Sources */, - E88441C0272D4FA900C7B650 /* SwipeView.swift in Sources */, E87F08C22782E2AC00742DA1 /* BaseWarp.metal in Sources */, E8FB63F2272BB495006F28B8 /* PreferencesWindow.swift in Sources */, E8F5724527167CAF00C5DF9C /* Track.swift in Sources */, @@ -623,14 +559,6 @@ version = 1.27.1; }; }; - E84A17362728E6F1005F8CB4 /* XCRemoteSwiftPackageReference "PromiseKit" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/mxcl/PromiseKit"; - requirement = { - kind = exactVersion; - version = 6.16.1; - }; - }; E8767C64273688FB00E39D17 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sindresorhus/LaunchAtLogin"; @@ -647,11 +575,6 @@ package = E80AD61B27859020001FC303 /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; - E84A17372728E6F1005F8CB4 /* PromiseKit */ = { - isa = XCSwiftPackageProductDependency; - package = E84A17362728E6F1005F8CB4 /* XCRemoteSwiftPackageReference "PromiseKit" */; - productName = PromiseKit; - }; E8767C65273688FB00E39D17 /* LaunchAtLogin */ = { isa = XCSwiftPackageProductDependency; package = E8767C64273688FB00E39D17 /* XCRemoteSwiftPackageReference "LaunchAtLogin" */; diff --git a/Jukebox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Jukebox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4ef78ef..7ff7818 100644 --- a/Jukebox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Jukebox.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -10,15 +10,6 @@ "version": "4.2.0" } }, - { - "package": "PromiseKit", - "repositoryURL": "https://github.com/mxcl/PromiseKit", - "state": { - "branch": null, - "revision": "00afea5360bf1c02ac83209c2c8991f73b5a4e7d", - "version": "6.16.1" - } - }, { "package": "Sparkle", "repositoryURL": "https://github.com/sparkle-project/Sparkle", diff --git a/Jukebox.xcodeproj/project.xcworkspace/xcuserdata/sasindujayasinghe.xcuserdatad/UserInterfaceState.xcuserstate b/Jukebox.xcodeproj/project.xcworkspace/xcuserdata/sasindujayasinghe.xcuserdatad/UserInterfaceState.xcuserstate index 144da67..13097cc 100644 Binary files a/Jukebox.xcodeproj/project.xcworkspace/xcuserdata/sasindujayasinghe.xcuserdatad/UserInterfaceState.xcuserstate and b/Jukebox.xcodeproj/project.xcworkspace/xcuserdata/sasindujayasinghe.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/Jukebox/Models/Track.swift b/Jukebox/Models/Track.swift index 3a746d6..642a422 100644 --- a/Jukebox/Models/Track.swift +++ b/Jukebox/Models/Track.swift @@ -9,11 +9,8 @@ import Foundation import SwiftUI struct Track { - var title = "" var artist = "" var album = "" var albumArt = NSImage() - var lyrics = "No lyrics for current song..." - } diff --git a/Jukebox/Networking/NetworkError.swift b/Jukebox/Networking/NetworkError.swift deleted file mode 100644 index 7897ad9..0000000 --- a/Jukebox/Networking/NetworkError.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// NetworkError.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import Foundation - -enum NetworkError: Error { - case invalidURL(description: String?) - case networkError(description: String?) - case decodingFailed(description: String?) -} - -extension NetworkError: LocalizedError { - var errorDescription: String? { - switch self { - case .invalidURL(let description): - return description - case .networkError(let description): - return description - case .decodingFailed(let description): - return description - } - } -} diff --git a/Jukebox/Networking/NetworkManager.swift b/Jukebox/Networking/NetworkManager.swift deleted file mode 100644 index dae3da8..0000000 --- a/Jukebox/Networking/NetworkManager.swift +++ /dev/null @@ -1,182 +0,0 @@ -// -// NetworkManager.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 26/10/21. -// - -import Foundation -import PromiseKit - -struct NetworkManager { - - static var shared: NetworkManager = { - return NetworkManager() - }() - - private init() {} - - // MARK: - API Calls - - /// Get the Access Token from the Spotify API to enable use of the API's features - /// - /// - Returns: A Promise containing either the Spotify Access Token Model or an error if failed - func getSpotifyAccessToken() -> Promise { - return Promise { resolver in - - // Build URL - guard let url = buildURL(host: "accounts.spotify.com", path: "/api/token") else { - resolver.reject(NetworkError.invalidURL(description: "Failed to build URL to get Spotify Access Token.")) - return - } - - // Initialise request content - let encodedClientIDAndClientSecret = (Secrets.SPOTIFY_CLIENT_ID + ":" + Secrets.SPOTIFY_CLIENT_SECRET).toBase64() - let headers = [ - "Authorization": "Basic \(encodedClientIDAndClientSecret)", - "Content-Type": "application/x-www-form-urlencoded" - ] - let body = "grant_type=client_credentials".data(using: .utf8) - - // Create request - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.allHTTPHeaderFields = headers - request.httpBody = body - - // Send HTTP request - URLSession.shared.dataTask(with: request) { data, response, error in - guard let data = data, error == nil else { - print(error!) - resolver.reject(NetworkError.networkError(description: error?.localizedDescription)) - return - } - do { - let decoded = try JSONDecoder().decode(SpotifyClientCredentials.self, from: data) - resolver.fulfill(decoded) - } catch let error { - resolver.reject(NetworkError.decodingFailed(description: "Failed to decode AccessToken into JSON.\n\(error.localizedDescription)")) - } - - }.resume() - - } - } - - /// Get the International Standard Recording Code (ISRC) for a given track from the Spotify API - /// - /// - Parameter song: A song object that contains details about the track e.g. Title and Artist - /// - Parameter accessToken: The access token provided by Spotify to access the API - /// - Returns: A Promise containing either the ISRC as a string or an error if failed - func getISRC(for track: Track, using accessToken: String) -> Promise { - return Promise { resolver in - - // Build URL - let params = [ - "q": "\(track.title) \(track.artist)", - "type": "track", - "limit": "1" - ] - - guard let url = buildURL(host: "api.spotify.com", path: "/v1/search", queryItems: params) else { - resolver.reject(NetworkError.invalidURL(description: "Failed to build URL to get ISRC for song.")) - return - } - - // Initialise request content - let headers = [ - "Accept": "application/json", - "Content-Type": "application/json", - "Authorization": "Bearer \(accessToken)" - ] - - // Create request - var request = URLRequest(url: url) - request.httpMethod = "GET" - request.allHTTPHeaderFields = headers - - // Send HTTP request - URLSession.shared.dataTask(with: request) { data, response, error in - guard let data = data, error == nil else { - resolver.reject(NetworkError.networkError(description: error?.localizedDescription)) - return - } - do { - let decoded = try JSONDecoder().decode(SpotifySearchResult.self, from: data) - guard let isrc = decoded.getISRC() else { - resolver.reject(NetworkError.decodingFailed(description: "Failed to get ISRC from decoded SpotifySearchResult JSON.")) - return - } - resolver.fulfill(isrc) - } catch let error { - resolver.reject(NetworkError.decodingFailed(description: "Failed to decode SpotifySearchResult into JSON.\n\(error.localizedDescription)")) - print(error) - } - }.resume() - - } - } - - /// Get the lyrics from the Musixmatch API given the International Standard Recording Code (ISRC) of the track - /// - /// - Parameter isrc: The International Standard Recording Code (ISRC) is a code that provides a means of identifying audio and video recordings - /// - Returns: A Promise containing either the result model of Musixmatch lyrics or an error if failed - func getLyricsForTrack(with isrc: String) -> Promise { - return Promise { resolver in - - // Build URL - let params = [ - "apikey": Secrets.MUSIXMATCH_API_KEY, - "track_isrc": isrc - ] - - guard let url = buildURL(host: "api.musixmatch.com", path: "/ws/1.1/track.lyrics.get", queryItems: params) else { - resolver.reject(NetworkError.invalidURL(description: "Failed to build URL to get lyrics for track.")) - return - } - - // Create request - var request = URLRequest(url: url) - request.httpMethod = "GET" - - // Send HTTP request - URLSession.shared.dataTask(with: request) { data, response, error in - guard let data = data, error == nil else { - resolver.reject(NetworkError.networkError(description: error?.localizedDescription)) - return - } - do { - let decoded = try JSONDecoder().decode(MusixMatchLyrics.self, from: data) - resolver.fulfill(decoded) - } catch let error { - resolver.reject(NetworkError.decodingFailed(description: "Failed to decode MusixMatchSnippetResult into JSON.\n\(error.localizedDescription)")) - } - }.resume() - - } - } - - // MARK: - Utility Functions - - /// Builds a URL given a host, path and query items - /// - /// - Parameter host: The host of the URL - /// - Parameter path: Path or endpoint, e.g. for an API - /// - Parameter queryItems: Query items for the URL - /// - Returns: A constructed `https` schemed URL with the given components - private func buildURL(host: String, path: String, queryItems: [String: String]? = nil) -> URL? { - var components = URLComponents() - - components.scheme = "https" - components.host = host - components.path = path - if let queryItems = queryItems { - components.queryItems = queryItems.map({ key, value in - URLQueryItem(name: key, value: value) - }) - } - - return components.url - } - -} diff --git a/Jukebox/Networking/ResponseModels/MusixMatchLyrics.swift b/Jukebox/Networking/ResponseModels/MusixMatchLyrics.swift deleted file mode 100644 index 758cb67..0000000 --- a/Jukebox/Networking/ResponseModels/MusixMatchLyrics.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// MusixMatchLyrics.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import Foundation - -struct MusixMatchLyrics: Codable { - let message: Message - - func getLyrics() -> String? { - return message.body.lyrics.lyricsBody - } -} - -struct Message: Codable { - let body: Body -} - -struct Body: Codable { - let lyrics: Lyrics -} - -struct Lyrics: Codable { - let lyricsBody: String - let scriptTrackingURL: String - let pixelTrackingURL: String - let lyricsCopyright: String - - enum CodingKeys: String, CodingKey { - case lyricsBody = "lyrics_body" - case scriptTrackingURL = "script_tracking_url" - case pixelTrackingURL = "pixel_tracking_url" - case lyricsCopyright = "lyrics_copyright" - } -} diff --git a/Jukebox/Networking/ResponseModels/SpotifyClientCredentials.swift b/Jukebox/Networking/ResponseModels/SpotifyClientCredentials.swift deleted file mode 100644 index 9368858..0000000 --- a/Jukebox/Networking/ResponseModels/SpotifyClientCredentials.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SpotifyClientCredentials.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import Foundation - -struct SpotifyClientCredentials: Codable { - let accessToken: String - let tokenType: String - let expiresIn: Int - - enum CodingKeys: String, CodingKey { - case accessToken = "access_token" - case tokenType = "token_type" - case expiresIn = "expires_in" - } -} diff --git a/Jukebox/Networking/ResponseModels/SpotifySearchResult.swift b/Jukebox/Networking/ResponseModels/SpotifySearchResult.swift deleted file mode 100644 index 3c68f09..0000000 --- a/Jukebox/Networking/ResponseModels/SpotifySearchResult.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// SpotifySearchResult.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import Foundation - -struct SpotifySearchResult: Codable { - let tracks: Tracks - - func getISRC() -> String? { - tracks.items.first?.externalIDS.isrc - } -} - -struct Tracks: Codable { - let items: [Item] -} - -struct Item: Codable { - let artists: [Artist] - let externalIDS: ExternalIDS - - enum CodingKeys: String, CodingKey { - case artists - case externalIDS = "external_ids" - } -} - -struct Artist: Codable { - let href: String - let id, name, type, uri: String - - enum CodingKeys: String, CodingKey { - case href, id, name, type, uri - } -} - -struct ExternalIDS: Codable { - let isrc: String -} diff --git a/Jukebox/Utilities/Secrets.swift b/Jukebox/Utilities/Secrets.swift deleted file mode 100644 index 923e629..0000000 --- a/Jukebox/Utilities/Secrets.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// Secrets.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import Foundation - -struct Secrets { - - private init() {} - - static var SPOTIFY_CLIENT_ID: String { - get { - guard let filePath = Bundle.main.path(forResource: "Secrets", ofType: "plist") else { - fatalError("Couldn't find file 'Secrets.plist'.") - } - - let plist = NSDictionary(contentsOfFile: filePath) - guard let value = plist?.object(forKey: "SPOTIFY_CLIENT_ID") as? String else { - fatalError("Couldn't find key 'SPOTIFY_CLIENT_ID' in 'Secrets.plist'.") - } - return value - } - } - - static var SPOTIFY_CLIENT_SECRET: String { - get { - guard let filePath = Bundle.main.path(forResource: "Secrets", ofType: "plist") else { - fatalError("Couldn't find file 'Secrets.plist'.") - } - - let plist = NSDictionary(contentsOfFile: filePath) - guard let value = plist?.object(forKey: "SPOTIFY_CLIENT_SECRET") as? String else { - fatalError("Couldn't find key 'SPOTIFY_CLIENT_SECRET' in 'Secrets.plist'.") - } - return value - } - } - - static var MUSIXMATCH_API_KEY: String { - get { - guard let filePath = Bundle.main.path(forResource: "Secrets", ofType: "plist") else { - fatalError("Couldn't find file 'Secrets.plist'.") - } - - let plist = NSDictionary(contentsOfFile: filePath) - guard let value = plist?.object(forKey: "MUSIXMATCH_API_KEY") as? String else { - fatalError("Couldn't find key 'MUSIXMATCH_API_KEY' in 'Secrets.plist'.") - } - return value - } - } - -} diff --git a/Jukebox/ViewModels/ContentViewModel.swift b/Jukebox/ViewModels/ContentViewModel.swift index bea0bf5..f83f559 100644 --- a/Jukebox/ViewModels/ContentViewModel.swift +++ b/Jukebox/ViewModels/ContentViewModel.swift @@ -7,7 +7,6 @@ import Foundation import SwiftUI -import PromiseKit import ScriptingBridge class ContentViewModel: ObservableObject { @@ -28,10 +27,6 @@ class ContentViewModel: ObservableObject { @Published var trackDuration: Double = 0 @Published var seekerPosition: Double = 0 - // Lyrics - private var accessToken: String? - private var accessTokenExpiryDate: Date? - init() { setupObservers() guard spotifyApp.isRunning else { return } @@ -80,9 +75,6 @@ class ContentViewModel: ObservableObject { print("The play state or the currently playing track changed") getPlayState() getTrackInformation() - - // Shelved for now - // fetchLyrics() } // MARK: - Media & Playback @@ -171,57 +163,4 @@ class ContentViewModel: ObservableObject { popoverIsShown = false } - // MARK: - Lyrics - - private func fetchAccessToken() { - - print("Fetching Access Token...") - - let networkManager = NetworkManager.shared - - firstly { - networkManager.getSpotifyAccessToken() - }.done { tokenInfo in - self.accessToken = tokenInfo.accessToken - self.accessTokenExpiryDate = Date().addingTimeInterval(TimeInterval(tokenInfo.expiresIn)) - self.fetchLyrics() - }.catch { error in - print(error.localizedDescription) - self.track.lyrics = "Something went wrong..." - } - - } - - private func fetchLyrics() { - - let networkManager = NetworkManager.shared - - // If access token or access token expiry is nil, fetch access token - guard let accessToken = accessToken, let expiryDate = accessTokenExpiryDate else { - fetchAccessToken() - return - } - - // If access token is expired, fetch access token - guard Date() < expiryDate else { - fetchAccessToken() - return - } - - print("Fetching Lyrics...") - - // Fetch the lyrics for the currently playing song - firstly { - networkManager.getISRC(for: track, using: accessToken) - }.then { isrc in - networkManager.getLyricsForTrack(with: isrc) - }.done { musixMatchLyrics in - self.track.lyrics = musixMatchLyrics.getLyrics() ?? "No lyrics for current song..." - }.catch { error in - print(error.localizedDescription) - self.track.lyrics = "No lyrics for current song..." - } - - } - } diff --git a/Jukebox/Views/ContentView.swift b/Jukebox/Views/ContentView.swift index 6561c89..8d0519e 100644 --- a/Jukebox/Views/ContentView.swift +++ b/Jukebox/Views/ContentView.swift @@ -18,12 +18,6 @@ struct ContentView: View { // States for animations @State private var isShowingPlaybackControls = false - /* Currently not being used, Lyrics has been shelved for now - @State private var showingLyrics = false - @State private var playbackScale = 1.0 - @State private var lyricsScale = 1.2 - */ - // Constants let primaryOpacity = 0.8 let primaryOpacity2 = 0.6 diff --git a/Jukebox/Views/LyricsView.swift b/Jukebox/Views/LyricsView.swift deleted file mode 100644 index 81508d9..0000000 --- a/Jukebox/Views/LyricsView.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// LyricsView.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 27/10/21. -// - -import SwiftUI - -struct LyricsView: View { - - // User Defaults - @AppStorage("visualizerStyle") private var visualizerStyle: VisualizerStyle = .albumArt - - // Properties - let lyrics: String - @Binding var showingLyrics: Bool - @Binding var playbackScale: Double - @Binding var lyricsScale: Double - - // Constants - let primaryOpacity = 0.8 - - var body: some View { - ZStack { - VStack { - HStack { - Button { - showingLyrics = false - playbackScale = 1.0 - lyricsScale = 1.2 - } label: { - Image(systemName: "xmark.circle.fill") - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(primaryOpacity) - : .primary.opacity(primaryOpacity)) - } - .pressButtonStyle() - Spacer() - } - Spacer() - } - VStack(alignment: .center) { - Text(lyrics) - .multilineTextAlignment(.center) - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(primaryOpacity) - : .primary.opacity(primaryOpacity)) - .font(.system(size: 20, weight: .bold)) - } - .padding(22) - } - .padding() - } -} diff --git a/Jukebox/Views/MarqueeText.swift b/Jukebox/Views/MarqueeText.swift deleted file mode 100644 index cc99599..0000000 --- a/Jukebox/Views/MarqueeText.swift +++ /dev/null @@ -1,90 +0,0 @@ -// -// MarqueeText.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 30/10/21. -// - -import Foundation - -import SwiftUI - -struct MarqueeText: View { - - @ObservedObject private var contentViewVM: ContentViewModel - private var font: NSFont - private var delay: Double - - @State private var textWidth: CGFloat - @State private var textHeight: CGFloat - @State private var isAnimating = false - @State private var viewID = 0 - - init(contentViewVM: ContentViewModel, font: NSFont, delay: Double) { - self.contentViewVM = contentViewVM - self.font = font - self.delay = delay - - textWidth = contentViewVM.track.title.stringWidth(with: font) - textHeight = contentViewVM.track.title.stringHeight(with: font) - } - - var body : some View { - GeometryReader { geo in - ZStack { - Text(contentViewVM.track.title) - .font(Font(font)) - .foregroundColor(.white).opacity(0.8) - .lineLimit(1) - .offset(x: self.isAnimating ? -textWidth - 16 : 0) - .fixedSize(horizontal: true, vertical: true) - .animation(.linear(duration: textWidth / 60).delay(delay).repeat(while: isAnimating)) - - Text(contentViewVM.track.title) - .font(Font(font)) - .foregroundColor(.black).opacity(0.8) - .lineLimit(1) - .offset(x: self.isAnimating ? 0 : textWidth + 16) - .fixedSize(horizontal: true, vertical: true) - .animation(.linear(duration: textWidth / 60).delay(delay).repeat(while: isAnimating)) - .opacity(isAnimating ? 1 : 0) - } - .id(viewID) - .onChange(of: contentViewVM.track.title, perform: { _ in - self.textWidth = contentViewVM.track.title.stringWidth(with: font) - self.textHeight = contentViewVM.track.title.stringHeight(with: font) - if (textWidth > geo.size.width) { - self.isAnimating = true - } else { - self.isAnimating = false - } - }) - .onAppear { - print("APPEARED") - print("Title: \(contentViewVM.track.title)") - print(textWidth) - print(textHeight) - if (textWidth > geo.size.width) { - self.isAnimating = true - } else { - self.isAnimating = false - } - } - } - .id(viewID) - .frame(height: textHeight) - .onTapGesture { - self.viewID += 1 - } - } -} - -extension Animation { - func `repeat`(while expression: Bool, autoreverses: Bool = false) -> Animation { - if expression { - return self.repeatForever(autoreverses: autoreverses) - } else { - return self - } - } -} diff --git a/Jukebox/Views/Seeker.swift b/Jukebox/Views/Seeker.swift deleted file mode 100644 index 13ec67d..0000000 --- a/Jukebox/Views/Seeker.swift +++ /dev/null @@ -1,101 +0,0 @@ -// -// Seeker.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 26/10/21. -// - -import SwiftUI - -struct Seeker: View { - - // User Defaults - @AppStorage("visualizerStyle") private var visualizerStyle: VisualizerStyle = .albumArt - @AppStorage("swipeToSeek") private var swipeToSeek = false - - // Properties - var trackDuration: Double - @Binding var seekerPosition: Double - let onEditingChanged: (Bool) -> Void - - // States for animations - @State private var seekerHeight: CGFloat = 4 - - // Constants - let primaryOpacity = 0.6 - let ternaryOpacity = 0.2 - - var body: some View { - GeometryReader { geo in - VStack { - ZStack(alignment: .leading) { - Rectangle() - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(ternaryOpacity) - : .primary.opacity(ternaryOpacity)) - Rectangle() - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(primaryOpacity) - : .primary.opacity(primaryOpacity)) - .frame(width: geo.size.width * CGFloat(self.seekerPosition / trackDuration)) -// .animation(.easeInOut, value: self.seekerPosition) - if swipeToSeek { - SwipeView(seekerPosition: self.$seekerPosition, onEditingChanged: onEditingChanged) - } - } - .frame(width: geo.size.width, height: seekerHeight) - .cornerRadius(6) - .gesture( - DragGesture(minimumDistance: 0) - .onChanged({ value in - onEditingChanged(true) - self.seekerPosition = min(max(0, Double(value.location.x / geo.size.width * trackDuration)), trackDuration) - }) - .onEnded({ _ in - onEditingChanged(false) - })) - HStack { - Text(formatSecondsForDisplay(seekerPosition)) - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(primaryOpacity) - : .primary.opacity(primaryOpacity)) - .font(.caption) - Spacer() - Text(formatSecondsForDisplay(trackDuration)) - .foregroundColor( - visualizerStyle != .none - ? .white.opacity(primaryOpacity) - : .primary.opacity(primaryOpacity)) - .font(.caption) - } - } - .frame(width: geo.size.width, height: geo.size.height) - .onHover { hovered in - if (hovered) { - withAnimation(.timingCurve(0.12, 0.76, 0.44, 0.99)) { - self.seekerHeight = 16 - } - } else { - withAnimation(.timingCurve(0.12, 0.76, 0.44, 0.99)) { - self.seekerHeight = 4 - } - } - } - } - } - - private func formatSecondsForDisplay(_ seconds: Double) -> String { - let date = Date.init(timeIntervalSince1970: seconds) - let hours = Int(seconds / 3600) - - let formatter = DateFormatter() - if (hours > 0) { formatter.dateFormat = "H:m:ss" } - else { formatter.dateFormat = "m:ss" } - - return formatter.string(from: date) - } - -} diff --git a/Jukebox/Views/SwipeView.swift b/Jukebox/Views/SwipeView.swift deleted file mode 100644 index fbd3b13..0000000 --- a/Jukebox/Views/SwipeView.swift +++ /dev/null @@ -1,89 +0,0 @@ -// -// SwipeView.swift -// Jukebox -// -// Created by Sasindu Jayasinghe on 30/10/21. -// - -import Foundation -import AppKit -import SwiftUI - -protocol NSSwipeViewDelegate: AnyObject { - - func didSwipe(with event: NSEvent) - - func swipeEnded(with event: NSEvent) - -} - -class NSSwipeView: NSView { - - weak var delegate: NSSwipeViewDelegate! - - override init(frame frameRect: NSRect) { - super.init(frame: frameRect) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - private func handleSwipe(with event: NSEvent) { - - switch event.phase { - case .ended: - delegate.swipeEnded(with: event) - default: - delegate.didSwipe(with: event) - } - - } - - override func scrollWheel(with event: NSEvent) { - handleSwipe(with: event) - } - -} - -struct SwipeView: NSViewRepresentable { - - @Binding var seekerPosition: Double - let onEditingChanged: (Bool) -> Void - - func makeNSView(context: Context) -> NSSwipeView { - let swipeView = NSSwipeView() - swipeView.delegate = context.coordinator - return swipeView - } - - func updateNSView(_ nsView: NSSwipeView, context: Context) {} - -} - -extension SwipeView { - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, NSSwipeViewDelegate { - - let parent: SwipeView - - init(_ parent: SwipeView) { - self.parent = parent - } - - func didSwipe(with event: NSEvent) { - parent.onEditingChanged(true) - parent.seekerPosition += event.deltaX - } - - func swipeEnded(with event: NSEvent) { - parent.onEditingChanged(false) - } - - } - -}