From afbc8d597eb6674a9b38e0cb6a93099652f45f3b Mon Sep 17 00:00:00 2001 From: eevee Date: Sat, 22 Jun 2024 00:36:51 +0300 Subject: [PATCH] fallback reasons --- .../EeveeSpotify/Lyrics/CustomLyrics.x.swift | 79 +++++++++++++++++-- .../MusixmatchLyricsDataSource.swift | 67 ++++++++++------ .../Lyrics/Models/LyricsError.swift | 12 ++- .../Extensions/UserDefaults+Extension.swift | 10 +++ .../Headers/SPTEncoreAttributedString.swift | 8 ++ .../Models/Headers/SPTEncoreAttributes.swift | 7 ++ .../Models/Headers/SPTEncoreLabel.swift | 6 ++ .../Models/Headers/SPTEncorePopUpDialog.swift | 2 +- .../Headers/SPTEncorePopUpPresenter.swift | 2 +- ...ettingsViewController+LyricsSections.swift | 8 ++ 10 files changed, 164 insertions(+), 37 deletions(-) create mode 100644 Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributedString.swift create mode 100644 Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributes.swift create mode 100644 Sources/EeveeSpotify/Models/Headers/SPTEncoreLabel.swift diff --git a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift index f4b61368..38b3f47f 100644 --- a/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift +++ b/Sources/EeveeSpotify/Lyrics/CustomLyrics.x.swift @@ -28,7 +28,64 @@ class EncoreButtonHook: ClassHook { } } +// + +private var lastLyricsError: LyricsError? = nil + private var hasShownRestrictedPopUp = false +private var hasShownUnauthorizedPopUp = false + +// + +class LyricsOnlyViewControllerHook: ClassHook { + + static let targetName = "Lyrics_NPVCommunicatorImpl.LyricsOnlyViewController" + + func viewDidLoad() { + + orig.viewDidLoad() + + if !UserDefaults.fallbackReasons { + return + } + + guard + let lyricsHeaderViewController = target.parent?.children.first, + let lyricsLabel = lyricsHeaderViewController.view.subviews.first + else { + return + } + + let encoreLabel = Dynamic.convert(lyricsLabel, to: SPTEncoreLabel.self) + + let attributedString = Dynamic.convert( + encoreLabel.text().firstObject as AnyObject, + to: SPTEncoreAttributedString.self + ) + + var text = [attributedString] + + if let description = lastLyricsError?.description { + + let attributes = Dynamic.SPTEncoreAttributes + .alloc(interface: SPTEncoreAttributes.self) + .`init`({ attributes in + attributes.setForegroundColor(.white.withAlphaComponent(0.5)) + }) + + text.append( + Dynamic.SPTEncoreAttributedString.alloc(interface: SPTEncoreAttributedString.self) + .initWithString( + "\nFallback: \(description)", + typeStyle: attributedString.typeStyle(), + attributes: attributes + ) + ) + } + + encoreLabel.setText(text as NSArray) + } +} func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { @@ -47,20 +104,28 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { spotifyTrackId: track.URI().spt_trackIdentifier(), source: source ) + + lastLyricsError = nil } catch let error as LyricsError { + lastLyricsError = error + switch error { case .InvalidMusixmatchToken: - PopUpHelper.showPopUp( - delayed: false, - message: "The tweak is unable to load lyrics from Musixmatch due to Unauthorized error. Please check or update your Musixmatch token. If you use an iPad, you should get the token from the Musixmatch app for iPad.", - buttonText: "OK" - ) - break + if !hasShownUnauthorizedPopUp { + + PopUpHelper.showPopUp( + delayed: false, + message: "The tweak is unable to load lyrics from Musixmatch due to Unauthorized error. Please check or update your Musixmatch token. If you use an iPad, you should get the token from the Musixmatch app for iPad.", + buttonText: "OK" + ) + + hasShownUnauthorizedPopUp.toggle() + } case .MusixmatchRestricted: @@ -75,8 +140,6 @@ func getCurrentTrackLyricsData(originalLyrics: Lyrics? = nil) throws -> Data { hasShownRestrictedPopUp.toggle() } - break - default: break } diff --git a/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift b/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift index 06d2ffd5..5ca5cf34 100644 --- a/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift +++ b/Sources/EeveeSpotify/Lyrics/DataSources/MusixmatchLyricsDataSource.swift @@ -68,42 +68,57 @@ struct MusixmatchLyricsDataSource { else { throw LyricsError.DecodingError } - - if let header = message["header"] as? [String: Any], - header["status_code"] as? Int == 401 { + + if let header = message["header"] as? [String: Any], + header["status_code"] as? Int == 401 { throw LyricsError.InvalidMusixmatchToken } if let trackSubtitlesGet = macroCalls["track.subtitles.get"] as? [String: Any], - let subtitlesMessage = trackSubtitlesGet["message"] as? [String: Any], - let subtitlesBody = subtitlesMessage["body"] as? [String: Any], - let subtitlesList = subtitlesBody["subtitle_list"] as? [Any], - let firstSubtitle = subtitlesList.first as? [String: Any], - let subtitle = firstSubtitle["subtitle"] as? [String: Any] { - - if let restricted = subtitle["restricted"] as? Bool, restricted { - throw LyricsError.MusixmatchRestricted + let subtitlesMessage = trackSubtitlesGet["message"] as? [String: Any], + let subtitlesHeader = subtitlesMessage["header"] as? [String: Any], + let subtitlesStatusCode = subtitlesHeader["status_code"] as? Int { + + if subtitlesStatusCode == 404 { + throw LyricsError.NoSuchSong } - if let subtitleBody = subtitle["subtitle_body"] as? String { - return PlainLyrics(content: subtitleBody, timeSynced: true) + if let subtitlesBody = subtitlesMessage["body"] as? [String: Any], + let subtitleList = subtitlesBody["subtitle_list"] as? [[String: Any]], + let firstSubtitle = subtitleList.first, + let subtitle = firstSubtitle["subtitle"] as? [String: Any] { + + if let restricted = subtitle["restricted"] as? Bool, restricted { + throw LyricsError.MusixmatchRestricted + } + + if let subtitleBody = subtitle["subtitle_body"] as? String { + return PlainLyrics(content: subtitleBody, timeSynced: true) + } } } - guard - let trackLyricsGet = macroCalls["track.lyrics.get"] as? [String: Any], - let lyricsMessage = trackLyricsGet["message"] as? [String: Any], - let lyricsBody = lyricsMessage["body"] as? [String: Any], - let lyrics = lyricsBody["lyrics"] as? [String: Any], - let plainLyrics = lyrics["lyrics_body"] as? String - else { - throw LyricsError.DecodingError - } - - if let restricted = lyrics["restricted"] as? Bool, restricted { - throw LyricsError.MusixmatchRestricted + if let trackLyricsGet = macroCalls["track.lyrics.get"] as? [String: Any], + let lyricsMessage = trackLyricsGet["message"] as? [String: Any], + let lyricsHeader = lyricsMessage["header"] as? [String: Any], + let lyricsStatusCode = lyricsHeader["status_code"] as? Int { + + if lyricsStatusCode == 404 { + throw LyricsError.NoSuchSong + } + + if let lyricsBody = lyricsMessage["body"] as? [String: Any], + let lyrics = lyricsBody["lyrics"] as? [String: Any], + let plainLyrics = lyrics["lyrics_body"] as? String { + + if let restricted = lyrics["restricted"] as? Bool, restricted { + throw LyricsError.MusixmatchRestricted + } + + return PlainLyrics(content: plainLyrics, timeSynced: false) + } } - return PlainLyrics(content: plainLyrics, timeSynced: false) + throw LyricsError.DecodingError } } diff --git a/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift b/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift index 05a0d22f..a16ba379 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/LyricsError.swift @@ -1,9 +1,19 @@ import Foundation -enum LyricsError: Error { +enum LyricsError: Error, CustomStringConvertible { case NoCurrentTrack case MusixmatchRestricted case InvalidMusixmatchToken case DecodingError case NoSuchSong + + var description: String { + switch self { + case .NoSuchSong: "No Song Found" + case .MusixmatchRestricted: "Restricted" + case .InvalidMusixmatchToken: "Unauthorized" + case .DecodingError: "Decoding Error" + case .NoCurrentTrack: "No Track Instance" + } + } } diff --git a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift index 196b8512..e5ad1b33 100644 --- a/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift +++ b/Sources/EeveeSpotify/Models/Extensions/UserDefaults+Extension.swift @@ -7,6 +7,7 @@ extension UserDefaults { private static let lyricsSourceKey = "lyricsSource" private static let musixmatchTokenKey = "musixmatchToken" private static let geniusFallbackKey = "geniusFallback" + private static let fallbackReasonsKey = "fallbackReasons" private static let darkPopUpsKey = "darkPopUps" private static let patchTypeKey = "patchType" private static let overwriteConfigurationKey = "overwriteConfiguration" @@ -42,6 +43,15 @@ extension UserDefaults { defaults.set(fallback, forKey: geniusFallbackKey) } } + + static var fallbackReasons: Bool { + get { + defaults.object(forKey: fallbackReasonsKey) as? Bool ?? true + } + set (reasons) { + defaults.set(reasons, forKey: fallbackReasonsKey) + } + } static var darkPopUps: Bool { get { diff --git a/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributedString.swift b/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributedString.swift new file mode 100644 index 00000000..c4fb4663 --- /dev/null +++ b/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributedString.swift @@ -0,0 +1,8 @@ +import Foundation + +@objc protocol SPTEncoreAttributedString { + func initWithString(_ string: String, typeStyle: Any, attributes: Any) -> SPTEncoreAttributedString + func text() -> String + func typeStyle() -> Any + func attributes() -> SPTEncoreAttributes +} diff --git a/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributes.swift b/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributes.swift new file mode 100644 index 00000000..1f16f73a --- /dev/null +++ b/Sources/EeveeSpotify/Models/Headers/SPTEncoreAttributes.swift @@ -0,0 +1,7 @@ +import UIKit + +@objc protocol SPTEncoreAttributes { + func `init`(_: (SPTEncoreAttributes) -> Void) -> SPTEncoreAttributes + func foregroundColor() -> UIColor + func setForegroundColor(_ color: UIColor) +} diff --git a/Sources/EeveeSpotify/Models/Headers/SPTEncoreLabel.swift b/Sources/EeveeSpotify/Models/Headers/SPTEncoreLabel.swift new file mode 100644 index 00000000..81e09392 --- /dev/null +++ b/Sources/EeveeSpotify/Models/Headers/SPTEncoreLabel.swift @@ -0,0 +1,6 @@ +import Foundation + +@objc protocol SPTEncoreLabel { + func text() -> NSArray + func setText(_ text: NSArray) +} diff --git a/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpDialog.swift b/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpDialog.swift index ce585795..48c29eba 100644 --- a/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpDialog.swift +++ b/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpDialog.swift @@ -9,4 +9,4 @@ import Foundation @objc enum ClickState: Int { case primary case secondary -} \ No newline at end of file +} diff --git a/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpPresenter.swift b/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpPresenter.swift index d513b64d..462d9862 100644 --- a/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpPresenter.swift +++ b/Sources/EeveeSpotify/Models/Headers/SPTEncorePopUpPresenter.swift @@ -4,4 +4,4 @@ import Foundation static func shared() -> SPTEncorePopUpPresenter func presentPopUp(_ popUp: SPTEncorePopUpDialog) func dismissPopupWithAnimate(_ animate: Bool, clearQueue: Bool, completion: Any?) -} \ No newline at end of file +} diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift index 46e38a9e..8e4dc8bc 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsViewController+LyricsSections.swift @@ -116,6 +116,14 @@ If the tweak is unable to find a song or process the lyrics, you'll see a "Could set: { UserDefaults.geniusFallback = $0 } ) ) + + Toggle( + "Show Fallback Reasons", + isOn: Binding( + get: { UserDefaults.fallbackReasons }, + set: { UserDefaults.fallbackReasons = $0 } + ) + ) } } }