mirror of
https://github.com/whoeevee/EeveeSpotifyReborn.git
synced 2026-01-09 00:23:20 +01:00
experiments
This commit is contained in:
1 parent
92e6cc7900
commit
a994490604
36 files changed
+264
-64
No files matched your search
+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)
|
||||
}
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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