From a994490604d8043cd45b7c9975bd862c321c981a Mon Sep 17 00:00:00 2001 From: eevee <94960726+whoeevee@users.noreply.github.com> Date: Wed, 18 Jun 2025 00:51:01 +0300 Subject: [PATCH] experiments --- .../Headers/SPTSharingSDKDestination.swift | 5 ++ .../ShowInstagramDestination.x.swift | 25 +++++++ .../LiveContainerSharing.x.swift | 38 ++++++++++ .../ExperimentsOptions+UserDefaults.swift | 12 +++ .../Models/Settings/ExperimentsOptions.swift | 4 + .../NowPlayingScrollViewController.swift | 7 ++ .../Models/Headers/SPTPlayerTrack.swift | 11 +++ .../LyricsColorOptions+UserDefaults.swift | 14 ++++ ...ettings.swift => LyricsColorOptions.swift} | 2 +- .../Settings/LyricsOptions+UserDefaults.swift | 15 ++++ .../Settings/LyricsSource+UserDefaults.swift | 18 +++++ .../Views/EeveeExperimentsSettingsView.swift | 37 ++++++++++ .../Lyrics/Helpers/AnonymousTokenHelper.swift | 0 .../Lyrics/Models/AnonymousTokenError.swift | 0 .../Lyrics/Models/LrclibURLState.swift | 0 ...yricsSettingsViewModel+setupBindings.swift | 0 .../EeveeLyricsSettingsViewModel.swift | 0 .../ViewModifiers/DynamicIconModifier.swift | 0 ...ricsSettingsView+lyricsSourceSection.swift | 0 ...ettingsView+showMusixmatchTokenAlert.swift | 0 .../Views/EeveeLyricsSettingsView.swift | 0 .../Views/EeveePatchingSettingsView.swift | 0 .../UI/Views/EeveeUISettingsView.swift | 0 .../GitHub/Helpers/GitHubHelper.swift | 0 .../GitHub/Models/EeveeContributor.swift | 0 .../Models/EeveeContributorSection.swift | 0 .../GitHub/Models/GitHubRelease.swift | 0 .../GitHub/Models/GitHubUser.swift | 0 .../GitHub/Views/EeveeContributorView.swift | 0 .../Views/EeveeContributorsSheetView.swift | 0 .../Settings/Shared/UserDefault.swift | 23 ++++++ .../Settings/Views/EeveeSettingsView.swift | 13 ++++ .../Extensions/UserDefaults+Extension.swift | 74 +++---------------- Sources/EeveeSpotify/Tweak.x.swift | 4 + .../en.lproj/Localizable.strings | 13 ++++ .../ru.lproj/Localizable.strings | 13 ++++ 36 files changed, 264 insertions(+), 64 deletions(-) create mode 100644 Sources/EeveeSpotify/Experiments/Instagram/Models/Headers/SPTSharingSDKDestination.swift create mode 100644 Sources/EeveeSpotify/Experiments/Instagram/ShowInstagramDestination.x.swift create mode 100644 Sources/EeveeSpotify/Experiments/LiveContainer/LiveContainerSharing.x.swift create mode 100644 Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions+UserDefaults.swift create mode 100644 Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions.swift create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Headers/NowPlayingScrollViewController.swift create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Headers/SPTPlayerTrack.swift create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions+UserDefaults.swift rename Sources/EeveeSpotify/Lyrics/Models/Settings/{LyricsColorsSettings.swift => LyricsColorOptions.swift} (75%) create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions+UserDefaults.swift create mode 100644 Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource+UserDefaults.swift create mode 100644 Sources/EeveeSpotify/Settings/Sections/Experiments/Views/EeveeExperimentsSettingsView.swift rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Helpers/AnonymousTokenHelper.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Models/AnonymousTokenError.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Models/LrclibURLState.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/ViewModels/EeveeLyricsSettingsViewModel+setupBindings.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/ViewModels/EeveeLyricsSettingsViewModel.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/ViewModifiers/DynamicIconModifier.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Views/EeveeLyricsSettingsView+lyricsSourceSection.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Views/EeveeLyricsSettingsView+showMusixmatchTokenAlert.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Lyrics/Views/EeveeLyricsSettingsView.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/Patching/Views/EeveePatchingSettingsView.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Sections}/UI/Views/EeveeUISettingsView.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Helpers/GitHubHelper.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Models/EeveeContributor.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Models/EeveeContributorSection.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Models/GitHubRelease.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Models/GitHubUser.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Views/EeveeContributorView.swift (100%) rename Sources/EeveeSpotify/Settings/{ => Shared}/GitHub/Views/EeveeContributorsSheetView.swift (100%) create mode 100644 Sources/EeveeSpotify/Settings/Shared/UserDefault.swift diff --git a/Sources/EeveeSpotify/Experiments/Instagram/Models/Headers/SPTSharingSDKDestination.swift b/Sources/EeveeSpotify/Experiments/Instagram/Models/Headers/SPTSharingSDKDestination.swift new file mode 100644 index 0000000..7a7185e --- /dev/null +++ b/Sources/EeveeSpotify/Experiments/Instagram/Models/Headers/SPTSharingSDKDestination.swift @@ -0,0 +1,5 @@ +import Foundation + +@objc protocol SPTSharingSDKDestination { + func destinationID() -> String +} diff --git a/Sources/EeveeSpotify/Experiments/Instagram/ShowInstagramDestination.x.swift b/Sources/EeveeSpotify/Experiments/Instagram/ShowInstagramDestination.x.swift new file mode 100644 index 0000000..7aa0b55 --- /dev/null +++ b/Sources/EeveeSpotify/Experiments/Instagram/ShowInstagramDestination.x.swift @@ -0,0 +1,25 @@ +import Orion +import UIKit + +struct InstgramDestinationGroup: HookGroup { } + +class SPTSharingSDKHook: ClassHook { + typealias Group = InstgramDestinationGroup + static let targetName = "SPTSharingSDK" + + func canHandleShareDestination(_ destination: SPTSharingSDKDestination) -> Bool { + if destination.destinationID().contains("instagram") { + return true + } + + return orig.canHandleShareDestination(destination) + } +} + +class FoundationImplPropertiesHook: ClassHook { + typealias Group = InstgramDestinationGroup + static let targetName = "SPTShare_FoundationImplProperties" + + func isInstagramStoriesCanvasSharingEnabled() -> Bool { return true } + func isInstagramDirectMessageSharingEnabled() -> Bool { return true } +} diff --git a/Sources/EeveeSpotify/Experiments/LiveContainer/LiveContainerSharing.x.swift b/Sources/EeveeSpotify/Experiments/LiveContainer/LiveContainerSharing.x.swift new file mode 100644 index 0000000..2d4ff0f --- /dev/null +++ b/Sources/EeveeSpotify/Experiments/LiveContainer/LiveContainerSharing.x.swift @@ -0,0 +1,38 @@ +import Orion +import UIKit +import UniformTypeIdentifiers + +class UIApplicationLiveContainerSharingHook: ClassHook { + func openURL( + _ url: URL, + options: [String: Any], + completionHandler: (@MainActor (ObjCBool) -> Void)? + ) { + if UserDefaults.experimentsOptions.liveContainerSharing, !target.canOpenURL(url) { + UIPasteboard.general.addItems([[UTType.url.identifier: url]]) + + let data = url.dataRepresentation + let liveContainerUrl = URL(string: "livecontainer://open-web-page?url=\(data.base64EncodedString())")! + + orig.openURL( + liveContainerUrl, + options: options, + completionHandler: { success in + completionHandler?(true) + + if !success.boolValue { + PopUpHelper.showPopUp( + delayed: false, + message: "could_not_share_popup".localized, + buttonText: "OK".uiKitLocalized + ) + } + } + ) + + return + } + + orig.openURL(url, options: options, completionHandler: completionHandler) + } +} diff --git a/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions+UserDefaults.swift b/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions+UserDefaults.swift new file mode 100644 index 0000000..30cee6e --- /dev/null +++ b/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions+UserDefaults.swift @@ -0,0 +1,12 @@ +import Foundation + +extension UserDefaults { + @UserDefault( + key: "experimentsOptions", + defaultValue: ExperimentsOptions( + showInstagramDestination: false, + liveContainerSharing: true + ) + ) + static var experimentsOptions +} diff --git a/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions.swift b/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions.swift new file mode 100644 index 0000000..238705d --- /dev/null +++ b/Sources/EeveeSpotify/Experiments/Models/Settings/ExperimentsOptions.swift @@ -0,0 +1,4 @@ +struct ExperimentsOptions: Codable, Equatable { + var showInstagramDestination: Bool + var liveContainerSharing: Bool +} diff --git a/Sources/EeveeSpotify/Lyrics/Models/Headers/NowPlayingScrollViewController.swift b/Sources/EeveeSpotify/Lyrics/Models/Headers/NowPlayingScrollViewController.swift new file mode 100644 index 0000000..8bc7687 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Headers/NowPlayingScrollViewController.swift @@ -0,0 +1,7 @@ +import Foundation +import UIKit + +@objc protocol NowPlayingScrollViewController { + func collectionView() -> UICollectionView + func nowPlayingScrollViewModelDidChangeScrollEnabledValue() +} diff --git a/Sources/EeveeSpotify/Lyrics/Models/Headers/SPTPlayerTrack.swift b/Sources/EeveeSpotify/Lyrics/Models/Headers/SPTPlayerTrack.swift new file mode 100644 index 0000000..6f2feb2 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Headers/SPTPlayerTrack.swift @@ -0,0 +1,11 @@ +import Foundation + +@objc protocol SPTPlayerTrack { + func setMetadata(_ metadata: [String:String]) + func metadata() -> [String:String] + func extractedColorHex() -> String? + func trackTitle() -> String + func artistTitle() -> String + func artistName() -> String + func URI() -> SPTURL +} diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions+UserDefaults.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions+UserDefaults.swift new file mode 100644 index 0000000..d5e4389 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions+UserDefaults.swift @@ -0,0 +1,14 @@ +import Foundation + +extension UserDefaults { + @UserDefault( + key: "lyricsColors", + defaultValue: LyricsColorOptions( + displayOriginalColors: true, + useStaticColor: false, + staticColor: "", + normalizationFactor: 0.5 + ) + ) + static var lyricsColors +} diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorsSettings.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions.swift similarity index 75% rename from Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorsSettings.swift rename to Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions.swift index d695783..ee155a2 100644 --- a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorsSettings.swift +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsColorOptions.swift @@ -1,6 +1,6 @@ import Foundation -struct LyricsColorsSettings: Codable, Equatable { +struct LyricsColorOptions: Codable, Equatable { var displayOriginalColors: Bool var useStaticColor: Bool var staticColor: String diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions+UserDefaults.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions+UserDefaults.swift new file mode 100644 index 0000000..a81f058 --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsOptions+UserDefaults.swift @@ -0,0 +1,15 @@ +import Foundation + +extension UserDefaults { + @UserDefault( + key: "lyricsOptions", + defaultValue: LyricsOptions( + romanization: false, + musixmatchLanguage: Locale.current.languageCode ?? "", + lrclibUrl: LrclibLyricsRepository.originalApiUrl, + geniusFallback: true, + showFallbackReasons: true + ) + ) + static var lyricsOptions +} diff --git a/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource+UserDefaults.swift b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource+UserDefaults.swift new file mode 100644 index 0000000..fb6323e --- /dev/null +++ b/Sources/EeveeSpotify/Lyrics/Models/Settings/LyricsSource+UserDefaults.swift @@ -0,0 +1,18 @@ +import Foundation + +extension UserDefaults { + private static let lyricsSourceKey = "lyricsSource" + + static var lyricsSource: LyricsSource { + get { + if let rawValue = container.object(forKey: lyricsSourceKey) as? Int { + return LyricsSource(rawValue: rawValue)! + } + + return LyricsSource.defaultSource + } + set (newSource) { + container.set(newSource.rawValue, forKey: lyricsSourceKey) + } + } +} diff --git a/Sources/EeveeSpotify/Settings/Sections/Experiments/Views/EeveeExperimentsSettingsView.swift b/Sources/EeveeSpotify/Settings/Sections/Experiments/Views/EeveeExperimentsSettingsView.swift new file mode 100644 index 0000000..306c4dc --- /dev/null +++ b/Sources/EeveeSpotify/Settings/Sections/Experiments/Views/EeveeExperimentsSettingsView.swift @@ -0,0 +1,37 @@ +import SwiftUI +import UIKit + +struct EeveeExperimentsSettingsView: View { + @State var experimentsOptions = UserDefaults.experimentsOptions + + var body: some View { + List { + Section(footer: Text("livecontainer_sharing_description".localized)) { + Toggle( + "livecontainer_sharing".localized, + isOn: $experimentsOptions.liveContainerSharing + ) + } + + Section( + footer: Text("show_instagram_destination_description" + .localizeWithFormat("restart_is_required_description".localized)) + ) { + Toggle( + "show_instagram_destination".localized, + isOn: $experimentsOptions.showInstagramDestination + ) + } + } + .onChange(of: experimentsOptions) { options in + UserDefaults.experimentsOptions = options + + if options.showInstagramDestination { + OfflineHelper.resetData() + } + } + + .listStyle(GroupedListStyle()) + .animation(.default, value: experimentsOptions) + } +} diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Helpers/AnonymousTokenHelper.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Helpers/AnonymousTokenHelper.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Helpers/AnonymousTokenHelper.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Helpers/AnonymousTokenHelper.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Models/AnonymousTokenError.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Models/AnonymousTokenError.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Models/AnonymousTokenError.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Models/AnonymousTokenError.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Models/LrclibURLState.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Models/LrclibURLState.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Models/LrclibURLState.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Models/LrclibURLState.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/ViewModels/EeveeLyricsSettingsViewModel+setupBindings.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModels/EeveeLyricsSettingsViewModel+setupBindings.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/ViewModels/EeveeLyricsSettingsViewModel+setupBindings.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModels/EeveeLyricsSettingsViewModel+setupBindings.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/ViewModels/EeveeLyricsSettingsViewModel.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModels/EeveeLyricsSettingsViewModel.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/ViewModels/EeveeLyricsSettingsViewModel.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModels/EeveeLyricsSettingsViewModel.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/ViewModifiers/DynamicIconModifier.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModifiers/DynamicIconModifier.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/ViewModifiers/DynamicIconModifier.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/ViewModifiers/DynamicIconModifier.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView+lyricsSourceSection.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView+lyricsSourceSection.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView+lyricsSourceSection.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView+lyricsSourceSection.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView+showMusixmatchTokenAlert.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView+showMusixmatchTokenAlert.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView+showMusixmatchTokenAlert.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView+showMusixmatchTokenAlert.swift diff --git a/Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView.swift b/Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Lyrics/Views/EeveeLyricsSettingsView.swift rename to Sources/EeveeSpotify/Settings/Sections/Lyrics/Views/EeveeLyricsSettingsView.swift diff --git a/Sources/EeveeSpotify/Settings/Patching/Views/EeveePatchingSettingsView.swift b/Sources/EeveeSpotify/Settings/Sections/Patching/Views/EeveePatchingSettingsView.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/Patching/Views/EeveePatchingSettingsView.swift rename to Sources/EeveeSpotify/Settings/Sections/Patching/Views/EeveePatchingSettingsView.swift diff --git a/Sources/EeveeSpotify/Settings/UI/Views/EeveeUISettingsView.swift b/Sources/EeveeSpotify/Settings/Sections/UI/Views/EeveeUISettingsView.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/UI/Views/EeveeUISettingsView.swift rename to Sources/EeveeSpotify/Settings/Sections/UI/Views/EeveeUISettingsView.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Helpers/GitHubHelper.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Helpers/GitHubHelper.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Helpers/GitHubHelper.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Helpers/GitHubHelper.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Models/EeveeContributor.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Models/EeveeContributor.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Models/EeveeContributor.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Models/EeveeContributor.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Models/EeveeContributorSection.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Models/EeveeContributorSection.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Models/EeveeContributorSection.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Models/EeveeContributorSection.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Models/GitHubRelease.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Models/GitHubRelease.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Models/GitHubRelease.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Models/GitHubRelease.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Models/GitHubUser.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Models/GitHubUser.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Models/GitHubUser.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Models/GitHubUser.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Views/EeveeContributorView.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Views/EeveeContributorView.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Views/EeveeContributorView.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Views/EeveeContributorView.swift diff --git a/Sources/EeveeSpotify/Settings/GitHub/Views/EeveeContributorsSheetView.swift b/Sources/EeveeSpotify/Settings/Shared/GitHub/Views/EeveeContributorsSheetView.swift similarity index 100% rename from Sources/EeveeSpotify/Settings/GitHub/Views/EeveeContributorsSheetView.swift rename to Sources/EeveeSpotify/Settings/Shared/GitHub/Views/EeveeContributorsSheetView.swift diff --git a/Sources/EeveeSpotify/Settings/Shared/UserDefault.swift b/Sources/EeveeSpotify/Settings/Shared/UserDefault.swift new file mode 100644 index 0000000..6e94fd1 --- /dev/null +++ b/Sources/EeveeSpotify/Settings/Shared/UserDefault.swift @@ -0,0 +1,23 @@ +import Foundation + +@propertyWrapper +struct UserDefault { + let key: String + let defaultValue: T + var container: UserDefaults = .standard + + var wrappedValue: T { + get { + if let data = container.data(forKey: key), + let value = try? JSONDecoder().decode(T.self, from: data) { + return value + } + return defaultValue + } + set { + if let data = try? JSONEncoder().encode(newValue) { + container.set(data, forKey: key) + } + } + } +} diff --git a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift index f036ca0..16f9cdc 100644 --- a/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift +++ b/Sources/EeveeSpotify/Settings/Views/EeveeSettingsView.swift @@ -76,6 +76,19 @@ struct EeveeSettingsView: View { ) } + Button { + pushSettingsController( + with: EeveeExperimentsSettingsView(), + title: "experiments".localized + ) + } label: { + NavigationSectionView( + color: .purple, + title: "experiments".localized, + imageSystemName: "sparkle" + ) + } + // Section(footer: Text("reset_data_description".localized)) { diff --git a/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift b/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift index bc68da6..4f51bfa 100644 --- a/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift +++ b/Sources/EeveeSpotify/Shared/Models/Extensions/UserDefaults+Extension.swift @@ -1,9 +1,8 @@ import Foundation extension UserDefaults { - private static let defaults = UserDefaults.standard + static var container: UserDefaults = .standard - private static let lyricsSourceKey = "lyricsSource" private static let musixmatchTokenKey = "musixmatchToken" private static let darkPopUpsKey = "darkPopUps" private static let patchTypeKey = "patchType" @@ -12,103 +11,52 @@ extension UserDefaults { private static let lyricsOptionsKey = "lyricsOptions" private static let hasShownCommonIssuesTipKey = "hasShownCommonIssuesTip" - static var lyricsSource: LyricsSource { - get { - if let rawValue = defaults.object(forKey: lyricsSourceKey) as? Int { - return LyricsSource(rawValue: rawValue)! - } - - return LyricsSource.defaultSource - } - set (newSource) { - defaults.set(newSource.rawValue, forKey: lyricsSourceKey) - } - } - static var musixmatchToken: String { get { - defaults.string(forKey: musixmatchTokenKey) ?? "" + container.string(forKey: musixmatchTokenKey) ?? "" } set (token) { - defaults.set(token, forKey: musixmatchTokenKey) - } - } - - static var lyricsOptions: LyricsOptions { - get { - if let data = defaults.object(forKey: lyricsOptionsKey) as? Data, - let lyricsOptions = try? JSONDecoder().decode(LyricsOptions.self, from: data) { - return lyricsOptions - } - - return LyricsOptions( - romanization: false, - musixmatchLanguage: Locale.current.languageCode ?? "", - lrclibUrl: LrclibLyricsRepository.originalApiUrl, - geniusFallback: true, - showFallbackReasons: true - ) - } - set (lyricsOptions) { - defaults.set(try! JSONEncoder().encode(lyricsOptions), forKey: lyricsOptionsKey) + container.set(token, forKey: musixmatchTokenKey) } } static var darkPopUps: Bool { get { - defaults.object(forKey: darkPopUpsKey) as? Bool ?? true + container.object(forKey: darkPopUpsKey) as? Bool ?? true } set (darkPopUps) { - defaults.set(darkPopUps, forKey: darkPopUpsKey) + container.set(darkPopUps, forKey: darkPopUpsKey) } } static var patchType: PatchType { get { - if let rawValue = defaults.object(forKey: patchTypeKey) as? Int { + if let rawValue = container.object(forKey: patchTypeKey) as? Int { return PatchType(rawValue: rawValue) ?? .requests } return .notSet } set (patchType) { - defaults.set(patchType.rawValue, forKey: patchTypeKey) + container.set(patchType.rawValue, forKey: patchTypeKey) } } static var overwriteConfiguration: Bool { get { - defaults.bool(forKey: overwriteConfigurationKey) + container.bool(forKey: overwriteConfigurationKey) } set (overwriteConfiguration) { - defaults.set(overwriteConfiguration, forKey: overwriteConfigurationKey) + container.set(overwriteConfiguration, forKey: overwriteConfigurationKey) } } static var hasShownCommonIssuesTip: Bool { get { - defaults.bool(forKey: hasShownCommonIssuesTipKey) + container.bool(forKey: hasShownCommonIssuesTipKey) } set (hasShownCommonIssuesTip) { - defaults.set(hasShownCommonIssuesTip, forKey: hasShownCommonIssuesTipKey) - } - } - - static var lyricsColors: LyricsColorsSettings { - get { - if let data = defaults.object(forKey: lyricsColorsKey) as? Data { - return try! JSONDecoder().decode(LyricsColorsSettings.self, from: data) - } - - return LyricsColorsSettings( - displayOriginalColors: true, - useStaticColor: false, - staticColor: "", - normalizationFactor: 0.5 - ) - } - set (lyricsColors) { - defaults.set(try! JSONEncoder().encode(lyricsColors), forKey: lyricsColorsKey) + container.set(hasShownCommonIssuesTip, forKey: hasShownCommonIssuesTipKey) } } } diff --git a/Sources/EeveeSpotify/Tweak.x.swift b/Sources/EeveeSpotify/Tweak.x.swift index 0af7c14..2563d38 100644 --- a/Sources/EeveeSpotify/Tweak.x.swift +++ b/Sources/EeveeSpotify/Tweak.x.swift @@ -27,6 +27,10 @@ struct EeveeSpotify: Tweak { } init() { + if UserDefaults.experimentsOptions.showInstagramDestination { + InstgramDestinationGroup().activate() + } + if UserDefaults.darkPopUps { DarkPopUps().activate() } 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 aff5c4a..6107e99 100644 --- a/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings +++ b/layout/Library/Application Support/EeveeSpotify.bundle/en.lproj/Localizable.strings @@ -3,6 +3,7 @@ patching = "Patching"; lyrics = "Lyrics"; customization = "Customization"; +experiments = "Experiments"; common_issues_tip_title = "Having Trouble?"; @@ -75,6 +76,14 @@ static_color = "Static Color"; color_normalization_factor = "Color Normalization Factor"; dark_popups = "Dark PopUps"; +// Experiments + +show_instagram_destination = "Show Instagram Destination"; +show_instagram_destination_description = "Always show the Instagram share destination. %@"; + +livecontainer_sharing = "Share to LiveContainer"; +livecontainer_sharing_description = "Share to LiveContainer and copy the URL if the selected destination is not supported."; + /* MARK: Premium PopUps */ have_premium_popup = "It looks like you have an active Premium subscription, so the tweak won't patch the data or restrict the use of Premium server-sided features. You can manage this in the EeveeSpotify settings."; @@ -83,6 +92,10 @@ high_audio_quality_popup = "Very high audio quality is server-sided and is not a playlist_downloading_popup = "Native playlist downloading is server-sided and is not available with this tweak. You can download podcast episodes and local playlists though."; download_local_playlist = "Download local playlist"; +// + +could_not_share_popup = "Couldn’t share to the selected destination or LiveContainer. The URL has been copied."; + /* MARK: Lyrics */ fallback_attribute = "Fallback"; diff --git a/layout/Library/Application Support/EeveeSpotify.bundle/ru.lproj/Localizable.strings b/layout/Library/Application Support/EeveeSpotify.bundle/ru.lproj/Localizable.strings index 21b80cc..d3d2e74 100644 --- a/layout/Library/Application Support/EeveeSpotify.bundle/ru.lproj/Localizable.strings +++ b/layout/Library/Application Support/EeveeSpotify.bundle/ru.lproj/Localizable.strings @@ -3,6 +3,7 @@ patching = "Патчинг"; lyrics = "Тексты"; customization = "Кастомизация"; +experiments = "Экспериментальные функции"; common_issues_tip_title = "Что-то не так?"; @@ -74,6 +75,14 @@ static_color = "Статический цвет"; color_normalization_factor = "Уровень нормализации цвета"; dark_popups = "Темные сообщения"; +// Experiments + +show_instagram_destination = "Поделиться в Instagram"; +show_instagram_destination_description = "Всегда показывать опцию для отправки в Instagram. %@"; + +livecontainer_sharing = "Отправлять в LiveContainer"; +livecontainer_sharing_description = "Отправлять в LiveContainer и копировать URL, если выбранная опция для отправки не поддерживается."; + /* MARK: Premium PopUps */ have_premium_popup = "Похоже, у Вас есть активная подписка Premium. Твик не будет изменять данные и ограничивать использование серверных функций. Вы можете управлять этим в настройках EeveeSpotify."; @@ -81,6 +90,10 @@ high_audio_quality_popup = "Очень высокое качество недо playlist_downloading_popup = "Встроенная загрузка плейлистов осуществляется из сервера и недоступна с твиком. Однако вы можете загружать эпизоды подкастов и локальные плейлисты."; download_local_playlist = "Загрузить локальный плейлист"; +// + +could_not_share_popup = "Не удалось поделиться контентом в выбранный сервис или LiveContainer. URL скопирован."; + /* MARK: Lyrics */ fallback_attribute = "Проблема";