mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
v5.5
This commit is contained in:
@@ -5,21 +5,17 @@ import SwiftUI
|
||||
struct DarkPopUps: HookGroup { }
|
||||
|
||||
class EncoreLabelHook: ClassHook<UIView> {
|
||||
|
||||
typealias Group = DarkPopUps
|
||||
static let targetName = "SPTEncoreLabel"
|
||||
|
||||
func intrinsicContentSize() -> CGSize {
|
||||
if let viewController = WindowHelper.shared.viewController(for: target),
|
||||
NSStringFromClass(type(of: viewController)) == "SPTEncorePopUpContainer"
|
||||
{
|
||||
let label = Dynamic.convert(target.subviews.first!, to: UILabel.self)
|
||||
|
||||
if let viewController = WindowHelper.shared.viewController(for: target) {
|
||||
|
||||
if NSStringFromClass(type(of: viewController)) == "SPTEncorePopUpContainer" {
|
||||
|
||||
let label = Dynamic.convert(target.subviews.first!, to: UILabel.self)
|
||||
|
||||
if !label.hasParent(matching: "Primary") {
|
||||
label.textColor = .white
|
||||
}
|
||||
if !label.hasParent(matching: "Primary") {
|
||||
label.textColor = .white
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +24,6 @@ class EncoreLabelHook: ClassHook<UIView> {
|
||||
}
|
||||
|
||||
class SPTEncorePopUpContainerHook: ClassHook<UIViewController> {
|
||||
|
||||
typealias Group = DarkPopUps
|
||||
static let targetName = "SPTEncorePopUpContainer"
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import UIKit
|
||||
import Orion
|
||||
|
||||
class PopUpHelper {
|
||||
|
||||
struct PopUpHelper {
|
||||
private static var isPopUpShowing = false
|
||||
|
||||
static let sharedPresenter = type(
|
||||
@@ -19,9 +18,7 @@ class PopUpHelper {
|
||||
onPrimaryClick: (() -> Void)? = nil,
|
||||
onSecondaryClick: (() -> Void)? = nil
|
||||
) {
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: delayed ? .now() + 3.0 : .now()) {
|
||||
|
||||
if isPopUpShowing {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import UIKit
|
||||
|
||||
class URLSessionHelper {
|
||||
|
||||
static let shared = URLSessionHelper()
|
||||
|
||||
private var requestsMap: [URL:Data]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import UIKit
|
||||
|
||||
class WindowHelper {
|
||||
|
||||
struct WindowHelper {
|
||||
static let shared = WindowHelper()
|
||||
|
||||
let window: UIWindow
|
||||
@@ -17,7 +16,6 @@ class WindowHelper {
|
||||
}
|
||||
|
||||
func findFirstViewController(_ regex: String) -> UIViewController? {
|
||||
|
||||
let rootView = self.rootViewController.view!
|
||||
var result: UIViewController?
|
||||
|
||||
@@ -37,6 +35,10 @@ class WindowHelper {
|
||||
searchViews(rootView)
|
||||
return result
|
||||
}
|
||||
|
||||
func dismissCurrentViewController() {
|
||||
rootViewController.dismiss(animated: true)
|
||||
}
|
||||
|
||||
func overrideUserInterfaceStyle(_ style: UIUserInterfaceStyle) {
|
||||
window.overrideUserInterfaceStyle = style
|
||||
|
||||
@@ -185,7 +185,7 @@ private func loadLyricsForCurrentTrack() throws {
|
||||
PopUpHelper.showPopUp(
|
||||
delayed: false,
|
||||
message: "musixmatch_unauthorized_popup".localized,
|
||||
buttonText: "OK"
|
||||
buttonText: "OK".uiKitLocalized
|
||||
)
|
||||
|
||||
hasShownUnauthorizedPopUp.toggle()
|
||||
@@ -197,7 +197,7 @@ private func loadLyricsForCurrentTrack() throws {
|
||||
PopUpHelper.showPopUp(
|
||||
delayed: false,
|
||||
message: "musixmatch_restricted_popup".localized,
|
||||
buttonText: "OK"
|
||||
buttonText: "OK".uiKitLocalized
|
||||
)
|
||||
|
||||
hasShownRestrictedPopUp.toggle()
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
struct GeniusLyricsRepository: LyricsRepository {
|
||||
|
||||
private let jsonDecoder: JSONDecoder
|
||||
private let apiUrl = "https://api.genius.com"
|
||||
private let session: URLSession
|
||||
|
||||
@@ -2,7 +2,6 @@ import Foundation
|
||||
import UIKit
|
||||
|
||||
class MusixmatchLyricsRepository: LyricsRepository {
|
||||
|
||||
private let apiUrl = "https://apic.musixmatch.com"
|
||||
|
||||
var selectedLanguage: String
|
||||
@@ -19,9 +18,7 @@ class MusixmatchLyricsRepository: LyricsRepository {
|
||||
_ path: String,
|
||||
query: [String: Any] = [:]
|
||||
) throws -> Data {
|
||||
|
||||
var stringUrl = "\(apiUrl)\(path)"
|
||||
|
||||
var finalQuery = query
|
||||
|
||||
finalQuery["usertoken"] = UserDefaults.musixmatchToken
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import NaturalLanguage
|
||||
|
||||
extension String {
|
||||
@@ -10,6 +11,11 @@ extension String {
|
||||
BundleHelper.shared.localizedString(self)
|
||||
}
|
||||
|
||||
var uiKitLocalized: String {
|
||||
let bundle = Bundle(for: UIApplication.self)
|
||||
return bundle.localizedString(forKey: self, value: nil, table: nil)
|
||||
}
|
||||
|
||||
func localizeWithFormat(_ arguments: CVarArg...) -> String{
|
||||
String(format: self.localized, arguments: arguments)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ import Orion
|
||||
import UIKit
|
||||
|
||||
class UIOpenURLContextHook: ClassHook<UIOpenURLContext> {
|
||||
|
||||
func URL() -> URL {
|
||||
|
||||
let url = orig.URL()
|
||||
|
||||
if url.isOpenSpotifySafariExtension {
|
||||
|
||||
@@ -4,7 +4,7 @@ private func showHavePremiumPopUp() {
|
||||
PopUpHelper.showPopUp(
|
||||
delayed: true,
|
||||
message: "have_premium_popup".localized,
|
||||
buttonText: "ok".localized
|
||||
buttonText: "OK".uiKitLocalized
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ class StreamQualitySettingsSectionHook: ClassHook<NSObject> {
|
||||
func shouldResetSelection() -> Bool {
|
||||
PopUpHelper.showPopUp(
|
||||
message: "high_audio_quality_popup".localized,
|
||||
buttonText: "ok".localized
|
||||
buttonText: "OK".uiKitLocalized
|
||||
)
|
||||
|
||||
return true
|
||||
@@ -20,7 +20,7 @@ class StreamQualitySettingsSectionHook: ClassHook<NSObject> {
|
||||
private func showOfflineModePopUp() {
|
||||
PopUpHelper.showPopUp(
|
||||
message: "playlist_downloading_popup".localized,
|
||||
buttonText: "ok".localized
|
||||
buttonText: "OK".uiKitLocalized
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ class ContentOffliningUIHelperImplementationHook: ClassHook<NSObject> {
|
||||
pageIdentifier: String,
|
||||
pageURI: URL
|
||||
) -> String {
|
||||
if pageIdentifier == "spotify:local-files" {
|
||||
return orig.downloadToggledWithCurrentAvailability(
|
||||
availability,
|
||||
addAction: addAction,
|
||||
removeAction: removeAction,
|
||||
pageIdentifier: pageIdentifier,
|
||||
pageURI: pageURI
|
||||
)
|
||||
}
|
||||
|
||||
showOfflineModePopUp()
|
||||
return pageIdentifier
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class ProfileSettingsSectionHook: ClassHook<NSObject> {
|
||||
|
||||
static let targetName = "ProfileSettingsSection"
|
||||
|
||||
func numberOfRows() -> Int {
|
||||
@@ -11,9 +10,7 @@ class ProfileSettingsSectionHook: ClassHook<NSObject> {
|
||||
}
|
||||
|
||||
func didSelectRow(_ row: Int) {
|
||||
|
||||
if row == 1 {
|
||||
|
||||
let rootSettingsController = WindowHelper.shared.findFirstViewController(
|
||||
"RootSettingsViewController"
|
||||
)!
|
||||
@@ -62,9 +59,7 @@ class ProfileSettingsSectionHook: ClassHook<NSObject> {
|
||||
}
|
||||
|
||||
func cellForRow(_ row: Int) -> UITableViewCell {
|
||||
|
||||
if row == 1 {
|
||||
|
||||
let settingsTableCell = Dynamic.SPTSettingsTableViewCell
|
||||
.alloc(interface: SPTSettingsTableViewCell.self)
|
||||
.initWithStyle(3, reuseIdentifier: "EeveeSpotify")
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import Foundation
|
||||
|
||||
struct GitHubHelper {
|
||||
private let apiUrl = "https://api.github.com"
|
||||
private let decoder = JSONDecoder()
|
||||
|
||||
static let shared = GitHubHelper()
|
||||
|
||||
init() {
|
||||
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
}
|
||||
|
||||
private func perform(_ path: String) async throws -> Data {
|
||||
let url = URL(string: "\(apiUrl)\(path)")!
|
||||
let (data, _) = try await URLSession.shared.data(from: url)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func getLatestRelease() async throws -> GitHubRelease {
|
||||
let data = try await perform("/repos/whoeevee/EeveeSpotify/releases/latest")
|
||||
return try decoder.decode(GitHubRelease.self, from: data)
|
||||
}
|
||||
|
||||
func getUser(_ username: String) async throws -> GitHubUser {
|
||||
let data = try await perform("/users/\(username)")
|
||||
return try decoder.decode(GitHubUser.self, from: data)
|
||||
}
|
||||
|
||||
func getContributors() async throws -> [GitHubUser] {
|
||||
let data = try await perform("/repos/whoeevee/EeveeSpotify/contributors")
|
||||
return try decoder.decode([GitHubUser].self, from: data)
|
||||
}
|
||||
|
||||
func getEeveeContributorSections() async throws -> [EeveeContributorSection] {
|
||||
let (data, _) = try await URLSession.shared.data(
|
||||
from: URL(
|
||||
string: "https://raw.githubusercontent.com/whoeevee/EeveeSpotify/swift/repo.altsource.json"
|
||||
)!
|
||||
)
|
||||
return try decoder.decode([EeveeContributorSection].self, from: data)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
struct EeveeContributor: Decodable, Equatable {
|
||||
var username: String
|
||||
var roles: [String]
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
struct EeveeContributorSection: Decodable, Equatable {
|
||||
var title: String
|
||||
var shuffled: Bool
|
||||
var contributors: [EeveeContributor]
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
struct GitHubRelease: Decodable {
|
||||
var tagName: String
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
struct GitHubReleaseInfo: Decodable {
|
||||
var tag_name: String
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
struct GitHubUser: Decodable, Equatable {
|
||||
var avatarUrl: String
|
||||
var htmlUrl: String
|
||||
var login: String
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import SwiftUI
|
||||
import UIKit
|
||||
|
||||
class ImageViewModel: ObservableObject {
|
||||
@Published var image: UIImage?
|
||||
|
||||
private var imageCache: NSCache<NSString, UIImage>?
|
||||
|
||||
init(urlString: String?) {
|
||||
loadImage(urlString: urlString)
|
||||
}
|
||||
|
||||
private func loadImage(urlString: String?) {
|
||||
guard let urlString = urlString else { return }
|
||||
|
||||
if let imageFromCache = getImageFromCache(from: urlString) {
|
||||
self.image = imageFromCache
|
||||
return
|
||||
}
|
||||
|
||||
loadImageFromURL(urlString: urlString)
|
||||
}
|
||||
|
||||
private func loadImageFromURL(urlString: String) {
|
||||
guard let url = URL(string: urlString) else { return }
|
||||
|
||||
URLSession.shared.dataTask(with: url) { data, response, error in
|
||||
guard error == nil else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let data = data else {
|
||||
return
|
||||
}
|
||||
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
guard let loadedImage = UIImage(data: data) else { return }
|
||||
self?.image = loadedImage
|
||||
self?.setImageCache(image: loadedImage, key: urlString)
|
||||
}
|
||||
}.resume()
|
||||
}
|
||||
|
||||
private func setImageCache(image: UIImage, key: String) {
|
||||
imageCache?.setObject(image, forKey: key as NSString)
|
||||
}
|
||||
|
||||
private func getImageFromCache(from key: String) -> UIImage? {
|
||||
return imageCache?.object(forKey: key as NSString) as? UIImage
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EeveeContributorView: View {
|
||||
var contributor: EeveeContributor
|
||||
var githubUser: GitHubUser
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Link(destination: URL(string: githubUser.htmlUrl)!) {
|
||||
HStack(spacing: 10) {
|
||||
ImageView(urlString: githubUser.avatarUrl)
|
||||
.frame(width: 48, height: 48)
|
||||
.clipShape(Circle())
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(contributor.username)
|
||||
.foregroundColor(.white)
|
||||
.font(.headline)
|
||||
|
||||
ForEach(contributor.roles, id: \.self) { role in
|
||||
Text(role)
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
ChevronRightView()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EeveeContributorsSheetView: View {
|
||||
@State private var users: [GitHubUser] = []
|
||||
@State private var sections: [EeveeContributorSection] = []
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
if users.isEmpty && sections.isEmpty {
|
||||
ProgressView("Loading".uiKitLocalized)
|
||||
}
|
||||
else {
|
||||
List {
|
||||
ForEach(sections, id: \.title) { section in
|
||||
Section {
|
||||
ForEach(
|
||||
section.shuffled
|
||||
? section.contributors.shuffled()
|
||||
: section.contributors,
|
||||
id: \.username
|
||||
) { contributor in
|
||||
if let user = users.first(where: { $0.login == contributor.username }) {
|
||||
EeveeContributorView(contributor: contributor, githubUser: user)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text(section.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("contributors".localized)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
|
||||
.toolbar {
|
||||
Button {
|
||||
WindowHelper.shared.dismissCurrentViewController()
|
||||
} label: {
|
||||
Text("Done".uiKitLocalized)
|
||||
.font(.headline)
|
||||
}
|
||||
}
|
||||
|
||||
.animation(.default, value: users)
|
||||
.animation(.default, value: sections)
|
||||
|
||||
.onAppear {
|
||||
Task {
|
||||
users = try await GitHubHelper.shared.getContributors()
|
||||
sections = try await GitHubHelper.shared.getEeveeContributorSections()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EeveeSettingsVersionView: View {
|
||||
@State private var latestVersion: String?
|
||||
@State private var isPresentingContributorsSheet = false
|
||||
|
||||
private func loadVersion() async throws {
|
||||
let release = try await GitHubHelper.shared.getLatestRelease()
|
||||
latestVersion = String(release.tagName.dropFirst(5)) // swiftX.X
|
||||
}
|
||||
|
||||
private var isUpdateAvailable: Bool {
|
||||
latestVersion != nil && latestVersion != EeveeSpotify.version
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
if isUpdateAvailable {
|
||||
Link(
|
||||
"update_available".localized,
|
||||
destination: URL(string: "https://github.com/whoeevee/EeveeSpotify/releases")!
|
||||
)
|
||||
}
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("v\(EeveeSpotify.version)")
|
||||
|
||||
if latestVersion == nil {
|
||||
HStack(spacing: 10) {
|
||||
ProgressView()
|
||||
Text("checking_for_update".localized)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Button("\("contributors".localized)...") {
|
||||
isPresentingContributorsSheet = true
|
||||
}
|
||||
.foregroundColor(.gray)
|
||||
.font(.subheadline.weight(.semibold))
|
||||
}
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $isPresentingContributorsSheet) {
|
||||
EeveeContributorsSheetView()
|
||||
}
|
||||
|
||||
.animation(.default, value: latestVersion)
|
||||
|
||||
.onAppear {
|
||||
Task {
|
||||
try await loadVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
extension EeveeSettingsView {
|
||||
func loadVersion() async throws {
|
||||
let (data, _) = try await URLSession.shared.data(
|
||||
from: URL(string: "https://api.github.com/repos/whoeevee/EeveeSpotify/releases/latest")!
|
||||
)
|
||||
|
||||
let tag = try JSONDecoder().decode(GitHubReleaseInfo.self, from: data).tag_name
|
||||
latestVersion = String(tag.dropFirst(5))
|
||||
}
|
||||
|
||||
private var isUpdateAvailable: Bool {
|
||||
guard
|
||||
let latest = Double(latestVersion),
|
||||
let current = Double(EeveeSpotify.version)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return latest > current
|
||||
}
|
||||
|
||||
@ViewBuilder func VersionSection() -> some View {
|
||||
Section {
|
||||
if isUpdateAvailable {
|
||||
Link(
|
||||
"update_available".localized,
|
||||
destination: URL(string: "https://github.com/whoeevee/EeveeSpotify/releases")!
|
||||
)
|
||||
}
|
||||
} footer: {
|
||||
VStack(alignment: .leading) {
|
||||
Text("v\(EeveeSpotify.version)")
|
||||
|
||||
if latestVersion.isEmpty {
|
||||
HStack(spacing: 10) {
|
||||
ProgressView()
|
||||
Text("checking_for_update".localized)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,6 @@ import UIKit
|
||||
struct EeveeSettingsView: View {
|
||||
let navigationController: UINavigationController
|
||||
|
||||
@State var latestVersion = ""
|
||||
|
||||
@State private var hasShownCommonIssuesTip = UserDefaults.hasShownCommonIssuesTip
|
||||
@State private var isClearingData = false
|
||||
|
||||
@@ -17,10 +15,17 @@ struct EeveeSettingsView: View {
|
||||
)
|
||||
navigationController.pushViewController(viewController, animated: true)
|
||||
}
|
||||
|
||||
init(navigationController: UINavigationController) {
|
||||
self.navigationController = navigationController
|
||||
|
||||
let spotifyAccentColor = UIColor(Color(hex: "#1ed760"))
|
||||
UIView.appearance().tintColor = spotifyAccentColor
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
VersionSection()
|
||||
EeveeSettingsVersionView()
|
||||
|
||||
if !hasShownCommonIssuesTip {
|
||||
CommonIssuesTipView(
|
||||
@@ -95,19 +100,13 @@ struct EeveeSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.listStyle(GroupedListStyle())
|
||||
|
||||
.animation(.default, value: isClearingData)
|
||||
.animation(.default, value: latestVersion)
|
||||
.animation(.default, value: hasShownCommonIssuesTip)
|
||||
|
||||
.onAppear {
|
||||
WindowHelper.shared.overrideUserInterfaceStyle(.dark)
|
||||
|
||||
Task {
|
||||
try await loadVersion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -1,7 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
extension EeveeLyricsSettingsView {
|
||||
|
||||
func getMusixmatchToken(_ input: String) -> String? {
|
||||
if let match = input.firstMatch("\\[UserToken\\]: ([a-f0-9]+)"),
|
||||
let tokenRange = Range(match.range(at: 1), in: input) {
|
||||
@@ -21,17 +20,15 @@ extension EeveeLyricsSettingsView {
|
||||
preferredStyle: .alert
|
||||
)
|
||||
|
||||
alert.view.tintColor = UIColor(Color(hex: "#1ed760"))
|
||||
|
||||
alert.addTextField() { textField in
|
||||
textField.placeholder = "---- Debug Info ---- [Device]: iPhone"
|
||||
}
|
||||
|
||||
alert.addAction(UIAlertAction(title: "cancel".localized, style: .cancel) { _ in
|
||||
alert.addAction(UIAlertAction(title: "Cancel".uiKitLocalized, style: .cancel) { _ in
|
||||
lyricsSource = oldSource
|
||||
})
|
||||
|
||||
alert.addAction(UIAlertAction(title: "ok".localized, style: .default) { _ in
|
||||
alert.addAction(UIAlertAction(title: "OK".uiKitLocalized, style: .default) { _ in
|
||||
let text = alert.textFields!.first!.text!
|
||||
|
||||
guard let token = getMusixmatchToken(text) else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct EeveeLyricsSettingsView: View {
|
||||
|
||||
@State var musixmatchToken = UserDefaults.musixmatchToken
|
||||
@State var lyricsSource = UserDefaults.lyricsSource
|
||||
@State var geniusFallback = UserDefaults.geniusFallback
|
||||
|
||||
@@ -63,8 +63,8 @@ struct EeveeUISettingsView: View {
|
||||
.modifier(ListRowSeparatorHidden())
|
||||
}
|
||||
}
|
||||
.listStyle(GroupedListStyle())
|
||||
|
||||
.listStyle(GroupedListStyle())
|
||||
.animation(.default, value: lyricsColors)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ChevronRightView: View {
|
||||
var body: some View {
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.subheadline.weight(.semibold))
|
||||
.foregroundColor(Color(UIColor.systemGray2))
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct CommonIssuesTipView: View {
|
||||
|
||||
var onDismiss: () -> Void
|
||||
|
||||
var body: some View {
|
||||
@@ -18,7 +17,9 @@ struct CommonIssuesTipView: View {
|
||||
}
|
||||
|
||||
Link(
|
||||
destination: URL(string: "https://github.com/whoeevee/EeveeSpotify/blob/swift/common_issues.md")!,
|
||||
destination: URL(
|
||||
string: "https://github.com/whoeevee/EeveeSpotify/blob/swift/common_issues.md"
|
||||
)!,
|
||||
label: {
|
||||
VStack {
|
||||
Text("\("common_issues_tip_message".localized) ")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ImageView: View {
|
||||
@ObservedObject private var imageViewModel: ImageViewModel
|
||||
|
||||
init(urlString: String?) {
|
||||
imageViewModel = ImageViewModel(urlString: urlString)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Image(uiImage: imageViewModel.image ?? UIImage())
|
||||
.resizable()
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
struct NavigationSectionView: View {
|
||||
|
||||
var color: Color
|
||||
var title: String
|
||||
var imageSystemName: String
|
||||
@@ -23,9 +22,7 @@ struct NavigationSectionView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.right")
|
||||
.foregroundColor(Color(UIColor.systemGray2))
|
||||
.font(.subheadline.bold())
|
||||
ChevronRightView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ func exitApplication() {
|
||||
struct PremiumPatching: HookGroup { }
|
||||
|
||||
struct EeveeSpotify: Tweak {
|
||||
|
||||
static let version = "5.4"
|
||||
static let version = "5.5"
|
||||
static let isOldSpotifyVersion = NSClassFromString("Lyrics_NPVCommunicatorImpl.LyricsOnlyViewController") == nil
|
||||
|
||||
init() {
|
||||
|
||||
Reference in New Issue
Block a user