mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
experiments
This commit is contained in:
+5
@@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
@objc protocol SPTSharingSDKDestination {
|
||||
func destinationID() -> String
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import Orion
|
||||
import UIKit
|
||||
|
||||
struct InstgramDestinationGroup: HookGroup { }
|
||||
|
||||
class SPTSharingSDKHook: ClassHook<NSObject> {
|
||||
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<NSObject> {
|
||||
typealias Group = InstgramDestinationGroup
|
||||
static let targetName = "SPTShare_FoundationImplProperties"
|
||||
|
||||
func isInstagramStoriesCanvasSharingEnabled() -> Bool { return true }
|
||||
func isInstagramDirectMessageSharingEnabled() -> Bool { return true }
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Orion
|
||||
import UIKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
class UIApplicationLiveContainerSharingHook: ClassHook<UIApplication> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
@UserDefault(
|
||||
key: "experimentsOptions",
|
||||
defaultValue: ExperimentsOptions(
|
||||
showInstagramDestination: false,
|
||||
liveContainerSharing: true
|
||||
)
|
||||
)
|
||||
static var experimentsOptions
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
struct ExperimentsOptions: Codable, Equatable {
|
||||
var showInstagramDestination: Bool
|
||||
var liveContainerSharing: Bool
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
@objc protocol NowPlayingScrollViewController {
|
||||
func collectionView() -> UICollectionView
|
||||
func nowPlayingScrollViewModelDidChangeScrollEnabledValue()
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
@UserDefault(
|
||||
key: "lyricsColors",
|
||||
defaultValue: LyricsColorOptions(
|
||||
displayOriginalColors: true,
|
||||
useStaticColor: false,
|
||||
staticColor: "",
|
||||
normalizationFactor: 0.5
|
||||
)
|
||||
)
|
||||
static var lyricsColors
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
struct LyricsColorsSettings: Codable, Equatable {
|
||||
struct LyricsColorOptions: Codable, Equatable {
|
||||
var displayOriginalColors: Bool
|
||||
var useStaticColor: Bool
|
||||
var staticColor: String
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import Foundation
|
||||
|
||||
@propertyWrapper
|
||||
struct UserDefault<T: Codable> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,10 @@ struct EeveeSpotify: Tweak {
|
||||
}
|
||||
|
||||
init() {
|
||||
if UserDefaults.experimentsOptions.showInstagramDestination {
|
||||
InstgramDestinationGroup().activate()
|
||||
}
|
||||
|
||||
if UserDefaults.darkPopUps {
|
||||
DarkPopUps().activate()
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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 = "Проблема";
|
||||
|
||||
Reference in New Issue
Block a user