From 07d505fe8035e34e5280cf2409e6d64b1df298a1 Mon Sep 17 00:00:00 2001 From: eevee Date: Sun, 29 Sep 2024 17:13:40 +0300 Subject: [PATCH] better cache cleaning (Library/Caches/), updated dumped configuration, updates some localizations, fixed downloading server-sided alert --- .../Lyrics/Models/Settings/LyricsSource.swift | 4 +- .../Premium/Helpers/OfflineHelper.swift | 20 +++++++-- .../Premium/ServerSidedReminder.x.swift | 39 +++++------------- .../Settings/Views/EeveeSettingsView.swift | 23 +++++++++-- ...ricsSettingsView+LyricsSourceSection.swift | 16 ++----- .../en.lproj/Localizable.strings | 6 +-- .../resolveconfiguration.bnk | Bin 51138 -> 52942 bytes .../ru.lproj/Localizable.strings | 6 +-- 8 files changed, 58 insertions(+), 56 deletions(-) diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource.swift index 17d21f7..08bb047 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource.swift @@ -1,12 +1,12 @@ import Foundation -enum LyricsSource : Int, CustomStringConvertible { +enum LyricsSource: Int, CaseIterable, CustomStringConvertible { case genius case lrclib case musixmatch case petit - var description : String { + var description: String { switch self { case .genius: "Genius" case .lrclib: "LRCLIB" diff --git a/Sources/EeveeSpotify/Premium/Helpers/OfflineHelper.swift b/Sources/EeveeSpotify/Premium/Helpers/OfflineHelper.swift index e7c7f11..18e65ba 100755 --- a/Sources/EeveeSpotify/Premium/Helpers/OfflineHelper.swift +++ b/Sources/EeveeSpotify/Premium/Helpers/OfflineHelper.swift @@ -1,17 +1,21 @@ import Foundation class OfflineHelper { - static private let applicationSupportPath = FileManager.default.urls( + static private let applicationSupportDirectory = FileManager.default.urls( for: .applicationSupportDirectory, in: .userDomainMask ) .first! + static private let cachesDirectory = FileManager.default.urls( + for: .cachesDirectory, in: .userDomainMask + ).first! + // - static private let persistentCachePath = applicationSupportPath + static private let persistentCachePath = applicationSupportDirectory .appendingPathComponent("PersistentCache") - static private let remoteConfigPath = applicationSupportPath + static private let remoteConfigPath = applicationSupportDirectory .appendingPathComponent("remote-config") // @@ -24,10 +28,18 @@ class OfflineHelper { try FileManager.default.removeItem(at: self.remoteConfigPath) } + static private func resetCaches() throws { + try FileManager.default.removeItem(at: self.cachesDirectory) + } + // - static func resetData() { + static func resetData(clearCaches: Bool = false) { try? resetPersistentCache() try? resetRemoteConfig() + + if clearCaches { + try? resetCaches() + } } } diff --git a/Sources/EeveeSpotify/Premium/ServerSidedReminder.x.swift b/Sources/EeveeSpotify/Premium/ServerSidedReminder.x.swift index b5fb61f..5cb0714 100644 --- a/Sources/EeveeSpotify/Premium/ServerSidedReminder.x.swift +++ b/Sources/EeveeSpotify/Premium/ServerSidedReminder.x.swift @@ -24,35 +24,18 @@ private func showOfflineModePopUp() { ) } -class FTPDownloadActionHook: ClassHook { - typealias Group = PremiumPatching - static let targetName = "ListUXPlatform_FreeTierPlaylistImpl.FTPDownloadAction" - - func execute(_ idk: Any) { - showOfflineModePopUp() - } -} - -class UIButtonHook: ClassHook { +class ContentOffliningUIHelperImplementationHook: ClassHook { typealias Group = PremiumPatching + static let targetName = "Offline_ContentOffliningUIImpl.ContentOffliningUIHelperImplementation" - func setHighlighted(_ highlighted: Bool) { - - if highlighted { - - if let identifier = target.accessibilityIdentifier, identifier.contains("DownloadButton"), - let viewController = WindowHelper.shared.viewController(for: target) { - - if !(NSStringFromClass(type(of: viewController)) ~= "Podcast|CreativeWorkPlatform") { - - target.removeTarget(nil, action: nil, for: .allEvents) - showOfflineModePopUp() - - return - } - } - } - - orig.setHighlighted(highlighted) + func downloadToggledWithCurrentAvailability( + _ availability: Int, + addAction: NSObject, + removeAction: NSObject, + pageIdentifier: String, + pageURI: URL + ) -> String { + showOfflineModePopUp() + return pageIdentifier } } diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift index 1f8c857..50f8780 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift @@ -5,7 +5,9 @@ struct EeveeSettingsView: View { let navigationController: UINavigationController @State var latestVersion = "" - @State var hasShownCommonIssuesTip = UserDefaults.hasShownCommonIssuesTip + + @State private var hasShownCommonIssuesTip = UserDefaults.hasShownCommonIssuesTip + @State private var isClearingData = false private func pushSettingsController(with view: any View, title: String) { let viewController = EeveeSettingsViewController( @@ -74,16 +76,29 @@ struct EeveeSettingsView: View { Section(footer: Text("reset_data_description".localized)) { Button { - OfflineHelper.resetData() - exitApplication() + isClearingData = true + + DispatchQueue.global(qos: .userInitiated).async { + OfflineHelper.resetData(clearCaches: true) + + DispatchQueue.main.async { + exitApplication() + } + } } label: { - Text("reset_data".localized) + if isClearingData { + ProgressView() + } + else { + Text("reset_data".localized) + } } } } .listStyle(GroupedListStyle()) + .animation(.default, value: isClearingData) .animation(.default, value: latestVersion) .animation(.default, value: hasShownCommonIssuesTip) diff --git a/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView+LyricsSourceSection.swift b/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView+LyricsSourceSection.swift index 7e1fba6..6c0ff42 100644 --- a/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView+LyricsSourceSection.swift +++ b/Sources/EeveeSpotify/Settings/Views/Sections/EeveeLyricsSettingsView+LyricsSourceSection.swift @@ -1,14 +1,11 @@ import SwiftUI extension EeveeLyricsSettingsView { - func lyricsSourceFooter() -> some View { var text = "lyrics_source_description".localized - if Locale.isInRegion("JP", orHasLanguage: "ja") { - text.append("\n\n") - text.append("petitlyrics_description".localized) - } + text.append("\n\n") + text.append("petitlyrics_description".localized) text.append("\n\n") text.append("lyrics_additional_info".localized) @@ -22,18 +19,13 @@ extension EeveeLyricsSettingsView { "lyrics_source".localized, selection: $lyricsSource ) { - Text("Genius").tag(LyricsSource.genius) - Text("LRCLIB").tag(LyricsSource.lrclib) - Text("Musixmatch").tag(LyricsSource.musixmatch) - if Locale.isInRegion("JP", orHasLanguage: "ja") { - Text("PetitLyrics").tag(LyricsSource.petit) + ForEach(LyricsSource.allCases, id: \.self) { lyricsSource in + Text(lyricsSource.description).tag(lyricsSource) } } if lyricsSource == .musixmatch { - VStack(alignment: .leading, spacing: 5) { - Text("musixmatch_user_token".localized) TextField("user_token_placeholder".localized, text: $musixmatchToken) diff --git a/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings b/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings index b9b557d..513a3cb 100644 --- a/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings +++ b/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings @@ -21,12 +21,12 @@ ok = "OK"; // Patching do_not_patch_premium = "Do Not Patch Premium"; -patching_description = "The tweak intercepts requests to load user data, deserializes it, and modifies the parameters in real-time. +patching_description = "EeveeSpotify intercepts requests to load user data, deserializes it, and modifies the parameters in real-time. If you have an active Premium subscription, you can turn on Do Not Patch Premium. The tweak won't patch the data or restrict the use of Premium server-sided features. App restart is required after changing."; overwrite_configuration = "Overwrite Configuration"; -overwrite_configuration_description = "Replace remote configuration with the dumped Premium one. It might fix some issues, such as appearing ads, but it's not guaranteed."; +overwrite_configuration_description = "Replace remote configuration with the dumped Premium one. This configuration defines most UI/UX parameters and may be helpful, although it could cause issues."; // Lyrics @@ -38,7 +38,7 @@ Genius: Offers the best quality lyrics, provides the most songs, and updates lyr LRCLIB: The most open service, offering time-synced lyrics. However, it lacks lyrics for many songs. Musixmatch: The service Spotify uses. Provides time-synced lyrics for many songs, but you'll need a user token to use this source."; -lyrics_additional_info = "If the tweak is unable to find a song or process the lyrics, you'll see a \"Couldn't load the lyrics for this song\" message. The lyrics might be wrong for some songs when using Genius due to how the tweak searches songs. I've made it work in most cases."; +lyrics_additional_info = "If EeveeSpotify is unable to find a song or process the lyrics, you'll see a \"Couldn't load the lyrics for this song\" message. The lyrics might be wrong for some songs when using Genius due to how the tweak searches songs. I've made it work in most cases."; petitlyrics_description = "PetitLyrics: Offers plenty of time-synced Japanese and some international lyrics."; musixmatch_user_token = "Musixmatch User Token"; diff --git a/layout/Library/Application Support/EeveeSpotify.bundle/resolveconfiguration.bnk b/layout/Library/Application Support/EeveeSpotify.bundle/resolveconfiguration.bnk index 3bf289ee8fa6c548b0ddb2862ce650327749403f..7d83529c237d1922e9b433b3c94daacccb9dd714 100644 GIT binary patch delta 17241 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 OqC#v_sXabjsr(<%BL$cM delta 14127 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