mirror of
https://github.com/yattee/yattee.git
synced 2025-12-16 04:58:15 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f002545cf |
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,13 +1,22 @@
|
|||||||
## Build 177
|
## Build 174
|
||||||
* Added Settings Import/Export (iOS, macOS)
|
|
||||||
* Export all settings, instances and accounts
|
|
||||||
* Import selected elements from the file
|
|
||||||
* Include unencrypted passwords in the export or provide them during the import
|
|
||||||
* Added Controls setting "Action button labels" icon or icon and text
|
|
||||||
* Added Advanced setting for MPV: "deinterlace"
|
|
||||||
* Updated dependencies
|
* Updated dependencies
|
||||||
* Updated localizations
|
* Updated localizations
|
||||||
* Fixed reported crash
|
* Fixed reported crashes
|
||||||
|
* Other minor changes and improvements
|
||||||
|
|
||||||
|
## Previous builds
|
||||||
|
* Description is collapsible with a button
|
||||||
|
* Links in description are clickable on macOS
|
||||||
|
* Aspect ratio is honored on resize on macOS
|
||||||
|
* Added support for private Invidious instances
|
||||||
|
* Collapsible chapters view, player setting "Open vertical chapters expanded"
|
||||||
|
* Current chapter is highlighted
|
||||||
|
* Disabled portrait upside down orientation on iPhone
|
||||||
|
* Fixed issue with handling private Invidious instances requests
|
||||||
|
* Fixed issue where Piped login token would not refresh
|
||||||
|
* Fixed issue with MPV subtitles not working
|
||||||
|
* Added Persian, Spanish, Turkish and Russian localizations
|
||||||
|
* Fixed issue with displaying account username
|
||||||
* Other minor changes and improvements
|
* Other minor changes and improvements
|
||||||
|
|
||||||
**Big thanks to the past, current and future project contributors!**
|
**Big thanks to the past, current and future project contributors!**
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -1,6 +1,6 @@
|
|||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem 'fastlane', git: 'https://github.com/nekrich/fastlane.git', branch: 'fix/match-tvos-devices-fetch'
|
gem "fastlane"
|
||||||
|
|
||||||
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile')
|
||||||
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
eval_gemfile(plugins_path) if File.exist?(plugins_path)
|
||||||
|
|||||||
108
Gemfile.lock
108
Gemfile.lock
@@ -1,50 +1,3 @@
|
|||||||
GIT
|
|
||||||
remote: https://github.com/nekrich/fastlane.git
|
|
||||||
revision: d2d51a9af37f9b04a157e78fd25d147cecc89980
|
|
||||||
branch: fix/match-tvos-devices-fetch
|
|
||||||
specs:
|
|
||||||
fastlane (2.219.0)
|
|
||||||
CFPropertyList (>= 2.3, < 4.0.0)
|
|
||||||
addressable (>= 2.8, < 3.0.0)
|
|
||||||
artifactory (~> 3.0)
|
|
||||||
aws-sdk-s3 (~> 1.0)
|
|
||||||
babosa (>= 1.0.3, < 2.0.0)
|
|
||||||
bundler (>= 1.12.0, < 3.0.0)
|
|
||||||
colored (~> 1.2)
|
|
||||||
commander (~> 4.6)
|
|
||||||
dotenv (>= 2.1.1, < 3.0.0)
|
|
||||||
emoji_regex (>= 0.1, < 4.0)
|
|
||||||
excon (>= 0.71.0, < 1.0.0)
|
|
||||||
faraday (~> 1.0)
|
|
||||||
faraday-cookie_jar (~> 0.0.6)
|
|
||||||
faraday_middleware (~> 1.0)
|
|
||||||
fastimage (>= 2.1.0, < 3.0.0)
|
|
||||||
gh_inspector (>= 1.1.2, < 2.0.0)
|
|
||||||
google-apis-androidpublisher_v3 (~> 0.3)
|
|
||||||
google-apis-playcustomapp_v1 (~> 0.1)
|
|
||||||
google-cloud-env (>= 1.6.0, < 2.0.0)
|
|
||||||
google-cloud-storage (~> 1.31)
|
|
||||||
highline (~> 2.0)
|
|
||||||
http-cookie (~> 1.0.5)
|
|
||||||
json (< 3.0.0)
|
|
||||||
jwt (>= 2.1.0, < 3)
|
|
||||||
mini_magick (>= 4.9.4, < 5.0.0)
|
|
||||||
multipart-post (>= 2.0.0, < 3.0.0)
|
|
||||||
naturally (~> 2.2)
|
|
||||||
optparse (>= 0.1.1, < 1.0.0)
|
|
||||||
plist (>= 3.1.0, < 4.0.0)
|
|
||||||
rubyzip (>= 2.0.0, < 3.0.0)
|
|
||||||
security (= 0.1.3)
|
|
||||||
simctl (~> 1.6.3)
|
|
||||||
terminal-notifier (>= 2.0.0, < 3.0.0)
|
|
||||||
terminal-table (~> 3)
|
|
||||||
tty-screen (>= 0.6.3, < 1.0.0)
|
|
||||||
tty-spinner (>= 0.8.0, < 1.0.0)
|
|
||||||
word_wrap (~> 1.0.0)
|
|
||||||
xcodeproj (>= 1.13.0, < 2.0.0)
|
|
||||||
xcpretty (~> 0.3.0)
|
|
||||||
xcpretty-travis-formatter (>= 0.0.3, < 2.0.0)
|
|
||||||
|
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
@@ -55,17 +8,17 @@ GEM
|
|||||||
artifactory (3.0.15)
|
artifactory (3.0.15)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.886.0)
|
aws-partitions (1.880.0)
|
||||||
aws-sdk-core (3.191.0)
|
aws-sdk-core (3.190.2)
|
||||||
aws-eventstream (~> 1, >= 1.3.0)
|
aws-eventstream (~> 1, >= 1.3.0)
|
||||||
aws-partitions (~> 1, >= 1.651.0)
|
aws-partitions (~> 1, >= 1.651.0)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
jmespath (~> 1, >= 1.6.1)
|
jmespath (~> 1, >= 1.6.1)
|
||||||
aws-sdk-kms (1.77.0)
|
aws-sdk-kms (1.76.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.188.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.143.0)
|
aws-sdk-s3 (1.142.0)
|
||||||
aws-sdk-core (~> 3, >= 3.191.0)
|
aws-sdk-core (~> 3, >= 3.189.0)
|
||||||
aws-sdk-kms (~> 1)
|
aws-sdk-kms (~> 1)
|
||||||
aws-sigv4 (~> 1.8)
|
aws-sigv4 (~> 1.8)
|
||||||
aws-sigv4 (1.8.0)
|
aws-sigv4 (1.8.0)
|
||||||
@@ -112,10 +65,51 @@ GEM
|
|||||||
faraday_middleware (1.2.0)
|
faraday_middleware (1.2.0)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fastimage (2.3.0)
|
fastimage (2.3.0)
|
||||||
|
fastlane (2.219.0)
|
||||||
|
CFPropertyList (>= 2.3, < 4.0.0)
|
||||||
|
addressable (>= 2.8, < 3.0.0)
|
||||||
|
artifactory (~> 3.0)
|
||||||
|
aws-sdk-s3 (~> 1.0)
|
||||||
|
babosa (>= 1.0.3, < 2.0.0)
|
||||||
|
bundler (>= 1.12.0, < 3.0.0)
|
||||||
|
colored
|
||||||
|
commander (~> 4.6)
|
||||||
|
dotenv (>= 2.1.1, < 3.0.0)
|
||||||
|
emoji_regex (>= 0.1, < 4.0)
|
||||||
|
excon (>= 0.71.0, < 1.0.0)
|
||||||
|
faraday (~> 1.0)
|
||||||
|
faraday-cookie_jar (~> 0.0.6)
|
||||||
|
faraday_middleware (~> 1.0)
|
||||||
|
fastimage (>= 2.1.0, < 3.0.0)
|
||||||
|
gh_inspector (>= 1.1.2, < 2.0.0)
|
||||||
|
google-apis-androidpublisher_v3 (~> 0.3)
|
||||||
|
google-apis-playcustomapp_v1 (~> 0.1)
|
||||||
|
google-cloud-env (>= 1.6.0, < 2.0.0)
|
||||||
|
google-cloud-storage (~> 1.31)
|
||||||
|
highline (~> 2.0)
|
||||||
|
http-cookie (~> 1.0.5)
|
||||||
|
json (< 3.0.0)
|
||||||
|
jwt (>= 2.1.0, < 3)
|
||||||
|
mini_magick (>= 4.9.4, < 5.0.0)
|
||||||
|
multipart-post (>= 2.0.0, < 3.0.0)
|
||||||
|
naturally (~> 2.2)
|
||||||
|
optparse (>= 0.1.1)
|
||||||
|
plist (>= 3.1.0, < 4.0.0)
|
||||||
|
rubyzip (>= 2.0.0, < 3.0.0)
|
||||||
|
security (= 0.1.3)
|
||||||
|
simctl (~> 1.6.3)
|
||||||
|
terminal-notifier (>= 2.0.0, < 3.0.0)
|
||||||
|
terminal-table (~> 3)
|
||||||
|
tty-screen (>= 0.6.3, < 1.0.0)
|
||||||
|
tty-spinner (>= 0.8.0, < 1.0.0)
|
||||||
|
word_wrap (~> 1.0.0)
|
||||||
|
xcodeproj (>= 1.13.0, < 2.0.0)
|
||||||
|
xcpretty (~> 0.3.0)
|
||||||
|
xcpretty-travis-formatter (>= 0.0.3)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
google-apis-androidpublisher_v3 (0.54.0)
|
google-apis-androidpublisher_v3 (0.54.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-core (0.11.3)
|
google-apis-core (0.11.2)
|
||||||
addressable (~> 2.5, >= 2.5.1)
|
addressable (~> 2.5, >= 2.5.1)
|
||||||
googleauth (>= 0.16.2, < 2.a)
|
googleauth (>= 0.16.2, < 2.a)
|
||||||
httpclient (>= 2.8.1, < 3.a)
|
httpclient (>= 2.8.1, < 3.a)
|
||||||
@@ -123,6 +117,7 @@ GEM
|
|||||||
representable (~> 3.0)
|
representable (~> 3.0)
|
||||||
retriable (>= 2.0, < 4.a)
|
retriable (>= 2.0, < 4.a)
|
||||||
rexml
|
rexml
|
||||||
|
webrick
|
||||||
google-apis-iamcredentials_v1 (0.17.0)
|
google-apis-iamcredentials_v1 (0.17.0)
|
||||||
google-apis-core (>= 0.11.0, < 2.a)
|
google-apis-core (>= 0.11.0, < 2.a)
|
||||||
google-apis-playcustomapp_v1 (0.13.0)
|
google-apis-playcustomapp_v1 (0.13.0)
|
||||||
@@ -195,8 +190,9 @@ GEM
|
|||||||
tty-cursor (~> 0.7)
|
tty-cursor (~> 0.7)
|
||||||
uber (0.1.0)
|
uber (0.1.0)
|
||||||
unicode-display_width (2.5.0)
|
unicode-display_width (2.5.0)
|
||||||
|
webrick (1.8.1)
|
||||||
word_wrap (1.0.0)
|
word_wrap (1.0.0)
|
||||||
xcodeproj (1.24.0)
|
xcodeproj (1.23.0)
|
||||||
CFPropertyList (>= 2.3.3, < 4.0)
|
CFPropertyList (>= 2.3.3, < 4.0)
|
||||||
atomos (~> 0.1.3)
|
atomos (~> 0.1.3)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
@@ -215,7 +211,7 @@ PLATFORMS
|
|||||||
x86_64-linux
|
x86_64-linux
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
fastlane!
|
fastlane
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.6
|
2.3.6
|
||||||
|
|||||||
@@ -64,10 +64,6 @@ final class AccountsModel: ObservableObject {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func find(_ id: Account.ID) -> Account? {
|
|
||||||
all.first { $0.id == id }
|
|
||||||
}
|
|
||||||
|
|
||||||
func configureAccount() {
|
func configureAccount() {
|
||||||
if let account = lastUsed ??
|
if let account = lastUsed ??
|
||||||
InstancesModel.shared.lastUsed?.anonymousAccount ??
|
InstancesModel.shared.lastUsed?.anonymousAccount ??
|
||||||
@@ -112,8 +108,8 @@ final class AccountsModel: ObservableObject {
|
|||||||
Defaults[.accounts].first { $0.id == id }
|
Defaults[.accounts].first { $0.id == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
static func add(instance: Instance, id: String? = UUID().uuidString, name: String, username: String, password: String) -> Account {
|
static func add(instance: Instance, name: String, username: String, password: String) -> Account {
|
||||||
let account = Account(id: id, instanceID: instance.id, name: name, urlString: instance.apiURLString)
|
let account = Account(instanceID: instance.id, name: name, urlString: instance.apiURLString)
|
||||||
Defaults[.accounts].append(account)
|
Defaults[.accounts].append(account)
|
||||||
|
|
||||||
setCredentials(account, username: username, password: password)
|
setCredentials(account, username: username, password: password)
|
||||||
|
|||||||
@@ -68,8 +68,4 @@ struct Instance: Defaults.Serializable, Hashable, Identifiable {
|
|||||||
func hash(into hasher: inout Hasher) {
|
func hash(into hasher: inout Hasher) {
|
||||||
hasher.combine(apiURL)
|
hasher.combine(apiURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
var accounts: [Account] {
|
|
||||||
AccountsModel.shared.all.filter { $0.instanceID == id }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,23 +42,15 @@ final class InstancesModel: ObservableObject {
|
|||||||
Defaults[.accounts].filter { $0.instanceID == id }
|
Defaults[.accounts].filter { $0.instanceID == id }
|
||||||
}
|
}
|
||||||
|
|
||||||
func add(id: String? = UUID().uuidString, app: VideosApp, name: String, url: String) -> Instance {
|
func add(app: VideosApp, name: String, url: String) -> Instance {
|
||||||
let instance = Instance(
|
let instance = Instance(
|
||||||
app: app, id: id, name: name, apiURLString: standardizedURL(url)
|
app: app, id: UUID().uuidString, name: name, apiURLString: standardizedURL(url)
|
||||||
)
|
)
|
||||||
Defaults[.instances].append(instance)
|
Defaults[.instances].append(instance)
|
||||||
|
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
func insert(id: String? = UUID().uuidString, app: VideosApp, name: String, url: String) -> Instance {
|
|
||||||
if let instance = Defaults[.instances].first(where: { $0.apiURL.absoluteString == standardizedURL(url) }) {
|
|
||||||
return instance
|
|
||||||
}
|
|
||||||
|
|
||||||
return add(id: id, app: app, name: name, url: url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setFrontendURL(_ instance: Instance, _ url: String) {
|
func setFrontendURL(_ instance: Instance, _ url: String) {
|
||||||
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
|
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
|
||||||
var instance = Defaults[.instances][index]
|
var instance = Defaults[.instances][index]
|
||||||
|
|||||||
@@ -154,8 +154,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
method: .post,
|
method: .post,
|
||||||
parameters: ["username": username, "password": password],
|
parameters: ["username": username, "password": password],
|
||||||
encoding: JSONEncoding.default
|
encoding: JSONEncoding.default
|
||||||
)
|
).responseDecodable(of: JSON.self) { [weak self] response in
|
||||||
.responseDecodable(of: JSON.self) { [weak self] response in
|
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ struct FavoritesModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add(_ item: FavoriteItem) {
|
func add(_ item: FavoriteItem) {
|
||||||
if contains(item) { return }
|
|
||||||
all.append(item)
|
all.append(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +122,4 @@ struct FavoritesModel {
|
|||||||
func widgetSettings(_ item: FavoriteItem) -> WidgetSettings {
|
func widgetSettings(_ item: FavoriteItem) -> WidgetSettings {
|
||||||
widgetsSettings.first { $0.id == item.widgetSettingsKey } ?? WidgetSettings(id: item.widgetSettingsKey)
|
widgetsSettings.first { $0.id == item.widgetSettingsKey } ?? WidgetSettings(id: item.widgetSettingsKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateWidgetSettings(_ settings: WidgetSettings) {
|
|
||||||
if let index = widgetsSettings.firstIndex(where: { $0.id == settings.id }) {
|
|
||||||
widgetsSettings[index] = settings
|
|
||||||
} else {
|
|
||||||
widgetsSettings.append(settings)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class AdvancedSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"showPlayNowInBackendContextMenu": Defaults[.showPlayNowInBackendContextMenu],
|
|
||||||
"showMPVPlaybackStats": Defaults[.showMPVPlaybackStats],
|
|
||||||
"mpvEnableLogging": Defaults[.mpvEnableLogging],
|
|
||||||
"mpvCacheSecs": Defaults[.mpvCacheSecs],
|
|
||||||
"mpvCachePauseWait": Defaults[.mpvCachePauseWait],
|
|
||||||
"mpvDeinterlace": Defaults[.mpvDeinterlace],
|
|
||||||
"showCacheStatus": Defaults[.showCacheStatus],
|
|
||||||
"feedCacheSize": Defaults[.feedCacheSize]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class BrowsingSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"showHome": Defaults[.showHome],
|
|
||||||
"showOpenActionsInHome": Defaults[.showOpenActionsInHome],
|
|
||||||
"showQueueInHome": Defaults[.showQueueInHome],
|
|
||||||
"showFavoritesInHome": Defaults[.showFavoritesInHome],
|
|
||||||
"favorites": Defaults[.favorites].compactMap { jsonFromString(FavoriteItem.bridge.serialize($0)) },
|
|
||||||
"widgetsSettings": Defaults[.widgetsSettings].compactMap { widgetSettingsJSON($0) },
|
|
||||||
"startupSection": Defaults[.startupSection].rawValue,
|
|
||||||
"visibleSections": Defaults[.visibleSections].compactMap { $0.rawValue },
|
|
||||||
"showOpenActionsToolbarItem": Defaults[.showOpenActionsToolbarItem],
|
|
||||||
"accountPickerDisplaysAnonymousAccounts": Defaults[.accountPickerDisplaysAnonymousAccounts],
|
|
||||||
"showUnwatchedFeedBadges": Defaults[.showUnwatchedFeedBadges],
|
|
||||||
"expandChannelDescription": Defaults[.expandChannelDescription],
|
|
||||||
"keepChannelsWithUnwatchedFeedOnTop": Defaults[.keepChannelsWithUnwatchedFeedOnTop],
|
|
||||||
"showChannelAvatarInChannelsLists": Defaults[.showChannelAvatarInChannelsLists],
|
|
||||||
"showChannelAvatarInVideosListing": Defaults[.showChannelAvatarInVideosListing],
|
|
||||||
"playerButtonSingleTapGesture": Defaults[.playerButtonSingleTapGesture].rawValue,
|
|
||||||
"playerButtonDoubleTapGesture": Defaults[.playerButtonDoubleTapGesture].rawValue,
|
|
||||||
"playerButtonShowsControlButtonsWhenMinimized": Defaults[.playerButtonShowsControlButtonsWhenMinimized],
|
|
||||||
"playerButtonIsExpanded": Defaults[.playerButtonIsExpanded],
|
|
||||||
"playerBarMaxWidth": Defaults[.playerBarMaxWidth],
|
|
||||||
"channelOnThumbnail": Defaults[.channelOnThumbnail],
|
|
||||||
"timeOnThumbnail": Defaults[.timeOnThumbnail],
|
|
||||||
"roundedThumbnails": Defaults[.roundedThumbnails],
|
|
||||||
"thumbnailsQuality": Defaults[.thumbnailsQuality].rawValue
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
override var platformJSON: JSON {
|
|
||||||
var export = JSON()
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
export["showDocuments"].bool = Defaults[.showDocuments]
|
|
||||||
export["lockPortraitWhenBrowsing"].bool = Defaults[.lockPortraitWhenBrowsing]
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
|
||||||
export["accountPickerDisplaysUsername"].bool = Defaults[.accountPickerDisplaysUsername]
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return export
|
|
||||||
}
|
|
||||||
|
|
||||||
private func widgetSettingsJSON(_ settings: WidgetSettings) -> JSON {
|
|
||||||
var json = JSON()
|
|
||||||
json.dictionaryObject = WidgetSettingsBridge().serialize(settings)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class ConstrolsSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"avPlayerUsesSystemControls": Defaults[.avPlayerUsesSystemControls],
|
|
||||||
"horizontalPlayerGestureEnabled": Defaults[.horizontalPlayerGestureEnabled],
|
|
||||||
"seekGestureSensitivity": Defaults[.seekGestureSensitivity],
|
|
||||||
"seekGestureSpeed": Defaults[.seekGestureSpeed],
|
|
||||||
"playerControlsLayout": Defaults[.playerControlsLayout].rawValue,
|
|
||||||
"fullScreenPlayerControlsLayout": Defaults[.fullScreenPlayerControlsLayout].rawValue,
|
|
||||||
"systemControlsCommands": Defaults[.systemControlsCommands].rawValue,
|
|
||||||
"buttonBackwardSeekDuration": Defaults[.buttonBackwardSeekDuration],
|
|
||||||
"buttonForwardSeekDuration": Defaults[.buttonForwardSeekDuration],
|
|
||||||
"gestureBackwardSeekDuration": Defaults[.gestureBackwardSeekDuration],
|
|
||||||
"gestureForwardSeekDuration": Defaults[.gestureForwardSeekDuration],
|
|
||||||
"systemControlsSeekDuration": Defaults[.systemControlsSeekDuration],
|
|
||||||
"playerControlsSettingsEnabled": Defaults[.playerControlsSettingsEnabled],
|
|
||||||
"playerControlsCloseEnabled": Defaults[.playerControlsCloseEnabled],
|
|
||||||
"playerControlsRestartEnabled": Defaults[.playerControlsRestartEnabled],
|
|
||||||
"playerControlsAdvanceToNextEnabled": Defaults[.playerControlsAdvanceToNextEnabled],
|
|
||||||
"playerControlsPlaybackModeEnabled": Defaults[.playerControlsPlaybackModeEnabled],
|
|
||||||
"playerControlsMusicModeEnabled": Defaults[.playerControlsMusicModeEnabled],
|
|
||||||
"playerActionsButtonLabelStyle": Defaults[.playerActionsButtonLabelStyle].rawValue,
|
|
||||||
"actionButtonShareEnabled": Defaults[.actionButtonShareEnabled],
|
|
||||||
"actionButtonAddToPlaylistEnabled": Defaults[.actionButtonAddToPlaylistEnabled],
|
|
||||||
"actionButtonSubscribeEnabled": Defaults[.actionButtonSubscribeEnabled],
|
|
||||||
"actionButtonSettingsEnabled": Defaults[.actionButtonSettingsEnabled],
|
|
||||||
"actionButtonHideEnabled": Defaults[.actionButtonHideEnabled],
|
|
||||||
"actionButtonCloseEnabled": Defaults[.actionButtonCloseEnabled],
|
|
||||||
"actionButtonFullScreenEnabled": Defaults[.actionButtonFullScreenEnabled],
|
|
||||||
"actionButtonPipEnabled": Defaults[.actionButtonPipEnabled],
|
|
||||||
"actionButtonLockOrientationEnabled": Defaults[.actionButtonLockOrientationEnabled],
|
|
||||||
"actionButtonRestartEnabled": Defaults[.actionButtonRestartEnabled],
|
|
||||||
"actionButtonAdvanceToNextItemEnabled": Defaults[.actionButtonAdvanceToNextItemEnabled],
|
|
||||||
"actionButtonMusicModeEnabled": Defaults[.actionButtonMusicModeEnabled]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class HistorySettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"saveRecents": Defaults[.saveRecents],
|
|
||||||
"saveHistory": Defaults[.saveHistory],
|
|
||||||
"showWatchingProgress": Defaults[.showWatchingProgress],
|
|
||||||
"saveLastPlayed": Defaults[.saveLastPlayed],
|
|
||||||
|
|
||||||
"watchedVideoPlayNowBehavior": Defaults[.watchedVideoPlayNowBehavior].rawValue,
|
|
||||||
"watchedThreshold": Defaults[.watchedThreshold],
|
|
||||||
"resetWatchedStatusOnPlaying": Defaults[.resetWatchedStatusOnPlaying],
|
|
||||||
|
|
||||||
"watchedVideoStyle": Defaults[.watchedVideoStyle].rawValue,
|
|
||||||
"watchedVideoBadgeColor": Defaults[.watchedVideoBadgeColor].rawValue,
|
|
||||||
"showToggleWatchedStatusButton": Defaults[.showToggleWatchedStatusButton]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class LocationsSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
var includePublicInstances = true
|
|
||||||
var includeInstances = true
|
|
||||||
var includeAccounts = true
|
|
||||||
var includeAccountsUnencryptedPasswords = false
|
|
||||||
|
|
||||||
init(includePublicInstances: Bool = true, includeInstances: Bool = true, includeAccounts: Bool = true, includeAccountsUnencryptedPasswords: Bool = false) {
|
|
||||||
self.includePublicInstances = includePublicInstances
|
|
||||||
self.includeInstances = includeInstances
|
|
||||||
self.includeAccounts = includeAccounts
|
|
||||||
self.includeAccountsUnencryptedPasswords = includeAccountsUnencryptedPasswords
|
|
||||||
}
|
|
||||||
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
var json = JSON()
|
|
||||||
|
|
||||||
if includePublicInstances {
|
|
||||||
json["instancesManifest"].string = Defaults[.instancesManifest]
|
|
||||||
json["countryOfPublicInstances"].string = Defaults[.countryOfPublicInstances] ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeInstances {
|
|
||||||
json["instances"].arrayObject = Defaults[.instances].compactMap { instanceJSON($0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if includeAccounts {
|
|
||||||
json["accounts"].arrayObject = Defaults[.accounts].compactMap { account in
|
|
||||||
var account = account
|
|
||||||
let (username, password) = AccountsModel.getCredentials(account)
|
|
||||||
account.username = username ?? ""
|
|
||||||
if includeAccountsUnencryptedPasswords {
|
|
||||||
account.password = password ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return accountJSON(account).dictionaryObject
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
private func instanceJSON(_ instance: Instance) -> JSON {
|
|
||||||
var json = JSON()
|
|
||||||
json.dictionaryObject = InstancesBridge().serialize(instance)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
private func accountJSON(_ account: Account) -> JSON {
|
|
||||||
var json = JSON()
|
|
||||||
json.dictionaryObject = AccountsBridge().serialize(account)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class OtherDataSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"lastAccountID": Defaults[.lastAccountID] ?? "",
|
|
||||||
"lastInstanceID": Defaults[.lastInstanceID] ?? "",
|
|
||||||
|
|
||||||
"playerRate": Defaults[.playerRate],
|
|
||||||
|
|
||||||
"trendingCategory": Defaults[.trendingCategory].rawValue,
|
|
||||||
"trendingCountry": Defaults[.trendingCountry].rawValue,
|
|
||||||
|
|
||||||
"subscriptionsViewPage": Defaults[.subscriptionsViewPage].rawValue,
|
|
||||||
"subscriptionsListingStyle": Defaults[.subscriptionsListingStyle].rawValue,
|
|
||||||
"popularListingStyle": Defaults[.popularListingStyle].rawValue,
|
|
||||||
"trendingListingStyle": Defaults[.trendingListingStyle].rawValue,
|
|
||||||
"playlistListingStyle": Defaults[.playlistListingStyle].rawValue,
|
|
||||||
"channelPlaylistListingStyle": Defaults[.channelPlaylistListingStyle].rawValue,
|
|
||||||
"searchListingStyle": Defaults[.searchListingStyle].rawValue,
|
|
||||||
|
|
||||||
"hideShorts": Defaults[.hideShorts],
|
|
||||||
"hideWatched": Defaults[.hideWatched]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class PlayerSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"playerInstanceID": Defaults[.playerInstanceID] ?? "",
|
|
||||||
"pauseOnHidingPlayer": Defaults[.pauseOnHidingPlayer],
|
|
||||||
"closeVideoOnEOF": Defaults[.closeVideoOnEOF],
|
|
||||||
"expandVideoDescription": Defaults[.expandVideoDescription],
|
|
||||||
"collapsedLinesDescription": Defaults[.collapsedLinesDescription],
|
|
||||||
"showChapters": Defaults[.showChapters],
|
|
||||||
"expandChapters": Defaults[.expandChapters],
|
|
||||||
"showRelated": Defaults[.showRelated],
|
|
||||||
"showInspector": Defaults[.showInspector].rawValue,
|
|
||||||
"playerSidebar": Defaults[.playerSidebar].rawValue,
|
|
||||||
"showKeywords": Defaults[.showKeywords],
|
|
||||||
"enableReturnYouTubeDislike": Defaults[.enableReturnYouTubeDislike],
|
|
||||||
"closePiPOnNavigation": Defaults[.closePiPOnNavigation],
|
|
||||||
"closePiPOnOpeningPlayer": Defaults[.closePiPOnOpeningPlayer],
|
|
||||||
"closePlayerOnOpeningPiP": Defaults[.closePlayerOnOpeningPiP]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
override var platformJSON: JSON {
|
|
||||||
var export = JSON()
|
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
export["pauseOnEnteringBackground"].bool = Defaults[.pauseOnEnteringBackground]
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
|
||||||
export["showScrollToTopInComments"].bool = Defaults[.showScrollToTopInComments]
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
export["honorSystemOrientationLock"].bool = Defaults[.honorSystemOrientationLock]
|
|
||||||
export["enterFullscreenInLandscape"].bool = Defaults[.enterFullscreenInLandscape]
|
|
||||||
export["rotateToLandscapeOnEnterFullScreen"].string = Defaults[.rotateToLandscapeOnEnterFullScreen].rawValue
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return export
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class QualitySettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"batteryCellularProfile": Defaults[.batteryCellularProfile],
|
|
||||||
"batteryNonCellularProfile": Defaults[.batteryNonCellularProfile],
|
|
||||||
"chargingCellularProfile": Defaults[.chargingCellularProfile],
|
|
||||||
"chargingNonCellularProfile": Defaults[.chargingNonCellularProfile],
|
|
||||||
"forceAVPlayerForLiveStreams": Defaults[.forceAVPlayerForLiveStreams],
|
|
||||||
"qualityProfiles": Defaults[.qualityProfiles].compactMap { qualityProfileJSON($0) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
func qualityProfileJSON(_ profile: QualityProfile) -> JSON {
|
|
||||||
var json = JSON()
|
|
||||||
json.dictionaryObject = QualityProfileBridge().serialize(profile)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class RecentlyOpenedExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"recentlyOpened": Defaults[.recentlyOpened].compactMap { recentItemJSON($0) }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private func recentItemJSON(_ recentItem: RecentItem) -> JSON {
|
|
||||||
var json = JSON()
|
|
||||||
json.dictionaryObject = RecentItemBridge().serialize(recentItem)
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
class SettingsGroupExporter { // swiftlint:disable:this final_class
|
|
||||||
var globalJSON: JSON {
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
|
|
||||||
var platformJSON: JSON {
|
|
||||||
[]
|
|
||||||
}
|
|
||||||
|
|
||||||
var exportJSON: JSON {
|
|
||||||
var json = globalJSON
|
|
||||||
|
|
||||||
if !platformJSON.isEmpty {
|
|
||||||
try? json.merge(with: platformJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
func jsonFromString(_ string: String?) -> JSON? {
|
|
||||||
if let data = string?.data(using: .utf8, allowLossyConversion: false),
|
|
||||||
let json = try? JSON(data: data)
|
|
||||||
{
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class SponsorBlockSettingsGroupExporter: SettingsGroupExporter {
|
|
||||||
override var globalJSON: JSON {
|
|
||||||
[
|
|
||||||
"sponsorBlockInstance": Defaults[.sponsorBlockInstance],
|
|
||||||
"sponsorBlockCategories": Array(Defaults[.sponsorBlockCategories])
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
final class ImportExportSettingsModel: ObservableObject {
|
|
||||||
static let shared = ImportExportSettingsModel()
|
|
||||||
|
|
||||||
static var exportFile: URL {
|
|
||||||
YatteeApp.settingsExportDirectory
|
|
||||||
.appendingPathComponent("Yattee Settings from \(Constants.deviceName).\(settingsExtension)")
|
|
||||||
}
|
|
||||||
|
|
||||||
static var settingsExtension: String {
|
|
||||||
"yatteesettings"
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ExportGroup: String, Identifiable, CaseIterable {
|
|
||||||
case browsingSettings
|
|
||||||
case playerSettings
|
|
||||||
case controlsSettings
|
|
||||||
case qualitySettings
|
|
||||||
case historySettings
|
|
||||||
case sponsorBlockSettings
|
|
||||||
case advancedSettings
|
|
||||||
|
|
||||||
case locationsSettings
|
|
||||||
case instances
|
|
||||||
case accounts
|
|
||||||
case accountsUnencryptedPasswords
|
|
||||||
|
|
||||||
case recentlyOpened
|
|
||||||
case otherData
|
|
||||||
|
|
||||||
static var settingsGroups: [Self] {
|
|
||||||
[.browsingSettings, .playerSettings, .controlsSettings, .qualitySettings, .historySettings, .sponsorBlockSettings, .advancedSettings]
|
|
||||||
}
|
|
||||||
|
|
||||||
static var locationsGroups: [Self] {
|
|
||||||
[.locationsSettings, .instances, .accounts, .accountsUnencryptedPasswords]
|
|
||||||
}
|
|
||||||
|
|
||||||
static var otherGroups: [Self] {
|
|
||||||
[.recentlyOpened, .otherData]
|
|
||||||
}
|
|
||||||
|
|
||||||
var id: RawValue {
|
|
||||||
rawValue
|
|
||||||
}
|
|
||||||
|
|
||||||
var label: String {
|
|
||||||
switch self {
|
|
||||||
case .browsingSettings:
|
|
||||||
return "Browsing"
|
|
||||||
case .playerSettings:
|
|
||||||
return "Player"
|
|
||||||
case .controlsSettings:
|
|
||||||
return "Controls"
|
|
||||||
case .qualitySettings:
|
|
||||||
return "Quality"
|
|
||||||
case .historySettings:
|
|
||||||
return "History"
|
|
||||||
case .sponsorBlockSettings:
|
|
||||||
return "SponsorBlock"
|
|
||||||
case .locationsSettings:
|
|
||||||
return "Public Locations"
|
|
||||||
case .instances:
|
|
||||||
return "Custom Locations"
|
|
||||||
case .accounts:
|
|
||||||
return "Accounts"
|
|
||||||
case .accountsUnencryptedPasswords:
|
|
||||||
return "Accounts passwords (unencrypted)"
|
|
||||||
case .advancedSettings:
|
|
||||||
return "Advanced"
|
|
||||||
case .recentlyOpened:
|
|
||||||
return "Recents"
|
|
||||||
case .otherData:
|
|
||||||
return "Other data"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Published var selectedExportGroups = Set<ExportGroup>()
|
|
||||||
static var defaultExportGroups = Set<ExportGroup>([
|
|
||||||
.browsingSettings,
|
|
||||||
.playerSettings,
|
|
||||||
.controlsSettings,
|
|
||||||
.qualitySettings,
|
|
||||||
.historySettings,
|
|
||||||
.sponsorBlockSettings,
|
|
||||||
.locationsSettings,
|
|
||||||
.instances,
|
|
||||||
.accounts,
|
|
||||||
.advancedSettings
|
|
||||||
])
|
|
||||||
|
|
||||||
@Published var isExportInProgress = false
|
|
||||||
|
|
||||||
private var navigation = NavigationModel.shared
|
|
||||||
private var settings = SettingsModel.shared
|
|
||||||
|
|
||||||
func toggleExportGroupSelection(_ group: ExportGroup) {
|
|
||||||
if isGroupSelected(group) {
|
|
||||||
selectedExportGroups.remove(group)
|
|
||||||
} else {
|
|
||||||
selectedExportGroups.insert(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNotEnabledSelectedGroups()
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset() {
|
|
||||||
isExportInProgress = false
|
|
||||||
selectedExportGroups = Self.defaultExportGroups
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset(_ model: ImportSettingsFileModel? = nil) {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
guard let model else { return }
|
|
||||||
|
|
||||||
selectedExportGroups = selectedExportGroups.filter { model.isGroupIncludedInFile($0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
func exportAction() {
|
|
||||||
DispatchQueue.global(qos: .background).async { [weak self] in
|
|
||||||
var writingOptions: JSONSerialization.WritingOptions = []
|
|
||||||
#if DEBUG
|
|
||||||
writingOptions.insert(.prettyPrinted)
|
|
||||||
writingOptions.insert(.sortedKeys)
|
|
||||||
#endif
|
|
||||||
try? self?.jsonForExport?.rawString(options: writingOptions)?.write(to: Self.exportFile, atomically: true, encoding: String.Encoding.utf8)
|
|
||||||
#if os(macOS)
|
|
||||||
DispatchQueue.main.async { [weak self] in
|
|
||||||
self?.isExportInProgress = false
|
|
||||||
}
|
|
||||||
NSWorkspace.shared.selectFile(Self.exportFile.path, inFileViewerRootedAtPath: YatteeApp.settingsExportDirectory.path)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var jsonForExport: JSON? {
|
|
||||||
[
|
|
||||||
"metadata": metadataJSON,
|
|
||||||
"browsingSettings": selectedExportGroups.contains(.browsingSettings) ? BrowsingSettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"playerSettings": selectedExportGroups.contains(.playerSettings) ? PlayerSettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"controlsSettings": selectedExportGroups.contains(.controlsSettings) ? ConstrolsSettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"qualitySettings": selectedExportGroups.contains(.qualitySettings) ? QualitySettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"historySettings": selectedExportGroups.contains(.historySettings) ? HistorySettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"sponsorBlockSettings": selectedExportGroups.contains(.sponsorBlockSettings) ? SponsorBlockSettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"locationsSettings": LocationsSettingsGroupExporter(
|
|
||||||
includePublicInstances: isGroupSelected(.locationsSettings),
|
|
||||||
includeInstances: isGroupSelected(.instances),
|
|
||||||
includeAccounts: isGroupSelected(.accounts),
|
|
||||||
includeAccountsUnencryptedPasswords: isGroupSelected(.accountsUnencryptedPasswords)
|
|
||||||
).exportJSON,
|
|
||||||
"advancedSettings": selectedExportGroups.contains(.advancedSettings) ? AdvancedSettingsGroupExporter().exportJSON : JSON(),
|
|
||||||
"recentlyOpened": selectedExportGroups.contains(.recentlyOpened) ? RecentlyOpenedExporter().exportJSON : JSON(),
|
|
||||||
"otherData": selectedExportGroups.contains(.otherData) ? OtherDataSettingsGroupExporter().exportJSON : JSON()
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
private var metadataJSON: JSON {
|
|
||||||
[
|
|
||||||
"build": YatteeApp.build,
|
|
||||||
"timestamp": "\(Date().timeIntervalSince1970)",
|
|
||||||
"platform": Constants.platform
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGroupSelected(_ group: ExportGroup) -> Bool {
|
|
||||||
selectedExportGroups.contains(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isGroupEnabled(_ group: ExportGroup) -> Bool {
|
|
||||||
switch group {
|
|
||||||
case .accounts:
|
|
||||||
return selectedExportGroups.contains(.instances)
|
|
||||||
case .accountsUnencryptedPasswords:
|
|
||||||
return selectedExportGroups.contains(.instances) && selectedExportGroups.contains(.accounts)
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeNotEnabledSelectedGroups() {
|
|
||||||
selectedExportGroups = selectedExportGroups.filter { isGroupEnabled($0) }
|
|
||||||
}
|
|
||||||
|
|
||||||
var isExportAvailable: Bool {
|
|
||||||
!selectedExportGroups.isEmpty && !isExportInProgress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,135 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import Foundation
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct ImportSettingsFileModel {
|
|
||||||
let url: URL
|
|
||||||
|
|
||||||
var filename: String {
|
|
||||||
String(url.lastPathComponent.dropLast(ImportExportSettingsModel.settingsExtension.count + 1))
|
|
||||||
}
|
|
||||||
|
|
||||||
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
|
||||||
if let locationsSettings = json?.dictionaryValue["locationsSettings"] {
|
|
||||||
return LocationsSettingsGroupImporter(
|
|
||||||
json: locationsSettings,
|
|
||||||
includePublicLocations: importExportModel.isGroupEnabled(.locationsSettings),
|
|
||||||
includedInstancesIDs: sheetViewModel.selectedInstances,
|
|
||||||
includedAccountsIDs: sheetViewModel.selectedAccounts,
|
|
||||||
includedAccountsPasswords: sheetViewModel.importableAccountsPasswords
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var importExportModel = ImportExportSettingsModel.shared
|
|
||||||
var sheetViewModel = ImportSettingsSheetViewModel.shared
|
|
||||||
|
|
||||||
func isGroupIncludedInFile(_ group: ImportExportSettingsModel.ExportGroup) -> Bool {
|
|
||||||
switch group {
|
|
||||||
case .locationsSettings:
|
|
||||||
return isPublicInstancesSettingsGroupInFile || instancesOrAccountsInFile
|
|
||||||
default:
|
|
||||||
return !groupJSON(group).isEmpty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPublicInstancesSettingsGroupInFile: Bool {
|
|
||||||
guard let dict = groupJSON(.locationsSettings).dictionary else { return false }
|
|
||||||
|
|
||||||
return dict.keys.contains("instancesManifest") || dict.keys.contains("countryOfPublicInstances")
|
|
||||||
}
|
|
||||||
|
|
||||||
var instancesOrAccountsInFile: Bool {
|
|
||||||
guard let dict = groupJSON(.locationsSettings).dictionary else { return false }
|
|
||||||
|
|
||||||
return (dict.keys.contains("instances") && !(dict["instances"]?.arrayValue.isEmpty ?? true)) ||
|
|
||||||
(dict.keys.contains("accounts") && !(dict["accounts"]?.arrayValue.isEmpty ?? true))
|
|
||||||
}
|
|
||||||
|
|
||||||
func groupJSON(_ group: ImportExportSettingsModel.ExportGroup) -> JSON {
|
|
||||||
json?.dictionaryValue[group.rawValue] ?? .init()
|
|
||||||
}
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if importExportModel.isGroupSelected(.browsingSettings), isGroupIncludedInFile(.browsingSettings) {
|
|
||||||
BrowsingSettingsGroupImporter(json: groupJSON(.browsingSettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.playerSettings), isGroupIncludedInFile(.playerSettings) {
|
|
||||||
PlayerSettingsGroupImporter(json: groupJSON(.playerSettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.controlsSettings), isGroupIncludedInFile(.controlsSettings) {
|
|
||||||
ConstrolsSettingsGroupImporter(json: groupJSON(.controlsSettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.qualitySettings), isGroupIncludedInFile(.qualitySettings) {
|
|
||||||
QualitySettingsGroupImporter(json: groupJSON(.qualitySettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.historySettings), isGroupIncludedInFile(.historySettings) {
|
|
||||||
HistorySettingsGroupImporter(json: groupJSON(.historySettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.sponsorBlockSettings), isGroupIncludedInFile(.sponsorBlockSettings) {
|
|
||||||
SponsorBlockSettingsGroupImporter(json: groupJSON(.sponsorBlockSettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
locationsSettingsGroupImporter?.performImport()
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.advancedSettings), isGroupIncludedInFile(.advancedSettings) {
|
|
||||||
AdvancedSettingsGroupImporter(json: groupJSON(.advancedSettings)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.recentlyOpened), isGroupIncludedInFile(.recentlyOpened) {
|
|
||||||
RecentlyOpenedImporter(json: groupJSON(.recentlyOpened)).performImport()
|
|
||||||
}
|
|
||||||
|
|
||||||
if importExportModel.isGroupSelected(.otherData), isGroupIncludedInFile(.otherData) {
|
|
||||||
OtherDataSettingsGroupImporter(json: groupJSON(.otherData)).performImport()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var json: JSON? {
|
|
||||||
if let fileContents = try? Data(contentsOf: url),
|
|
||||||
let json = try? JSON(data: fileContents)
|
|
||||||
{
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataBuild: String? {
|
|
||||||
if let build = json?.dictionaryValue["metadata"]?.dictionaryValue["build"]?.string {
|
|
||||||
return build
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataPlatform: String? {
|
|
||||||
if let platform = json?.dictionaryValue["metadata"]?.dictionaryValue["platform"]?.string {
|
|
||||||
return platform
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var metadataDate: String? {
|
|
||||||
if let timestamp = json?.dictionaryValue["metadata"]?.dictionaryValue["timestamp"]?.doubleValue {
|
|
||||||
let date = Date(timeIntervalSince1970: timestamp)
|
|
||||||
return dateFormatter.string(from: date)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var dateFormatter: DateFormatter {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateStyle = .long
|
|
||||||
formatter.timeStyle = .medium
|
|
||||||
|
|
||||||
return formatter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct AdvancedSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let showPlayNowInBackendContextMenu = json["showPlayNowInBackendContextMenu"].bool {
|
|
||||||
Defaults[.showPlayNowInBackendContextMenu] = showPlayNowInBackendContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showMPVPlaybackStats = json["showMPVPlaybackStats"].bool {
|
|
||||||
Defaults[.showMPVPlaybackStats] = showMPVPlaybackStats
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mpvEnableLogging = json["mpvEnableLogging"].bool {
|
|
||||||
Defaults[.mpvEnableLogging] = mpvEnableLogging
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mpvCacheSecs = json["mpvCacheSecs"].string {
|
|
||||||
Defaults[.mpvCacheSecs] = mpvCacheSecs
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mpvCachePauseWait = json["mpvCachePauseWait"].string {
|
|
||||||
Defaults[.mpvCachePauseWait] = mpvCachePauseWait
|
|
||||||
}
|
|
||||||
|
|
||||||
if let mpvDeinterlace = json["mpvDeinterlace"].bool {
|
|
||||||
Defaults[.mpvDeinterlace] = mpvDeinterlace
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showCacheStatus = json["showCacheStatus"].bool {
|
|
||||||
Defaults[.showCacheStatus] = showCacheStatus
|
|
||||||
}
|
|
||||||
|
|
||||||
if let feedCacheSize = json["feedCacheSize"].string {
|
|
||||||
Defaults[.feedCacheSize] = feedCacheSize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct BrowsingSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let showHome = json["showHome"].bool {
|
|
||||||
Defaults[.showHome] = showHome
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showOpenActionsInHome = json["showOpenActionsInHome"].bool {
|
|
||||||
Defaults[.showOpenActionsInHome] = showOpenActionsInHome
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showQueueInHome = json["showQueueInHome"].bool {
|
|
||||||
Defaults[.showQueueInHome] = showQueueInHome
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showFavoritesInHome = json["showFavoritesInHome"].bool {
|
|
||||||
Defaults[.showFavoritesInHome] = showFavoritesInHome
|
|
||||||
}
|
|
||||||
|
|
||||||
if let favorites = json["favorites"].array {
|
|
||||||
favorites.forEach { favoriteJSON in
|
|
||||||
if let jsonString = favoriteJSON.rawString(options: []),
|
|
||||||
let item = FavoriteItem.bridge.deserialize(jsonString)
|
|
||||||
{
|
|
||||||
FavoritesModel.shared.add(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let widgetsFavorites = json["widgetsSettings"].array {
|
|
||||||
widgetsFavorites.forEach { widgetJSON in
|
|
||||||
let dict = widgetJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
if let item = WidgetSettingsBridge().deserialize(dict) {
|
|
||||||
FavoritesModel.shared.updateWidgetSettings(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let startupSectionString = json["startupSection"].string,
|
|
||||||
let startupSection = StartupSection(rawValue: startupSectionString)
|
|
||||||
{
|
|
||||||
Defaults[.startupSection] = startupSection
|
|
||||||
}
|
|
||||||
|
|
||||||
if let visibleSections = json["visibleSections"].array {
|
|
||||||
let sections = visibleSections.compactMap { visibleSectionJSON in
|
|
||||||
if let visibleSectionString = visibleSectionJSON.rawString(options: []),
|
|
||||||
let section = VisibleSection(rawValue: visibleSectionString)
|
|
||||||
{
|
|
||||||
return section
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
Defaults[.visibleSections] = Set(sections)
|
|
||||||
}
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
if let showOpenActionsToolbarItem = json["showOpenActionsToolbarItem"].bool {
|
|
||||||
Defaults[.showOpenActionsToolbarItem] = showOpenActionsToolbarItem
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lockPortraitWhenBrowsing = json["lockPortraitWhenBrowsing"].bool {
|
|
||||||
Defaults[.lockPortraitWhenBrowsing] = lockPortraitWhenBrowsing
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
|
||||||
if let accountPickerDisplaysUsername = json["accountPickerDisplaysUsername"].bool {
|
|
||||||
Defaults[.accountPickerDisplaysUsername] = accountPickerDisplaysUsername
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if let accountPickerDisplaysAnonymousAccounts = json["accountPickerDisplaysAnonymousAccounts"].bool {
|
|
||||||
Defaults[.accountPickerDisplaysAnonymousAccounts] = accountPickerDisplaysAnonymousAccounts
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showUnwatchedFeedBadges = json["showUnwatchedFeedBadges"].bool {
|
|
||||||
Defaults[.showUnwatchedFeedBadges] = showUnwatchedFeedBadges
|
|
||||||
}
|
|
||||||
|
|
||||||
if let expandChannelDescription = json["expandChannelDescription"].bool {
|
|
||||||
Defaults[.expandChannelDescription] = expandChannelDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
if let keepChannelsWithUnwatchedFeedOnTop = json["keepChannelsWithUnwatchedFeedOnTop"].bool {
|
|
||||||
Defaults[.keepChannelsWithUnwatchedFeedOnTop] = keepChannelsWithUnwatchedFeedOnTop
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showChannelAvatarInChannelsLists = json["showChannelAvatarInChannelsLists"].bool {
|
|
||||||
Defaults[.showChannelAvatarInChannelsLists] = showChannelAvatarInChannelsLists
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showChannelAvatarInVideosListing = json["showChannelAvatarInVideosListing"].bool {
|
|
||||||
Defaults[.showChannelAvatarInVideosListing] = showChannelAvatarInVideosListing
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerButtonSingleTapGestureString = json["playerButtonSingleTapGesture"].string,
|
|
||||||
let playerButtonSingleTapGesture = PlayerTapGestureAction(rawValue: playerButtonSingleTapGestureString)
|
|
||||||
{
|
|
||||||
Defaults[.playerButtonSingleTapGesture] = playerButtonSingleTapGesture
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerButtonDoubleTapGestureString = json["playerButtonDoubleTapGesture"].string,
|
|
||||||
let playerButtonDoubleTapGesture = PlayerTapGestureAction(rawValue: playerButtonDoubleTapGestureString)
|
|
||||||
{
|
|
||||||
Defaults[.playerButtonDoubleTapGesture] = playerButtonDoubleTapGesture
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerButtonShowsControlButtonsWhenMinimized = json["playerButtonShowsControlButtonsWhenMinimized"].bool {
|
|
||||||
Defaults[.playerButtonShowsControlButtonsWhenMinimized] = playerButtonShowsControlButtonsWhenMinimized
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerButtonIsExpanded = json["playerButtonIsExpanded"].bool {
|
|
||||||
Defaults[.playerButtonIsExpanded] = playerButtonIsExpanded
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerBarMaxWidth = json["playerBarMaxWidth"].string {
|
|
||||||
Defaults[.playerBarMaxWidth] = playerBarMaxWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
if let channelOnThumbnail = json["channelOnThumbnail"].bool {
|
|
||||||
Defaults[.channelOnThumbnail] = channelOnThumbnail
|
|
||||||
}
|
|
||||||
|
|
||||||
if let timeOnThumbnail = json["timeOnThumbnail"].bool {
|
|
||||||
Defaults[.timeOnThumbnail] = timeOnThumbnail
|
|
||||||
}
|
|
||||||
|
|
||||||
if let roundedThumbnails = json["roundedThumbnails"].bool {
|
|
||||||
Defaults[.roundedThumbnails] = roundedThumbnails
|
|
||||||
}
|
|
||||||
|
|
||||||
if let thumbnailsQualityString = json["thumbnailsQuality"].string,
|
|
||||||
let thumbnailsQuality = ThumbnailsQuality(rawValue: thumbnailsQualityString)
|
|
||||||
{
|
|
||||||
Defaults[.thumbnailsQuality] = thumbnailsQuality
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct ConstrolsSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let avPlayerUsesSystemControls = json["avPlayerUsesSystemControls"].bool {
|
|
||||||
Defaults[.avPlayerUsesSystemControls] = avPlayerUsesSystemControls
|
|
||||||
}
|
|
||||||
|
|
||||||
if let horizontalPlayerGestureEnabled = json["horizontalPlayerGestureEnabled"].bool {
|
|
||||||
Defaults[.horizontalPlayerGestureEnabled] = horizontalPlayerGestureEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let seekGestureSensitivity = json["seekGestureSensitivity"].double {
|
|
||||||
Defaults[.seekGestureSensitivity] = seekGestureSensitivity
|
|
||||||
}
|
|
||||||
|
|
||||||
if let seekGestureSpeed = json["seekGestureSpeed"].double {
|
|
||||||
Defaults[.seekGestureSpeed] = seekGestureSpeed
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsLayoutString = json["playerControlsLayout"].string,
|
|
||||||
let playerControlsLayout = PlayerControlsLayout(rawValue: playerControlsLayoutString)
|
|
||||||
{
|
|
||||||
Defaults[.playerControlsLayout] = playerControlsLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
if let fullScreenPlayerControlsLayoutString = json["fullScreenPlayerControlsLayout"].string,
|
|
||||||
let fullScreenPlayerControlsLayout = PlayerControlsLayout(rawValue: fullScreenPlayerControlsLayoutString)
|
|
||||||
{
|
|
||||||
Defaults[.fullScreenPlayerControlsLayout] = fullScreenPlayerControlsLayout
|
|
||||||
}
|
|
||||||
|
|
||||||
if let systemControlsCommandsString = json["systemControlsCommands"].string,
|
|
||||||
let systemControlsCommands = SystemControlsCommands(rawValue: systemControlsCommandsString)
|
|
||||||
{
|
|
||||||
Defaults[.systemControlsCommands] = systemControlsCommands
|
|
||||||
}
|
|
||||||
|
|
||||||
if let buttonBackwardSeekDuration = json["buttonBackwardSeekDuration"].string {
|
|
||||||
Defaults[.buttonBackwardSeekDuration] = buttonBackwardSeekDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if let buttonForwardSeekDuration = json["buttonForwardSeekDuration"].string {
|
|
||||||
Defaults[.buttonForwardSeekDuration] = buttonForwardSeekDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if let gestureBackwardSeekDuration = json["gestureBackwardSeekDuration"].string {
|
|
||||||
Defaults[.gestureBackwardSeekDuration] = gestureBackwardSeekDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if let gestureForwardSeekDuration = json["gestureForwardSeekDuration"].string {
|
|
||||||
Defaults[.gestureForwardSeekDuration] = gestureForwardSeekDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if let systemControlsSeekDuration = json["systemControlsSeekDuration"].string {
|
|
||||||
Defaults[.systemControlsSeekDuration] = systemControlsSeekDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsSettingsEnabled = json["playerControlsSettingsEnabled"].bool {
|
|
||||||
Defaults[.playerControlsSettingsEnabled] = playerControlsSettingsEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsCloseEnabled = json["playerControlsCloseEnabled"].bool {
|
|
||||||
Defaults[.playerControlsCloseEnabled] = playerControlsCloseEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsRestartEnabled = json["playerControlsRestartEnabled"].bool {
|
|
||||||
Defaults[.playerControlsRestartEnabled] = playerControlsRestartEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsAdvanceToNextEnabled = json["playerControlsAdvanceToNextEnabled"].bool {
|
|
||||||
Defaults[.playerControlsAdvanceToNextEnabled] = playerControlsAdvanceToNextEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsPlaybackModeEnabled = json["playerControlsPlaybackModeEnabled"].bool {
|
|
||||||
Defaults[.playerControlsPlaybackModeEnabled] = playerControlsPlaybackModeEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerControlsMusicModeEnabled = json["playerControlsMusicModeEnabled"].bool {
|
|
||||||
Defaults[.playerControlsMusicModeEnabled] = playerControlsMusicModeEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerActionsButtonLabelStyleString = json["playerActionsButtonLabelStyle"].string,
|
|
||||||
let playerActionsButtonLabelStyle = ButtonLabelStyle(rawValue: playerActionsButtonLabelStyleString)
|
|
||||||
{
|
|
||||||
Defaults[.playerActionsButtonLabelStyle] = playerActionsButtonLabelStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonShareEnabled = json["actionButtonShareEnabled"].bool {
|
|
||||||
Defaults[.actionButtonShareEnabled] = actionButtonShareEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonAddToPlaylistEnabled = json["actionButtonAddToPlaylistEnabled"].bool {
|
|
||||||
Defaults[.actionButtonAddToPlaylistEnabled] = actionButtonAddToPlaylistEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonSubscribeEnabled = json["actionButtonSubscribeEnabled"].bool {
|
|
||||||
Defaults[.actionButtonSubscribeEnabled] = actionButtonSubscribeEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonSettingsEnabled = json["actionButtonSettingsEnabled"].bool {
|
|
||||||
Defaults[.actionButtonSettingsEnabled] = actionButtonSettingsEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonHideEnabled = json["actionButtonHideEnabled"].bool {
|
|
||||||
Defaults[.actionButtonHideEnabled] = actionButtonHideEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonCloseEnabled = json["actionButtonCloseEnabled"].bool {
|
|
||||||
Defaults[.actionButtonCloseEnabled] = actionButtonCloseEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonFullScreenEnabled = json["actionButtonFullScreenEnabled"].bool {
|
|
||||||
Defaults[.actionButtonFullScreenEnabled] = actionButtonFullScreenEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonPipEnabled = json["actionButtonPipEnabled"].bool {
|
|
||||||
Defaults[.actionButtonPipEnabled] = actionButtonPipEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonLockOrientationEnabled = json["actionButtonLockOrientationEnabled"].bool {
|
|
||||||
Defaults[.actionButtonLockOrientationEnabled] = actionButtonLockOrientationEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonRestartEnabled = json["actionButtonRestartEnabled"].bool {
|
|
||||||
Defaults[.actionButtonRestartEnabled] = actionButtonRestartEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonAdvanceToNextItemEnabled = json["actionButtonAdvanceToNextItemEnabled"].bool {
|
|
||||||
Defaults[.actionButtonAdvanceToNextItemEnabled] = actionButtonAdvanceToNextItemEnabled
|
|
||||||
}
|
|
||||||
|
|
||||||
if let actionButtonMusicModeEnabled = json["actionButtonMusicModeEnabled"].bool {
|
|
||||||
Defaults[.actionButtonMusicModeEnabled] = actionButtonMusicModeEnabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct HistorySettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let saveRecents = json["saveRecents"].bool {
|
|
||||||
Defaults[.saveRecents] = saveRecents
|
|
||||||
}
|
|
||||||
|
|
||||||
if let saveHistory = json["saveHistory"].bool {
|
|
||||||
Defaults[.saveHistory] = saveHistory
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showWatchingProgress = json["showWatchingProgress"].bool {
|
|
||||||
Defaults[.showWatchingProgress] = showWatchingProgress
|
|
||||||
}
|
|
||||||
|
|
||||||
if let saveLastPlayed = json["saveLastPlayed"].bool {
|
|
||||||
Defaults[.saveLastPlayed] = saveLastPlayed
|
|
||||||
}
|
|
||||||
|
|
||||||
if let watchedVideoPlayNowBehaviorString = json["watchedVideoPlayNowBehavior"].string,
|
|
||||||
let watchedVideoPlayNowBehavior = WatchedVideoPlayNowBehavior(rawValue: watchedVideoPlayNowBehaviorString)
|
|
||||||
{
|
|
||||||
Defaults[.watchedVideoPlayNowBehavior] = watchedVideoPlayNowBehavior
|
|
||||||
}
|
|
||||||
|
|
||||||
if let watchedThreshold = json["watchedThreshold"].int {
|
|
||||||
Defaults[.watchedThreshold] = watchedThreshold
|
|
||||||
}
|
|
||||||
|
|
||||||
if let resetWatchedStatusOnPlaying = json["resetWatchedStatusOnPlaying"].bool {
|
|
||||||
Defaults[.resetWatchedStatusOnPlaying] = resetWatchedStatusOnPlaying
|
|
||||||
}
|
|
||||||
|
|
||||||
if let watchedVideoStyleString = json["watchedVideoStyle"].string,
|
|
||||||
let watchedVideoStyle = WatchedVideoStyle(rawValue: watchedVideoStyleString)
|
|
||||||
{
|
|
||||||
Defaults[.watchedVideoStyle] = watchedVideoStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
if let watchedVideoBadgeColorString = json["watchedVideoBadgeColor"].string,
|
|
||||||
let watchedVideoBadgeColor = WatchedVideoBadgeColor(rawValue: watchedVideoBadgeColorString)
|
|
||||||
{
|
|
||||||
Defaults[.watchedVideoBadgeColor] = watchedVideoBadgeColor
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showToggleWatchedStatusButton = json["showToggleWatchedStatusButton"].bool {
|
|
||||||
Defaults[.showToggleWatchedStatusButton] = showToggleWatchedStatusButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct LocationsSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
var includePublicLocations = true
|
|
||||||
var includedInstancesIDs = Set<Instance.ID>()
|
|
||||||
var includedAccountsIDs = Set<Account.ID>()
|
|
||||||
var includedAccountsPasswords = [Account.ID: String]()
|
|
||||||
|
|
||||||
init(
|
|
||||||
json: JSON,
|
|
||||||
includePublicLocations: Bool = true,
|
|
||||||
includedInstancesIDs: Set<Instance.ID> = [],
|
|
||||||
includedAccountsIDs: Set<Account.ID> = [],
|
|
||||||
includedAccountsPasswords: [Account.ID: String] = [:]
|
|
||||||
) {
|
|
||||||
self.json = json
|
|
||||||
self.includePublicLocations = includePublicLocations
|
|
||||||
self.includedInstancesIDs = includedInstancesIDs
|
|
||||||
self.includedAccountsIDs = includedAccountsIDs
|
|
||||||
self.includedAccountsPasswords = includedAccountsPasswords
|
|
||||||
}
|
|
||||||
|
|
||||||
var instances: [Instance] {
|
|
||||||
if let instances = json["instances"].array {
|
|
||||||
return instances.compactMap { instanceJSON in
|
|
||||||
let dict = instanceJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
return InstancesBridge().deserialize(dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
var accounts: [Account] {
|
|
||||||
if let accounts = json["accounts"].array {
|
|
||||||
return accounts.compactMap { accountJSON in
|
|
||||||
let dict = accountJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
return AccountsBridge().deserialize(dict)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if includePublicLocations {
|
|
||||||
Defaults[.instancesManifest] = json["instancesManifest"].string ?? ""
|
|
||||||
Defaults[.countryOfPublicInstances] = json["countryOfPublicInstances"].string ?? ""
|
|
||||||
}
|
|
||||||
|
|
||||||
instances.filter { includedInstancesIDs.contains($0.id) }.forEach { instance in
|
|
||||||
_ = InstancesModel.shared.insert(id: instance.id, app: instance.app, name: instance.name, url: instance.apiURLString)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let accounts = json["accounts"].array {
|
|
||||||
accounts.forEach { accountJSON in
|
|
||||||
let dict = accountJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
if let account = AccountsBridge().deserialize(dict),
|
|
||||||
includedAccountsIDs.contains(account.id)
|
|
||||||
{
|
|
||||||
var password = account.password
|
|
||||||
if password?.isEmpty ?? true {
|
|
||||||
password = includedAccountsPasswords[account.id]
|
|
||||||
}
|
|
||||||
if let password,
|
|
||||||
!password.isEmpty,
|
|
||||||
let instanceID = account.instanceID,
|
|
||||||
let instance = InstancesModel.shared.find(instanceID)
|
|
||||||
{
|
|
||||||
if !instance.accounts.contains(where: { instanceAccount in
|
|
||||||
let (username, _) = instanceAccount.credentials
|
|
||||||
return username == account.username
|
|
||||||
}) {
|
|
||||||
_ = AccountsModel.add(instance: instance, id: account.id, name: account.name, username: account.username, password: password)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct OtherDataSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let lastAccountID = json["lastAccountID"].string {
|
|
||||||
Defaults[.lastAccountID] = lastAccountID
|
|
||||||
}
|
|
||||||
|
|
||||||
if let lastInstanceID = json["lastInstanceID"].string {
|
|
||||||
Defaults[.lastInstanceID] = lastInstanceID
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerRate = json["playerRate"].double {
|
|
||||||
Defaults[.playerRate] = playerRate
|
|
||||||
}
|
|
||||||
|
|
||||||
if let trendingCategoryString = json["trendingCategory"].string,
|
|
||||||
let trendingCategory = TrendingCategory(rawValue: trendingCategoryString)
|
|
||||||
{
|
|
||||||
Defaults[.trendingCategory] = trendingCategory
|
|
||||||
}
|
|
||||||
|
|
||||||
if let trendingCountryString = json["trendingCountry"].string,
|
|
||||||
let trendingCountry = Country(rawValue: trendingCountryString)
|
|
||||||
{
|
|
||||||
Defaults[.trendingCountry] = trendingCountry
|
|
||||||
}
|
|
||||||
|
|
||||||
if let subscriptionsViewPageString = json["subscriptionsViewPage"].string,
|
|
||||||
let subscriptionsViewPage = SubscriptionsView.Page(rawValue: subscriptionsViewPageString)
|
|
||||||
{
|
|
||||||
Defaults[.subscriptionsViewPage] = subscriptionsViewPage
|
|
||||||
}
|
|
||||||
|
|
||||||
if let subscriptionsListingStyle = json["subscriptionsListingStyle"].string {
|
|
||||||
Defaults[.subscriptionsListingStyle] = ListingStyle(rawValue: subscriptionsListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let popularListingStyle = json["popularListingStyle"].string {
|
|
||||||
Defaults[.popularListingStyle] = ListingStyle(rawValue: popularListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let trendingListingStyle = json["trendingListingStyle"].string {
|
|
||||||
Defaults[.trendingListingStyle] = ListingStyle(rawValue: trendingListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playlistListingStyle = json["playlistListingStyle"].string {
|
|
||||||
Defaults[.playlistListingStyle] = ListingStyle(rawValue: playlistListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let channelPlaylistListingStyle = json["channelPlaylistListingStyle"].string {
|
|
||||||
Defaults[.channelPlaylistListingStyle] = ListingStyle(rawValue: channelPlaylistListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let searchListingStyle = json["searchListingStyle"].string {
|
|
||||||
Defaults[.searchListingStyle] = ListingStyle(rawValue: searchListingStyle) ?? .list
|
|
||||||
}
|
|
||||||
|
|
||||||
if let hideShorts = json["hideShorts"].bool {
|
|
||||||
Defaults[.hideShorts] = hideShorts
|
|
||||||
}
|
|
||||||
|
|
||||||
if let hideWatched = json["hideWatched"].bool {
|
|
||||||
Defaults[.hideWatched] = hideWatched
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct PlayerSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let playerInstanceID = json["playerInstanceID"].string {
|
|
||||||
Defaults[.playerInstanceID] = playerInstanceID
|
|
||||||
}
|
|
||||||
|
|
||||||
if let pauseOnHidingPlayer = json["pauseOnHidingPlayer"].bool {
|
|
||||||
Defaults[.pauseOnHidingPlayer] = pauseOnHidingPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
if let closeVideoOnEOF = json["closeVideoOnEOF"].bool {
|
|
||||||
Defaults[.closeVideoOnEOF] = closeVideoOnEOF
|
|
||||||
}
|
|
||||||
|
|
||||||
if let expandVideoDescription = json["expandVideoDescription"].bool {
|
|
||||||
Defaults[.expandVideoDescription] = expandVideoDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
if let collapsedLinesDescription = json["collapsedLinesDescription"].int {
|
|
||||||
Defaults[.collapsedLinesDescription] = collapsedLinesDescription
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showChapters = json["showChapters"].bool {
|
|
||||||
Defaults[.showChapters] = showChapters
|
|
||||||
}
|
|
||||||
|
|
||||||
if let expandChapters = json["expandChapters"].bool {
|
|
||||||
Defaults[.expandChapters] = expandChapters
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showRelated = json["showRelated"].bool {
|
|
||||||
Defaults[.showRelated] = showRelated
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showInspectorString = json["showInspector"].string,
|
|
||||||
let showInspector = ShowInspectorSetting(rawValue: showInspectorString)
|
|
||||||
{
|
|
||||||
Defaults[.showInspector] = showInspector
|
|
||||||
}
|
|
||||||
|
|
||||||
if let playerSidebarString = json["playerSidebar"].string,
|
|
||||||
let playerSidebar = PlayerSidebarSetting(rawValue: playerSidebarString)
|
|
||||||
{
|
|
||||||
Defaults[.playerSidebar] = playerSidebar
|
|
||||||
}
|
|
||||||
|
|
||||||
if let showKeywords = json["showKeywords"].bool {
|
|
||||||
Defaults[.showKeywords] = showKeywords
|
|
||||||
}
|
|
||||||
|
|
||||||
if let enableReturnYouTubeDislike = json["enableReturnYouTubeDislike"].bool {
|
|
||||||
Defaults[.enableReturnYouTubeDislike] = enableReturnYouTubeDislike
|
|
||||||
}
|
|
||||||
|
|
||||||
if let closePiPOnNavigation = json["closePiPOnNavigation"].bool {
|
|
||||||
Defaults[.closePiPOnNavigation] = closePiPOnNavigation
|
|
||||||
}
|
|
||||||
|
|
||||||
if let closePiPOnOpeningPlayer = json["closePiPOnOpeningPlayer"].bool {
|
|
||||||
Defaults[.closePiPOnOpeningPlayer] = closePiPOnOpeningPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
if let closePlayerOnOpeningPiP = json["closePlayerOnOpeningPiP"].bool {
|
|
||||||
Defaults[.closePlayerOnOpeningPiP] = closePlayerOnOpeningPiP
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
if let pauseOnEnteringBackground = json["pauseOnEnteringBackground"].bool {
|
|
||||||
Defaults[.pauseOnEnteringBackground] = pauseOnEnteringBackground
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
|
||||||
if let showScrollToTopInComments = json["showScrollToTopInComments"].bool {
|
|
||||||
Defaults[.showScrollToTopInComments] = showScrollToTopInComments
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
if let honorSystemOrientationLock = json["honorSystemOrientationLock"].bool {
|
|
||||||
Defaults[.honorSystemOrientationLock] = honorSystemOrientationLock
|
|
||||||
}
|
|
||||||
|
|
||||||
if let enterFullscreenInLandscape = json["enterFullscreenInLandscape"].bool {
|
|
||||||
Defaults[.enterFullscreenInLandscape] = enterFullscreenInLandscape
|
|
||||||
}
|
|
||||||
|
|
||||||
if let rotateToLandscapeOnEnterFullScreenString = json["rotateToLandscapeOnEnterFullScreen"].string,
|
|
||||||
let rotateToLandscapeOnEnterFullScreen = FullScreenRotationSetting(rawValue: rotateToLandscapeOnEnterFullScreenString)
|
|
||||||
{
|
|
||||||
Defaults[.rotateToLandscapeOnEnterFullScreen] = rotateToLandscapeOnEnterFullScreen
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct QualitySettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let batteryCellularProfileString = json["batteryCellularProfile"].string {
|
|
||||||
Defaults[.batteryCellularProfile] = batteryCellularProfileString
|
|
||||||
}
|
|
||||||
|
|
||||||
if let batteryNonCellularProfileString = json["batteryNonCellularProfile"].string {
|
|
||||||
Defaults[.batteryNonCellularProfile] = batteryNonCellularProfileString
|
|
||||||
}
|
|
||||||
|
|
||||||
if let chargingCellularProfileString = json["chargingCellularProfile"].string {
|
|
||||||
Defaults[.chargingCellularProfile] = chargingCellularProfileString
|
|
||||||
}
|
|
||||||
|
|
||||||
if let chargingNonCellularProfileString = json["chargingNonCellularProfile"].string {
|
|
||||||
Defaults[.chargingNonCellularProfile] = chargingNonCellularProfileString
|
|
||||||
}
|
|
||||||
|
|
||||||
if let forceAVPlayerForLiveStreams = json["forceAVPlayerForLiveStreams"].bool {
|
|
||||||
Defaults[.forceAVPlayerForLiveStreams] = forceAVPlayerForLiveStreams
|
|
||||||
}
|
|
||||||
|
|
||||||
if let qualityProfiles = json["qualityProfiles"].array {
|
|
||||||
qualityProfiles.forEach { qualityProfileJSON in
|
|
||||||
let dict = qualityProfileJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
if let item = QualityProfileBridge().deserialize(dict) {
|
|
||||||
QualityProfilesModel.shared.update(item, item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct RecentlyOpenedImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let recentlyOpened = json["recentlyOpened"].array {
|
|
||||||
recentlyOpened.forEach { recentlyOpenedJSON in
|
|
||||||
let dict = recentlyOpenedJSON.dictionaryValue.mapValues { json in json.stringValue }
|
|
||||||
if let item = RecentItemBridge().deserialize(dict) {
|
|
||||||
RecentsModel.shared.add(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import Defaults
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct SponsorBlockSettingsGroupImporter {
|
|
||||||
var json: JSON
|
|
||||||
|
|
||||||
func performImport() {
|
|
||||||
if let sponsorBlockInstance = json["sponsorBlockInstance"].string {
|
|
||||||
Defaults[.sponsorBlockInstance] = sponsorBlockInstance
|
|
||||||
}
|
|
||||||
|
|
||||||
if let sponsorBlockCategories = json["sponsorBlockCategories"].array {
|
|
||||||
Defaults[.sponsorBlockCategories] = Set(sponsorBlockCategories.compactMap { $0.string })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -107,10 +107,6 @@ final class NavigationModel: ObservableObject {
|
|||||||
|
|
||||||
@Published var presentingFileImporter = false
|
@Published var presentingFileImporter = false
|
||||||
|
|
||||||
@Published var presentingSettingsImportSheet = false
|
|
||||||
@Published var presentingSettingsFileImporter = false
|
|
||||||
@Published var settingsImportURL: URL?
|
|
||||||
|
|
||||||
func openChannel(_ channel: Channel, navigationStyle: NavigationStyle) {
|
func openChannel(_ channel: Channel, navigationStyle: NavigationStyle) {
|
||||||
guard channel.id != Video.fixtureChannelID else {
|
guard channel.id != Video.fixtureChannelID else {
|
||||||
return
|
return
|
||||||
@@ -273,8 +269,6 @@ final class NavigationModel: ObservableObject {
|
|||||||
presentingChannel = false
|
presentingChannel = false
|
||||||
presentingPlaylist = false
|
presentingPlaylist = false
|
||||||
presentingOpenVideos = false
|
presentingOpenVideos = false
|
||||||
presentingFileImporter = false
|
|
||||||
presentingSettingsImportSheet = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func hideKeyboard() {
|
func hideKeyboard() {
|
||||||
@@ -285,9 +279,8 @@ final class NavigationModel: ObservableObject {
|
|||||||
|
|
||||||
func presentAlert(title: String, message: String? = nil) {
|
func presentAlert(title: String, message: String? = nil) {
|
||||||
let message = message.isNil ? nil : Text(message!)
|
let message = message.isNil ? nil : Text(message!)
|
||||||
let alert = Alert(title: Text(title), message: message)
|
alert = Alert(title: Text(title), message: message)
|
||||||
|
presentingAlert = true
|
||||||
presentAlert(alert)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentRequestErrorAlert(_ error: RequestError) {
|
func presentRequestErrorAlert(_ error: RequestError) {
|
||||||
@@ -296,11 +289,6 @@ final class NavigationModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func presentAlert(_ alert: Alert) {
|
func presentAlert(_ alert: Alert) {
|
||||||
guard !presentingSettings else {
|
|
||||||
SettingsModel.shared.presentAlert(alert)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.alert = alert
|
self.alert = alert
|
||||||
presentingAlert = true
|
presentingAlert = true
|
||||||
}
|
}
|
||||||
@@ -323,16 +311,6 @@ final class NavigationModel: ObservableObject {
|
|||||||
print("not implemented")
|
print("not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentSettingsImportSheet(_ url: URL, forceSettings: Bool = false) {
|
|
||||||
guard !presentingSettings, !forceSettings else {
|
|
||||||
ImportExportSettingsModel.shared.reset()
|
|
||||||
SettingsModel.shared.presentSettingsImportSheet(url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
settingsImportURL = url
|
|
||||||
presentingSettingsImportSheet = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typealias TabSelection = NavigationModel.TabSelection
|
typealias TabSelection = NavigationModel.TabSelection
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ final class MPVClient: ObservableObject {
|
|||||||
checkError(mpv_set_option_string(mpv, "hwdec", machine == "x86_64" ? "no" : "auto-safe"))
|
checkError(mpv_set_option_string(mpv, "hwdec", machine == "x86_64" ? "no" : "auto-safe"))
|
||||||
checkError(mpv_set_option_string(mpv, "vo", "libmpv"))
|
checkError(mpv_set_option_string(mpv, "vo", "libmpv"))
|
||||||
checkError(mpv_set_option_string(mpv, "demuxer-lavf-analyzeduration", "1"))
|
checkError(mpv_set_option_string(mpv, "demuxer-lavf-analyzeduration", "1"))
|
||||||
checkError(mpv_set_option_string(mpv, "deinterlace", Defaults[.mpvDeinterlace] ? "yes" : "no"))
|
|
||||||
|
|
||||||
checkError(mpv_initialize(mpv))
|
checkError(mpv_initialize(mpv))
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,6 @@ final class SettingsModel: ObservableObject {
|
|||||||
@Published var presentingAlert = false
|
@Published var presentingAlert = false
|
||||||
@Published var alert = Alert(title: Text("Error"))
|
@Published var alert = Alert(title: Text("Error"))
|
||||||
|
|
||||||
@Published var presentingSettingsImportSheet = false
|
|
||||||
@Published var settingsImportURL: URL?
|
|
||||||
|
|
||||||
func presentAlert(title: String, message: String? = nil) {
|
func presentAlert(title: String, message: String? = nil) {
|
||||||
let message = message.isNil ? nil : Text(message!)
|
let message = message.isNil ? nil : Text(message!)
|
||||||
alert = Alert(title: Text(title), message: message)
|
alert = Alert(title: Text(title), message: message)
|
||||||
@@ -20,9 +17,4 @@ final class SettingsModel: ObservableObject {
|
|||||||
self.alert = alert
|
self.alert = alert
|
||||||
presentingAlert = true
|
presentingAlert = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func presentSettingsImportSheet(_ url: URL) {
|
|
||||||
settingsImportURL = url
|
|
||||||
presentingSettingsImportSheet = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import Defaults
|
|||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum Constants {
|
struct Constants {
|
||||||
static let yatteeProtocol = "yattee://"
|
static let yatteeProtocol = "yattee://"
|
||||||
static let overlayAnimation = Animation.linear(duration: 0.2)
|
static let overlayAnimation = Animation.linear(duration: 0.2)
|
||||||
static var isIPhone: Bool {
|
static var isIPhone: Bool {
|
||||||
@@ -61,26 +61,6 @@ enum Constants {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
static var deviceName: String {
|
|
||||||
#if os(macOS)
|
|
||||||
Host().localizedName ?? "Mac"
|
|
||||||
#else
|
|
||||||
UIDevice.current.name
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static var platform: String {
|
|
||||||
#if os(macOS)
|
|
||||||
"macOS"
|
|
||||||
#elseif os(iOS)
|
|
||||||
"iOS"
|
|
||||||
#elseif os(tvOS)
|
|
||||||
"tvOS"
|
|
||||||
#else
|
|
||||||
"unknown"
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static func seekIcon(_ type: String, _ interval: TimeInterval) -> String {
|
static func seekIcon(_ type: String, _ interval: TimeInterval) -> String {
|
||||||
let interval = Int(interval)
|
let interval = Int(interval)
|
||||||
let allVersions = [10, 15, 30, 45, 60, 75, 90]
|
let allVersions = [10, 15, 30, 45, 60, 75, 90]
|
||||||
|
|||||||
@@ -6,22 +6,37 @@ import SwiftUI
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extension Defaults.Keys {
|
extension Defaults.Keys {
|
||||||
// MARK: GROUP - Browsing
|
static let instancesManifest = Key<String>("instancesManifest", default: "")
|
||||||
|
static let countryOfPublicInstances = Key<String?>("countryOfPublicInstances")
|
||||||
|
|
||||||
|
static let instances = Key<[Instance]>("instances", default: [])
|
||||||
|
static let accounts = Key<[Account]>("accounts", default: [])
|
||||||
|
static let lastAccountID = Key<Account.ID?>("lastAccountID")
|
||||||
|
static let lastInstanceID = Key<Instance.ID?>("lastInstanceID")
|
||||||
|
static let lastUsedPlaylistID = Key<Playlist.ID?>("lastPlaylistID")
|
||||||
|
static let lastAccountIsPublic = Key<Bool>("lastAccountIsPublic", default: false)
|
||||||
|
|
||||||
|
static let sponsorBlockInstance = Key<String>("sponsorBlockInstance", default: "https://sponsor.ajay.app")
|
||||||
|
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
||||||
|
|
||||||
|
static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false)
|
||||||
|
|
||||||
static let showHome = Key<Bool>("showHome", default: true)
|
static let showHome = Key<Bool>("showHome", default: true)
|
||||||
static let showOpenActionsInHome = Key<Bool>("showOpenActionsInHome", default: true)
|
static let showOpenActionsInHome = Key<Bool>("showOpenActionsInHome", default: true)
|
||||||
static let showQueueInHome = Key<Bool>("showQueueInHome", default: true)
|
static let showQueueInHome = Key<Bool>("showQueueInHome", default: true)
|
||||||
static let showFavoritesInHome = Key<Bool>("showFavoritesInHome", default: true)
|
|
||||||
static let favorites = Key<[FavoriteItem]>("favorites", default: [])
|
|
||||||
static let widgetsSettings = Key<[WidgetSettings]>("widgetsSettings", default: [])
|
|
||||||
static let startupSection = Key<StartupSection>("startupSection", default: .home)
|
|
||||||
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.subscriptions, .trending, .playlists])
|
|
||||||
|
|
||||||
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
|
static let showOpenActionsToolbarItem = Key<Bool>("showOpenActionsToolbarItem", default: false)
|
||||||
|
static let showFavoritesInHome = Key<Bool>("showFavoritesInHome", default: true)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
static let showDocuments = Key<Bool>("showDocuments", default: false)
|
static let showDocuments = Key<Bool>("showDocuments", default: false)
|
||||||
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
|
|
||||||
#endif
|
#endif
|
||||||
|
static let homeHistoryItems = Key<Int>("homeHistoryItems", default: 10)
|
||||||
|
static let favorites = Key<[FavoriteItem]>("favorites", default: [])
|
||||||
|
|
||||||
|
static let playerButtonSingleTapGesture = Key<PlayerTapGestureAction>("playerButtonSingleTapGesture", default: .togglePlayer)
|
||||||
|
static let playerButtonDoubleTapGesture = Key<PlayerTapGestureAction>("playerButtonDoubleTapGesture", default: .nothing)
|
||||||
|
static let playerButtonShowsControlButtonsWhenMinimized = Key<Bool>("playerButtonShowsControlButtonsWhenMinimized", default: false)
|
||||||
|
static let playerButtonIsExpanded = Key<Bool>("playerButtonIsExpanded", default: false)
|
||||||
|
static let playerBarMaxWidth = Key<String>("playerBarMaxWidth", default: "600")
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
@@ -31,139 +46,21 @@ extension Defaults.Keys {
|
|||||||
#endif
|
#endif
|
||||||
static let accountPickerDisplaysUsername = Key<Bool>("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault)
|
static let accountPickerDisplaysUsername = Key<Bool>("accountPickerDisplaysUsername", default: accountPickerDisplaysUsernameDefault)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static let accountPickerDisplaysAnonymousAccounts = Key<Bool>("accountPickerDisplaysAnonymousAccounts", default: true)
|
static let accountPickerDisplaysAnonymousAccounts = Key<Bool>("accountPickerDisplaysAnonymousAccounts", default: true)
|
||||||
|
#if os(iOS)
|
||||||
|
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
|
||||||
|
#endif
|
||||||
static let showUnwatchedFeedBadges = Key<Bool>("showUnwatchedFeedBadges", default: false)
|
static let showUnwatchedFeedBadges = Key<Bool>("showUnwatchedFeedBadges", default: false)
|
||||||
static let expandChannelDescription = Key<Bool>("expandChannelDescription", default: false)
|
|
||||||
|
|
||||||
static let keepChannelsWithUnwatchedFeedOnTop = Key<Bool>("keepChannelsWithUnwatchedFeedOnTop", default: true)
|
static let keepChannelsWithUnwatchedFeedOnTop = Key<Bool>("keepChannelsWithUnwatchedFeedOnTop", default: true)
|
||||||
static let showChannelAvatarInChannelsLists = Key<Bool>("showChannelAvatarInChannelsLists", default: true)
|
static let showToggleWatchedStatusButton = Key<Bool>("showToggleWatchedStatusButton", default: false)
|
||||||
static let showChannelAvatarInVideosListing = Key<Bool>("showChannelAvatarInVideosListing", default: true)
|
static let expandChannelDescription = Key<Bool>("expandChannelDescription", default: false)
|
||||||
|
|
||||||
static let playerButtonSingleTapGesture = Key<PlayerTapGestureAction>("playerButtonSingleTapGesture", default: .togglePlayer)
|
|
||||||
static let playerButtonDoubleTapGesture = Key<PlayerTapGestureAction>("playerButtonDoubleTapGesture", default: .nothing)
|
|
||||||
static let playerButtonShowsControlButtonsWhenMinimized = Key<Bool>("playerButtonShowsControlButtonsWhenMinimized", default: false)
|
|
||||||
static let playerButtonIsExpanded = Key<Bool>("playerButtonIsExpanded", default: false)
|
|
||||||
static let playerBarMaxWidth = Key<String>("playerBarMaxWidth", default: "600")
|
|
||||||
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false)
|
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false)
|
||||||
static let timeOnThumbnail = Key<Bool>("timeOnThumbnail", default: true)
|
static let timeOnThumbnail = Key<Bool>("timeOnThumbnail", default: true)
|
||||||
static let roundedThumbnails = Key<Bool>("roundedThumbnails", default: true)
|
static let roundedThumbnails = Key<Bool>("roundedThumbnails", default: true)
|
||||||
static let thumbnailsQuality = Key<ThumbnailsQuality>("thumbnailsQuality", default: .highest)
|
static let thumbnailsQuality = Key<ThumbnailsQuality>("thumbnailsQuality", default: .highest)
|
||||||
|
|
||||||
// MARK: GROUP - Player
|
static let captionsLanguageCode = Key<String?>("captionsLanguageCode")
|
||||||
|
static let activeBackend = Key<PlayerBackendType>("activeBackend", default: .mpv)
|
||||||
static let playerInstanceID = Key<Instance.ID?>("playerInstance")
|
|
||||||
|
|
||||||
#if os(tvOS)
|
|
||||||
static let pauseOnHidingPlayerDefault = true
|
|
||||||
#else
|
|
||||||
static let pauseOnHidingPlayerDefault = false
|
|
||||||
#endif
|
|
||||||
static let pauseOnHidingPlayer = Key<Bool>("pauseOnHidingPlayer", default: pauseOnHidingPlayerDefault)
|
|
||||||
|
|
||||||
static let closeVideoOnEOF = Key<Bool>("closeVideoOnEOF", default: false)
|
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
static let pauseOnEnteringBackground = Key<Bool>("pauseOnEnteringBackground", default: true)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
static let expandVideoDescriptionDefault = Constants.isIPad
|
|
||||||
#else
|
|
||||||
static let expandVideoDescriptionDefault = true
|
|
||||||
#endif
|
|
||||||
static let expandVideoDescription = Key<Bool>("expandVideoDescription", default: expandVideoDescriptionDefault)
|
|
||||||
|
|
||||||
static let collapsedLinesDescription = Key<Int>("collapsedLinesDescription", default: 5)
|
|
||||||
|
|
||||||
static let showChapters = Key<Bool>("showChapters", default: true)
|
|
||||||
static let expandChapters = Key<Bool>("expandChapters", default: true)
|
|
||||||
static let showRelated = Key<Bool>("showRelated", default: true)
|
|
||||||
static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)
|
|
||||||
|
|
||||||
static let playerSidebar = Key<PlayerSidebarSetting>("playerSidebar", default: .defaultValue)
|
|
||||||
static let showKeywords = Key<Bool>("showKeywords", default: false)
|
|
||||||
#if !os(tvOS)
|
|
||||||
static let showScrollToTopInComments = Key<Bool>("showScrollToTopInComments", default: true)
|
|
||||||
#endif
|
|
||||||
static let enableReturnYouTubeDislike = Key<Bool>("enableReturnYouTubeDislike", default: false)
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
|
|
||||||
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone)
|
|
||||||
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>(
|
|
||||||
"rotateToLandscapeOnEnterFullScreen",
|
|
||||||
default: UIDevice.current.userInterfaceIdiom == .phone ? .landscapeRight : .disabled
|
|
||||||
)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static let closePiPOnNavigation = Key<Bool>("closePiPOnNavigation", default: false)
|
|
||||||
static let closePiPOnOpeningPlayer = Key<Bool>("closePiPOnOpeningPlayer", default: false)
|
|
||||||
static let closePlayerOnOpeningPiP = Key<Bool>("closePlayerOnOpeningPiP", default: false)
|
|
||||||
#if !os(macOS)
|
|
||||||
static let closePiPAndOpenPlayerOnEnteringForeground = Key<Bool>("closePiPAndOpenPlayerOnEnteringForeground", default: false)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// MARK: GROUP - Controls
|
|
||||||
|
|
||||||
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
|
|
||||||
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
|
|
||||||
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
|
|
||||||
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
static let playerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
|
|
||||||
static let fullScreenPlayerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
|
|
||||||
#elseif os(tvOS)
|
|
||||||
static let playerControlsLayoutDefault = PlayerControlsLayout.tvRegular
|
|
||||||
static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.tvRegular
|
|
||||||
#else
|
|
||||||
static let playerControlsLayoutDefault = PlayerControlsLayout.medium
|
|
||||||
static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.medium
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static let playerControlsLayout = Key<PlayerControlsLayout>("playerControlsLayout", default: playerControlsLayoutDefault)
|
|
||||||
static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault)
|
|
||||||
|
|
||||||
static let systemControlsCommands = Key<SystemControlsCommands>("systemControlsCommands", default: .restartAndAdvanceToNext)
|
|
||||||
|
|
||||||
static let buttonBackwardSeekDuration = Key<String>("buttonBackwardSeekDuration", default: "10")
|
|
||||||
static let buttonForwardSeekDuration = Key<String>("buttonForwardSeekDuration", default: "10")
|
|
||||||
static let gestureBackwardSeekDuration = Key<String>("gestureBackwardSeekDuration", default: "10")
|
|
||||||
static let gestureForwardSeekDuration = Key<String>("gestureForwardSeekDuration", default: "10")
|
|
||||||
static let systemControlsSeekDuration = Key<String>("systemControlsBackwardSeekDuration", default: "10")
|
|
||||||
|
|
||||||
#if os(iOS)
|
|
||||||
static let playerControlsLockOrientationEnabled = Key<Bool>("playerControlsLockOrientationEnabled", default: true)
|
|
||||||
#endif
|
|
||||||
#if os(tvOS)
|
|
||||||
static let playerControlsSettingsEnabledDefault = true
|
|
||||||
#else
|
|
||||||
static let playerControlsSettingsEnabledDefault = false
|
|
||||||
#endif
|
|
||||||
static let playerControlsSettingsEnabled = Key<Bool>("playerControlsSettingsEnabled", default: playerControlsSettingsEnabledDefault)
|
|
||||||
static let playerControlsCloseEnabled = Key<Bool>("playerControlsCloseEnabled", default: true)
|
|
||||||
static let playerControlsRestartEnabled = Key<Bool>("playerControlsRestartEnabled", default: false)
|
|
||||||
static let playerControlsAdvanceToNextEnabled = Key<Bool>("playerControlsAdvanceToNextEnabled", default: false)
|
|
||||||
static let playerControlsPlaybackModeEnabled = Key<Bool>("playerControlsPlaybackModeEnabled", default: false)
|
|
||||||
static let playerControlsMusicModeEnabled = Key<Bool>("playerControlsMusicModeEnabled", default: false)
|
|
||||||
|
|
||||||
static let playerActionsButtonLabelStyle = Key<ButtonLabelStyle>("playerActionsButtonLabelStyle", default: .iconAndText)
|
|
||||||
|
|
||||||
static let actionButtonShareEnabled = Key<Bool>("actionButtonShareEnabled", default: true)
|
|
||||||
static let actionButtonAddToPlaylistEnabled = Key<Bool>("actionButtonAddToPlaylistEnabled", default: true)
|
|
||||||
static let actionButtonSubscribeEnabled = Key<Bool>("actionButtonSubscribeEnabled", default: false)
|
|
||||||
static let actionButtonSettingsEnabled = Key<Bool>("actionButtonSettingsEnabled", default: true)
|
|
||||||
static let actionButtonHideEnabled = Key<Bool>("actionButtonHideEnabled", default: false)
|
|
||||||
static let actionButtonCloseEnabled = Key<Bool>("actionButtonCloseEnabled", default: true)
|
|
||||||
static let actionButtonFullScreenEnabled = Key<Bool>("actionButtonFullScreenEnabled", default: false)
|
|
||||||
static let actionButtonPipEnabled = Key<Bool>("actionButtonPipEnabled", default: false)
|
|
||||||
static let actionButtonLockOrientationEnabled = Key<Bool>("actionButtonLockOrientationEnabled", default: false)
|
|
||||||
static let actionButtonRestartEnabled = Key<Bool>("actionButtonRestartEnabled", default: false)
|
|
||||||
static let actionButtonAdvanceToNextItemEnabled = Key<Bool>("actionButtonAdvanceToNextItemEnabled", default: false)
|
|
||||||
static let actionButtonMusicModeEnabled = Key<Bool>("actionButtonMusicModeEnabled", default: true)
|
|
||||||
|
|
||||||
// MARK: GROUP - Quality
|
|
||||||
|
|
||||||
static let hd2160pMPVProfile = QualityProfile(id: "hd2160pMPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases)
|
static let hd2160pMPVProfile = QualityProfile(id: "hd2160pMPVProfile", backend: .mpv, resolution: .hd2160p60, formats: QualityProfile.Format.allCases)
|
||||||
static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases)
|
static let hd1080pMPVProfile = QualityProfile(id: "hd1080pMPVProfile", backend: .mpv, resolution: .hd1080p60, formats: QualityProfile.Format.allCases)
|
||||||
@@ -212,67 +109,150 @@ extension Defaults.Keys {
|
|||||||
static let chargingCellularProfileDefault = hd1080pMPVProfile.id
|
static let chargingCellularProfileDefault = hd1080pMPVProfile.id
|
||||||
static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id
|
static let chargingNonCellularProfileDefault = hd1080pMPVProfile.id
|
||||||
#endif
|
#endif
|
||||||
|
static let playerRate = Key<Double>("playerRate", default: 1.0)
|
||||||
|
static let qualityProfiles = Key<[QualityProfile]>("qualityProfiles", default: qualityProfilesDefault)
|
||||||
static let batteryCellularProfile = Key<QualityProfile.ID>("batteryCellularProfile", default: batteryCellularProfileDefault)
|
static let batteryCellularProfile = Key<QualityProfile.ID>("batteryCellularProfile", default: batteryCellularProfileDefault)
|
||||||
static let batteryNonCellularProfile = Key<QualityProfile.ID>("batteryNonCellularProfile", default: batteryNonCellularProfileDefault)
|
static let batteryNonCellularProfile = Key<QualityProfile.ID>("batteryNonCellularProfile", default: batteryNonCellularProfileDefault)
|
||||||
static let chargingCellularProfile = Key<QualityProfile.ID>("chargingCellularProfile", default: chargingCellularProfileDefault)
|
static let chargingCellularProfile = Key<QualityProfile.ID>("chargingCellularProfile", default: chargingCellularProfileDefault)
|
||||||
static let chargingNonCellularProfile = Key<QualityProfile.ID>("chargingNonCellularProfile", default: chargingNonCellularProfileDefault)
|
static let chargingNonCellularProfile = Key<QualityProfile.ID>("chargingNonCellularProfile", default: chargingNonCellularProfileDefault)
|
||||||
static let forceAVPlayerForLiveStreams = Key<Bool>("forceAVPlayerForLiveStreams", default: true)
|
static let forceAVPlayerForLiveStreams = Key<Bool>("forceAVPlayerForLiveStreams", default: true)
|
||||||
|
static let playerSidebar = Key<PlayerSidebarSetting>("playerSidebar", default: .defaultValue)
|
||||||
|
static let playerInstanceID = Key<Instance.ID?>("playerInstance")
|
||||||
|
|
||||||
static let qualityProfiles = Key<[QualityProfile]>("qualityProfiles", default: qualityProfilesDefault)
|
#if os(iOS)
|
||||||
|
static let playerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
|
||||||
|
static let fullScreenPlayerControlsLayoutDefault = UIDevice.current.userInterfaceIdiom == .pad ? PlayerControlsLayout.medium : .small
|
||||||
|
#elseif os(tvOS)
|
||||||
|
static let playerControlsLayoutDefault = PlayerControlsLayout.tvRegular
|
||||||
|
static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.tvRegular
|
||||||
|
#else
|
||||||
|
static let playerControlsLayoutDefault = PlayerControlsLayout.medium
|
||||||
|
static let fullScreenPlayerControlsLayoutDefault = PlayerControlsLayout.medium
|
||||||
|
#endif
|
||||||
|
|
||||||
// MARK: GROUP - History
|
static let playerControlsLayout = Key<PlayerControlsLayout>("playerControlsLayout", default: playerControlsLayoutDefault)
|
||||||
|
static let fullScreenPlayerControlsLayout = Key<PlayerControlsLayout>("fullScreenPlayerControlsLayout", default: fullScreenPlayerControlsLayoutDefault)
|
||||||
|
static let avPlayerUsesSystemControls = Key<Bool>("avPlayerUsesSystemControls", default: true)
|
||||||
|
static let horizontalPlayerGestureEnabled = Key<Bool>("horizontalPlayerGestureEnabled", default: true)
|
||||||
|
static let seekGestureSpeed = Key<Double>("seekGestureSpeed", default: 0.5)
|
||||||
|
static let seekGestureSensitivity = Key<Double>("seekGestureSensitivity", default: 30.0)
|
||||||
|
static let showKeywords = Key<Bool>("showKeywords", default: false)
|
||||||
|
#if !os(tvOS)
|
||||||
|
static let showScrollToTopInComments = Key<Bool>("showScrollToTopInComments", default: true)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
static let expandVideoDescriptionDefault = Constants.isIPad
|
||||||
|
#else
|
||||||
|
static let expandVideoDescriptionDefault = true
|
||||||
|
#endif
|
||||||
|
static let expandVideoDescription = Key<Bool>("expandVideoDescription", default: expandVideoDescriptionDefault)
|
||||||
|
static let collapsedLinesDescription = Key<Int>("collapsedLinesDescription", default: 5)
|
||||||
|
|
||||||
|
static let showChannelAvatarInChannelsLists = Key<Bool>("showChannelAvatarInChannelsLists", default: true)
|
||||||
|
static let showChannelAvatarInVideosListing = Key<Bool>("showChannelAvatarInVideosListing", default: true)
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
static let pauseOnHidingPlayerDefault = true
|
||||||
|
#else
|
||||||
|
static let pauseOnHidingPlayerDefault = false
|
||||||
|
#endif
|
||||||
|
static let pauseOnHidingPlayer = Key<Bool>("pauseOnHidingPlayer", default: pauseOnHidingPlayerDefault)
|
||||||
|
|
||||||
|
#if !os(macOS)
|
||||||
|
static let pauseOnEnteringBackground = Key<Bool>("pauseOnEnteringBackground", default: true)
|
||||||
|
#endif
|
||||||
|
static let closeVideoOnEOF = Key<Bool>("closeVideoOnEOF", default: false)
|
||||||
|
static let closePiPOnNavigation = Key<Bool>("closePiPOnNavigation", default: false)
|
||||||
|
static let closePiPOnOpeningPlayer = Key<Bool>("closePiPOnOpeningPlayer", default: false)
|
||||||
|
#if !os(macOS)
|
||||||
|
static let closePiPAndOpenPlayerOnEnteringForeground = Key<Bool>("closePiPAndOpenPlayerOnEnteringForeground", default: false)
|
||||||
|
#endif
|
||||||
|
static let closePlayerOnOpeningPiP = Key<Bool>("closePlayerOnOpeningPiP", default: false)
|
||||||
|
|
||||||
|
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
||||||
|
|
||||||
|
static let queue = Key<[PlayerQueueItem]>("queue", default: [])
|
||||||
|
static let saveLastPlayed = Key<Bool>("saveLastPlayed", default: false)
|
||||||
|
static let lastPlayed = Key<PlayerQueueItem?>("lastPlayed")
|
||||||
|
static let playbackMode = Key<PlayerModel.PlaybackMode>("playbackMode", default: .queue)
|
||||||
|
|
||||||
static let saveRecents = Key<Bool>("saveRecents", default: true)
|
|
||||||
static let saveHistory = Key<Bool>("saveHistory", default: true)
|
static let saveHistory = Key<Bool>("saveHistory", default: true)
|
||||||
static let showWatchingProgress = Key<Bool>("showWatchingProgress", default: true)
|
static let showWatchingProgress = Key<Bool>("showWatchingProgress", default: true)
|
||||||
static let saveLastPlayed = Key<Bool>("saveLastPlayed", default: false)
|
|
||||||
|
|
||||||
static let watchedVideoPlayNowBehavior = Key<WatchedVideoPlayNowBehavior>("watchedVideoPlayNowBehavior", default: .continue)
|
|
||||||
static let watchedThreshold = Key<Int>("watchedThreshold", default: 90)
|
static let watchedThreshold = Key<Int>("watchedThreshold", default: 90)
|
||||||
static let resetWatchedStatusOnPlaying = Key<Bool>("resetWatchedStatusOnPlaying", default: false)
|
|
||||||
|
|
||||||
static let watchedVideoStyle = Key<WatchedVideoStyle>("watchedVideoStyle", default: .badge)
|
static let watchedVideoStyle = Key<WatchedVideoStyle>("watchedVideoStyle", default: .badge)
|
||||||
static let watchedVideoBadgeColor = Key<WatchedVideoBadgeColor>("WatchedVideoBadgeColor", default: .red)
|
static let watchedVideoBadgeColor = Key<WatchedVideoBadgeColor>("WatchedVideoBadgeColor", default: .red)
|
||||||
static let showToggleWatchedStatusButton = Key<Bool>("showToggleWatchedStatusButton", default: false)
|
static let watchedVideoPlayNowBehavior = Key<WatchedVideoPlayNowBehavior>("watchedVideoPlayNowBehavior", default: .continue)
|
||||||
|
static let resetWatchedStatusOnPlaying = Key<Bool>("resetWatchedStatusOnPlaying", default: false)
|
||||||
// MARK: GROUP - SponsorBlock
|
static let saveRecents = Key<Bool>("saveRecents", default: true)
|
||||||
|
|
||||||
static let sponsorBlockInstance = Key<String>("sponsorBlockInstance", default: "https://sponsor.ajay.app")
|
|
||||||
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
|
||||||
|
|
||||||
// MARK: GROUP - Locations
|
|
||||||
|
|
||||||
static let instancesManifest = Key<String>("instancesManifest", default: "")
|
|
||||||
static let countryOfPublicInstances = Key<String?>("countryOfPublicInstances")
|
|
||||||
|
|
||||||
static let instances = Key<[Instance]>("instances", default: [])
|
|
||||||
static let accounts = Key<[Account]>("accounts", default: [])
|
|
||||||
|
|
||||||
// MARK: Group - Advanced
|
|
||||||
|
|
||||||
static let showPlayNowInBackendContextMenu = Key<Bool>("showPlayNowInBackendContextMenu", default: false)
|
|
||||||
|
|
||||||
static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false)
|
|
||||||
static let mpvEnableLogging = Key<Bool>("mpvEnableLogging", default: false)
|
|
||||||
static let mpvCacheSecs = Key<String>("mpvCacheSecs", default: "120")
|
|
||||||
static let mpvCachePauseWait = Key<String>("mpvCachePauseWait", default: "3")
|
|
||||||
static let mpvDeinterlace = Key<Bool>("mpvDeinterlace", default: false)
|
|
||||||
|
|
||||||
static let showCacheStatus = Key<Bool>("showCacheStatus", default: false)
|
|
||||||
static let feedCacheSize = Key<String>("feedCacheSize", default: "50")
|
|
||||||
|
|
||||||
// MARK: GROUP - Other exportable
|
|
||||||
|
|
||||||
static let lastAccountID = Key<Account.ID?>("lastAccountID")
|
|
||||||
static let lastInstanceID = Key<Instance.ID?>("lastInstanceID")
|
|
||||||
|
|
||||||
static let playerRate = Key<Double>("playerRate", default: 1.0)
|
|
||||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
|
||||||
|
|
||||||
static let trendingCategory = Key<TrendingCategory>("trendingCategory", default: .default)
|
static let trendingCategory = Key<TrendingCategory>("trendingCategory", default: .default)
|
||||||
static let trendingCountry = Key<Country>("trendingCountry", default: .us)
|
static let trendingCountry = Key<Country>("trendingCountry", default: .us)
|
||||||
|
|
||||||
|
static let visibleSections = Key<Set<VisibleSection>>("visibleSections", default: [.subscriptions, .trending, .playlists])
|
||||||
|
static let startupSection = Key<StartupSection>("startupSection", default: .home)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
static let honorSystemOrientationLock = Key<Bool>("honorSystemOrientationLock", default: true)
|
||||||
|
static let enterFullscreenInLandscape = Key<Bool>("enterFullscreenInLandscape", default: UIDevice.current.userInterfaceIdiom == .phone)
|
||||||
|
static let rotateToLandscapeOnEnterFullScreen = Key<FullScreenRotationSetting>(
|
||||||
|
"rotateToLandscapeOnEnterFullScreen",
|
||||||
|
default: UIDevice.current.userInterfaceIdiom == .phone ? .landscapeRight : .disabled
|
||||||
|
)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false)
|
||||||
|
static let showPlayNowInBackendContextMenu = Key<Bool>("showPlayNowInBackendContextMenu", default: false)
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
static let playerDetailsPageButtonLabelStyleDefault = ButtonLabelStyle.iconAndText
|
||||||
|
#else
|
||||||
|
static let playerDetailsPageButtonLabelStyleDefault = UIDevice.current.userInterfaceIdiom == .phone ? ButtonLabelStyle.iconOnly : .iconAndText
|
||||||
|
#endif
|
||||||
|
static let playerActionsButtonLabelStyle = Key<ButtonLabelStyle>("playerActionsButtonLabelStyle", default: .iconAndText)
|
||||||
|
|
||||||
|
static let systemControlsCommands = Key<SystemControlsCommands>("systemControlsCommands", default: .restartAndAdvanceToNext)
|
||||||
|
|
||||||
|
static let buttonBackwardSeekDuration = Key<String>("buttonBackwardSeekDuration", default: "10")
|
||||||
|
static let buttonForwardSeekDuration = Key<String>("buttonForwardSeekDuration", default: "10")
|
||||||
|
static let gestureBackwardSeekDuration = Key<String>("gestureBackwardSeekDuration", default: "10")
|
||||||
|
static let gestureForwardSeekDuration = Key<String>("gestureForwardSeekDuration", default: "10")
|
||||||
|
static let systemControlsSeekDuration = Key<String>("systemControlsBackwardSeekDuration", default: "10")
|
||||||
|
static let actionButtonShareEnabled = Key<Bool>("actionButtonShareEnabled", default: true)
|
||||||
|
static let actionButtonAddToPlaylistEnabled = Key<Bool>("actionButtonAddToPlaylistEnabled", default: true)
|
||||||
|
static let actionButtonSubscribeEnabled = Key<Bool>("actionButtonSubscribeEnabled", default: false)
|
||||||
|
static let actionButtonSettingsEnabled = Key<Bool>("actionButtonSettingsEnabled", default: true)
|
||||||
|
static let actionButtonHideEnabled = Key<Bool>("actionButtonHideEnabled", default: false)
|
||||||
|
static let actionButtonCloseEnabled = Key<Bool>("actionButtonCloseEnabled", default: true)
|
||||||
|
static let actionButtonFullScreenEnabled = Key<Bool>("actionButtonFullScreenEnabled", default: false)
|
||||||
|
static let actionButtonPipEnabled = Key<Bool>("actionButtonPipEnabled", default: false)
|
||||||
|
static let actionButtonLockOrientationEnabled = Key<Bool>("actionButtonLockOrientationEnabled", default: false)
|
||||||
|
static let actionButtonRestartEnabled = Key<Bool>("actionButtonRestartEnabled", default: false)
|
||||||
|
static let actionButtonAdvanceToNextItemEnabled = Key<Bool>("actionButtonAdvanceToNextItemEnabled", default: false)
|
||||||
|
static let actionButtonMusicModeEnabled = Key<Bool>("actionButtonMusicModeEnabled", default: true)
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
|
static let playerControlsLockOrientationEnabled = Key<Bool>("playerControlsLockOrientationEnabled", default: true)
|
||||||
|
#endif
|
||||||
|
#if os(tvOS)
|
||||||
|
static let playerControlsSettingsEnabledDefault = true
|
||||||
|
#else
|
||||||
|
static let playerControlsSettingsEnabledDefault = false
|
||||||
|
#endif
|
||||||
|
static let playerControlsSettingsEnabled = Key<Bool>("playerControlsSettingsEnabled", default: playerControlsSettingsEnabledDefault)
|
||||||
|
static let playerControlsCloseEnabled = Key<Bool>("playerControlsCloseEnabled", default: true)
|
||||||
|
static let playerControlsRestartEnabled = Key<Bool>("playerControlsRestartEnabled", default: false)
|
||||||
|
static let playerControlsAdvanceToNextEnabled = Key<Bool>("playerControlsAdvanceToNextEnabled", default: false)
|
||||||
|
static let playerControlsPlaybackModeEnabled = Key<Bool>("playerControlsPlaybackModeEnabled", default: false)
|
||||||
|
static let playerControlsMusicModeEnabled = Key<Bool>("playerControlsMusicModeEnabled", default: false)
|
||||||
|
|
||||||
|
static let mpvCacheSecs = Key<String>("mpvCacheSecs", default: "120")
|
||||||
|
static let mpvCachePauseWait = Key<String>("mpvCachePauseWait", default: "3")
|
||||||
|
static let mpvEnableLogging = Key<Bool>("mpvEnableLogging", default: false)
|
||||||
|
|
||||||
|
static let showCacheStatus = Key<Bool>("showCacheStatus", default: false)
|
||||||
|
static let feedCacheSize = Key<String>("feedCacheSize", default: "50")
|
||||||
|
|
||||||
static let subscriptionsViewPage = Key<SubscriptionsView.Page>("subscriptionsViewPage", default: .feed)
|
static let subscriptionsViewPage = Key<SubscriptionsView.Page>("subscriptionsViewPage", default: .feed)
|
||||||
|
|
||||||
static let subscriptionsListingStyle = Key<ListingStyle>("subscriptionsListingStyle", default: .cells)
|
static let subscriptionsListingStyle = Key<ListingStyle>("subscriptionsListingStyle", default: .cells)
|
||||||
@@ -283,22 +263,11 @@ extension Defaults.Keys {
|
|||||||
static let searchListingStyle = Key<ListingStyle>("searchListingStyle", default: .cells)
|
static let searchListingStyle = Key<ListingStyle>("searchListingStyle", default: .cells)
|
||||||
static let hideShorts = Key<Bool>("hideShorts", default: false)
|
static let hideShorts = Key<Bool>("hideShorts", default: false)
|
||||||
static let hideWatched = Key<Bool>("hideWatched", default: false)
|
static let hideWatched = Key<Bool>("hideWatched", default: false)
|
||||||
|
static let showInspector = Key<ShowInspectorSetting>("showInspector", default: .onlyLocal)
|
||||||
// MARK: GROUP - Not exportable
|
static let showChapters = Key<Bool>("showChapters", default: true)
|
||||||
|
static let expandChapters = Key<Bool>("expandChapters", default: true)
|
||||||
static let queue = Key<[PlayerQueueItem]>("queue", default: [])
|
static let showRelated = Key<Bool>("showRelated", default: true)
|
||||||
static let playbackMode = Key<PlayerModel.PlaybackMode>("playbackMode", default: .queue)
|
static let widgetsSettings = Key<[WidgetSettings]>("widgetsSettings", default: [])
|
||||||
static let lastPlayed = Key<PlayerQueueItem?>("lastPlayed")
|
|
||||||
|
|
||||||
static let activeBackend = Key<PlayerBackendType>("activeBackend", default: .mpv)
|
|
||||||
static let captionsLanguageCode = Key<String?>("captionsLanguageCode")
|
|
||||||
|
|
||||||
static let lastUsedPlaylistID = Key<Playlist.ID?>("lastPlaylistID")
|
|
||||||
static let lastAccountIsPublic = Key<Bool>("lastAccountIsPublic", default: false)
|
|
||||||
|
|
||||||
// MARK: LEGACY
|
|
||||||
|
|
||||||
static let homeHistoryItems = Key<Int>("homeHistoryItems", default: 10)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ResolutionSetting: String, CaseIterable, Defaults.Serializable {
|
enum ResolutionSetting: String, CaseIterable, Defaults.Serializable {
|
||||||
@@ -432,15 +401,6 @@ enum ButtonLabelStyle: String, CaseIterable, Defaults.Serializable {
|
|||||||
var text: Bool {
|
var text: Bool {
|
||||||
self == .iconAndText
|
self == .iconAndText
|
||||||
}
|
}
|
||||||
|
|
||||||
var description: String {
|
|
||||||
switch self {
|
|
||||||
case .iconOnly:
|
|
||||||
return "Icon only"
|
|
||||||
case .iconAndText:
|
|
||||||
return "Icon and text"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ThumbnailsQuality: String, CaseIterable, Defaults.Serializable {
|
enum ThumbnailsQuality: String, CaseIterable, Defaults.Serializable {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum Delay {
|
struct Delay {
|
||||||
@discardableResult static func by(_ interval: TimeInterval, block: @escaping () -> Void) -> Timer {
|
@discardableResult static func by(_ interval: TimeInterval, block: @escaping () -> Void) -> Timer {
|
||||||
Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { _ in block() }
|
Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { _ in block() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ struct HomeView: View {
|
|||||||
@Default(.favorites) private var favorites
|
@Default(.favorites) private var favorites
|
||||||
@Default(.widgetsSettings) private var widgetsSettings
|
@Default(.widgetsSettings) private var widgetsSettings
|
||||||
#endif
|
#endif
|
||||||
|
@Default(.homeHistoryItems) private var homeHistoryItems
|
||||||
@Default(.showFavoritesInHome) private var showFavoritesInHome
|
@Default(.showFavoritesInHome) private var showFavoritesInHome
|
||||||
@Default(.showOpenActionsInHome) private var showOpenActionsInHome
|
@Default(.showOpenActionsInHome) private var showOpenActionsInHome
|
||||||
@Default(.showQueueInHome) private var showQueueInHome
|
@Default(.showQueueInHome) private var showQueueInHome
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ struct ContentView: View {
|
|||||||
SettingsView()
|
SettingsView()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.modifier(ImportSettingsSheetViewModifier(isPresented: $navigation.presentingSettingsImportSheet, settingsFile: $navigation.settingsImportURL))
|
|
||||||
.background(
|
.background(
|
||||||
EmptyView().sheet(isPresented: $navigation.presentingAccounts) {
|
EmptyView().sheet(isPresented: $navigation.presentingAccounts) {
|
||||||
AccountsView()
|
AccountsView()
|
||||||
|
|||||||
@@ -14,11 +14,6 @@ struct OpenURLHandler {
|
|||||||
var navigationStyle: NavigationStyle
|
var navigationStyle: NavigationStyle
|
||||||
|
|
||||||
func handle(_ url: URL) {
|
func handle(_ url: URL) {
|
||||||
if url.isFileURL, url.standardizedFileURL.absoluteString.hasSuffix(".\(ImportExportSettingsModel.settingsExtension)") {
|
|
||||||
navigation.presentSettingsImportSheet(url)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if Self.firstHandle {
|
if Self.firstHandle {
|
||||||
Self.firstHandle = false
|
Self.firstHandle = false
|
||||||
|
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ struct CommentView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var authorAvatar: some View {
|
private var authorAvatar: some View {
|
||||||
WebImage(url: URL(string: comment.authorAvatarURL), options: [.lowPriority])
|
WebImage(url: URL(string: comment.authorAvatarURL)!, options: [.lowPriority])
|
||||||
.resizable()
|
.resizable()
|
||||||
.placeholder {
|
.placeholder {
|
||||||
Rectangle().fill(Color("PlaceholderColor"))
|
Rectangle().fill(Color("PlaceholderColor"))
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ struct AccountForm: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let account = AccountsModel.add(instance: instance, id: nil, name: name, username: username, password: password)
|
let account = AccountsModel.add(instance: instance, name: name, username: username, password: password)
|
||||||
selectedAccount?.wrappedValue = account
|
selectedAccount?.wrappedValue = account
|
||||||
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
presentationMode.wrappedValue.dismiss()
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ struct AdvancedSettings: View {
|
|||||||
@Default(.showMPVPlaybackStats) private var showMPVPlaybackStats
|
@Default(.showMPVPlaybackStats) private var showMPVPlaybackStats
|
||||||
@Default(.mpvCacheSecs) private var mpvCacheSecs
|
@Default(.mpvCacheSecs) private var mpvCacheSecs
|
||||||
@Default(.mpvCachePauseWait) private var mpvCachePauseWait
|
@Default(.mpvCachePauseWait) private var mpvCachePauseWait
|
||||||
@Default(.mpvDeinterlace) private var mpvDeinterlace
|
|
||||||
@Default(.mpvEnableLogging) private var mpvEnableLogging
|
@Default(.mpvEnableLogging) private var mpvEnableLogging
|
||||||
@Default(.showCacheStatus) private var showCacheStatus
|
@Default(.showCacheStatus) private var showCacheStatus
|
||||||
@Default(.feedCacheSize) private var feedCacheSize
|
@Default(.feedCacheSize) private var feedCacheSize
|
||||||
@@ -88,8 +87,6 @@ struct AdvancedSettings: View {
|
|||||||
}
|
}
|
||||||
.multilineTextAlignment(.trailing)
|
.multilineTextAlignment(.trailing)
|
||||||
|
|
||||||
Toggle("deinterlace", isOn: $mpvDeinterlace)
|
|
||||||
|
|
||||||
if mpvEnableLogging {
|
if mpvEnableLogging {
|
||||||
logButton
|
logButton
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ExportSettings: View {
|
|
||||||
@ObservedObject private var model = ImportExportSettingsModel.shared
|
|
||||||
@State private var presentingShareSheet = false
|
|
||||||
@StateObject private var settings = SettingsModel.shared
|
|
||||||
|
|
||||||
private var filesToShare = [ImportExportSettingsModel.exportFile]
|
|
||||||
@ObservedObject private var navigation = NavigationModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
#if os(macOS)
|
|
||||||
VStack {
|
|
||||||
list
|
|
||||||
|
|
||||||
importExportButtons
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
list
|
|
||||||
#if os(iOS)
|
|
||||||
.listStyle(.insetGrouped)
|
|
||||||
.sheet(
|
|
||||||
isPresented: $presentingShareSheet,
|
|
||||||
onDismiss: { self.model.isExportInProgress = false }
|
|
||||||
) {
|
|
||||||
ShareSheet(activityItems: filesToShare)
|
|
||||||
.id("settings-share-\(filesToShare.count)")
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.navigationTitle("Export Settings")
|
|
||||||
}
|
|
||||||
|
|
||||||
var list: some View {
|
|
||||||
List {
|
|
||||||
exportView
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
model.reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var importExportButtons: some View {
|
|
||||||
HStack {
|
|
||||||
importButton
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
exportButton
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder var importButton: some View {
|
|
||||||
#if os(macOS)
|
|
||||||
Button {
|
|
||||||
navigation.presentingSettingsFileImporter = true
|
|
||||||
} label: {
|
|
||||||
Label("Import", systemImage: "square.and.arrow.down")
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ExportGroupRow: View {
|
|
||||||
let group: ImportExportSettingsModel.ExportGroup
|
|
||||||
|
|
||||||
@ObservedObject private var model = ImportExportSettingsModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: { model.toggleExportGroupSelection(group) }) {
|
|
||||||
HStack {
|
|
||||||
Text(group.label)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.opacity(isGroupInSelectedGroups ? 1 : 0)
|
|
||||||
}
|
|
||||||
.animation(nil, value: isGroupInSelectedGroups)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var isGroupInSelectedGroups: Bool {
|
|
||||||
model.selectedExportGroups.contains(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var exportView: some View {
|
|
||||||
Group {
|
|
||||||
Section(header: Text("Settings")) {
|
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.settingsGroups) { group in
|
|
||||||
ExportGroupRow(group: group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Text("Locations")) {
|
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.locationsGroups) { group in
|
|
||||||
ExportGroupRow(group: group)
|
|
||||||
.disabled(!model.isGroupEnabled(group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Text("Other"), footer: otherGroupsFooter) {
|
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.otherGroups) { group in
|
|
||||||
ExportGroupRow(group: group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !os(macOS)
|
|
||||||
Section {
|
|
||||||
exportButton
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
.disabled(model.isExportInProgress)
|
|
||||||
}
|
|
||||||
|
|
||||||
var exportButton: some View {
|
|
||||||
Button(action: exportSettings) {
|
|
||||||
Label(model.isExportInProgress ? "Export in progress..." : "Export...", systemImage: model.isExportInProgress ? "fireworks" : "square.and.arrow.up")
|
|
||||||
.animation(nil, value: model.isExportInProgress)
|
|
||||||
#if !os(macOS)
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.disabled(!model.isExportAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder var otherGroupsFooter: some View {
|
|
||||||
Text("Other data include last used playback preferences and listing options")
|
|
||||||
}
|
|
||||||
|
|
||||||
func exportSettings() {
|
|
||||||
let export = {
|
|
||||||
model.isExportInProgress = true
|
|
||||||
Delay.by(0.3) {
|
|
||||||
model.exportAction()
|
|
||||||
#if !os(macOS)
|
|
||||||
self.presentingShareSheet = true
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.isGroupSelected(.accountsUnencryptedPasswords) {
|
|
||||||
settings.presentAlert(Alert(
|
|
||||||
title: Text("Are you sure you want to export unencrypted passwords?"),
|
|
||||||
message: Text("Do not share this file with anyone or you can lose access to your accounts. If you don't select to export passwords you will be asked to provide them during import"),
|
|
||||||
primaryButton: .destructive(Text("Export"), action: export),
|
|
||||||
secondaryButton: .cancel()
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
export()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ExportSettings_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
NavigationView {
|
|
||||||
ExportSettings()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ImportSettingsAccountRow: View {
|
|
||||||
var account: Account
|
|
||||||
var fileModel: ImportSettingsFileModel
|
|
||||||
|
|
||||||
@State private var password = ""
|
|
||||||
|
|
||||||
@State private var isValid = false
|
|
||||||
@State private var isValidated = false
|
|
||||||
@State private var isValidating = false
|
|
||||||
@State private var validationError: String?
|
|
||||||
@State private var validationDebounce = Debounce()
|
|
||||||
|
|
||||||
@ObservedObject private var model = ImportSettingsSheetViewModel.shared
|
|
||||||
|
|
||||||
func afterValidation() {
|
|
||||||
if isValid {
|
|
||||||
model.importableAccounts.insert(account.id)
|
|
||||||
model.selectedAccounts.insert(account.id)
|
|
||||||
model.importableAccountsPasswords[account.id] = password
|
|
||||||
} else {
|
|
||||||
model.selectedAccounts.remove(account.id)
|
|
||||||
model.importableAccounts.remove(account.id)
|
|
||||||
model.importableAccountsPasswords.removeValue(forKey: account.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: { model.toggleAccount(account, accounts: accounts) }) {
|
|
||||||
let accountExists = AccountsModel.shared.find(account.id) != nil
|
|
||||||
|
|
||||||
VStack(alignment: .leading) {
|
|
||||||
HStack {
|
|
||||||
Text(account.username)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.opacity(isChecked ? 1 : 0)
|
|
||||||
}
|
|
||||||
Text(account.instance?.description ?? "")
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
|
|
||||||
Group {
|
|
||||||
if let instanceID = account.instanceID {
|
|
||||||
if accountExists {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.foregroundColor(Color("AppRedColor"))
|
|
||||||
Text("Account already exists")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Group {
|
|
||||||
if InstancesModel.shared.find(instanceID) != nil {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("Custom Location already exists")
|
|
||||||
}
|
|
||||||
} else if model.selectedInstances.contains(instanceID) {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
Text("Custom Location selected for import")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.foregroundColor(.red)
|
|
||||||
Text("Custom Location not selected for import")
|
|
||||||
}
|
|
||||||
.foregroundColor(Color("AppRedColor"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minHeight: 20)
|
|
||||||
|
|
||||||
if account.password.isNil || account.password!.isEmpty {
|
|
||||||
Group {
|
|
||||||
if password.isEmpty {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "key")
|
|
||||||
Text("Password required to import")
|
|
||||||
}
|
|
||||||
.foregroundColor(Color("AppRedColor"))
|
|
||||||
} else {
|
|
||||||
AccountValidationStatus(
|
|
||||||
app: .constant(instance.app),
|
|
||||||
isValid: $isValid,
|
|
||||||
isValidated: $isValidated,
|
|
||||||
isValidating: $isValidating,
|
|
||||||
error: $validationError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minHeight: 20)
|
|
||||||
} else {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "checkmark.circle.fill")
|
|
||||||
.foregroundColor(.green)
|
|
||||||
|
|
||||||
Text("Password saved in import file")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.font(.caption)
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
|
|
||||||
if !accountExists && (account.password.isNil || account.password!.isEmpty) {
|
|
||||||
SecureField("Password", text: $password)
|
|
||||||
.onChange(of: password) { _ in validate() }
|
|
||||||
#if !os(tvOS)
|
|
||||||
.textFieldStyle(RoundedBorderTextFieldStyle())
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onChange(of: isValid) { _ in afterValidation() }
|
|
||||||
.animation(nil, value: isChecked)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isChecked: Bool {
|
|
||||||
model.isSelectedForImport(account)
|
|
||||||
}
|
|
||||||
|
|
||||||
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
|
||||||
fileModel.locationsSettingsGroupImporter
|
|
||||||
}
|
|
||||||
|
|
||||||
var accounts: [Account] {
|
|
||||||
fileModel.locationsSettingsGroupImporter?.accounts ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
private var instance: Instance! {
|
|
||||||
(fileModel.locationsSettingsGroupImporter?.instances ?? []).first { $0.id == account.instanceID }
|
|
||||||
}
|
|
||||||
|
|
||||||
private var validator: AccountValidator {
|
|
||||||
AccountValidator(
|
|
||||||
app: .constant(instance.app),
|
|
||||||
url: instance.apiURLString,
|
|
||||||
account: Account(instanceID: instance.id, urlString: instance.apiURLString, username: account.username, password: password),
|
|
||||||
id: .constant(account.username),
|
|
||||||
isValid: $isValid,
|
|
||||||
isValidated: $isValidated,
|
|
||||||
isValidating: $isValidating,
|
|
||||||
error: $validationError
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func validate() {
|
|
||||||
isValid = false
|
|
||||||
validationDebounce.invalidate()
|
|
||||||
|
|
||||||
guard !account.username.isEmpty, !password.isEmpty else {
|
|
||||||
validator.reset()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isValidating = true
|
|
||||||
|
|
||||||
validationDebounce.debouncing(1) {
|
|
||||||
validator.validateAccount()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportSettingsAccountRow_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
let fileModel = ImportSettingsFileModel(url: URL(string: "https://gist.githubusercontent.com/arekf/578668969c9fdef1b3828bea864c3956/raw/f794a95a20261bcb1145e656c8dda00bea339e2a/yattee-recents.yatteesettings")!)
|
|
||||||
|
|
||||||
return List {
|
|
||||||
ImportSettingsAccountRow(
|
|
||||||
account: .init(name: "arekf", urlString: "https://instance.com", username: "arekf"),
|
|
||||||
fileModel: fileModel
|
|
||||||
)
|
|
||||||
ImportSettingsAccountRow(
|
|
||||||
account: .init(name: "arekf", urlString: "https://instance.com", username: "arekf", password: "a"),
|
|
||||||
fileModel: fileModel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ImportSettingsFileImporterViewModifier: ViewModifier {
|
|
||||||
@Binding var isPresented: Bool
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
content
|
|
||||||
.fileImporter(isPresented: $isPresented, allowedContentTypes: [.json]) { result in
|
|
||||||
do {
|
|
||||||
let selectedFile = try result.get()
|
|
||||||
var urlToOpen: URL?
|
|
||||||
|
|
||||||
if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(selectedFile) {
|
|
||||||
urlToOpen = bookmarkURL
|
|
||||||
}
|
|
||||||
|
|
||||||
if selectedFile.startAccessingSecurityScopedResource() {
|
|
||||||
URLBookmarkModel.shared.saveBookmark(selectedFile)
|
|
||||||
urlToOpen = selectedFile
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let urlToOpen else { return }
|
|
||||||
NavigationModel.shared.presentSettingsImportSheet(urlToOpen, forceSettings: true)
|
|
||||||
} catch {
|
|
||||||
NavigationModel.shared.presentAlert(title: "Could not open Files")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct ImportSettingsSheetView: View {
|
|
||||||
@Binding var settingsFile: URL?
|
|
||||||
@StateObject private var model = ImportSettingsSheetViewModel.shared
|
|
||||||
@StateObject private var importExportModel = ImportExportSettingsModel.shared
|
|
||||||
|
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
|
||||||
|
|
||||||
@State private var presentingCompletedAlert = false
|
|
||||||
|
|
||||||
private let accountsModel = AccountsModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Group {
|
|
||||||
#if os(macOS)
|
|
||||||
list
|
|
||||||
.frame(width: 700, height: 800)
|
|
||||||
#else
|
|
||||||
NavigationView {
|
|
||||||
list
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
.onAppear {
|
|
||||||
guard let fileModel else { return }
|
|
||||||
model.reset(fileModel.locationsSettingsGroupImporter)
|
|
||||||
importExportModel.reset(fileModel)
|
|
||||||
}
|
|
||||||
.onChange(of: settingsFile) { _ in
|
|
||||||
importExportModel.reset(fileModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var list: some View {
|
|
||||||
List {
|
|
||||||
importGroupView
|
|
||||||
|
|
||||||
importOptions
|
|
||||||
|
|
||||||
metadata
|
|
||||||
}
|
|
||||||
.alert(isPresented: $presentingCompletedAlert) {
|
|
||||||
completedAlert
|
|
||||||
}
|
|
||||||
#if os(iOS)
|
|
||||||
.backport
|
|
||||||
.scrollDismissesKeyboardInteractively()
|
|
||||||
#endif
|
|
||||||
.navigationTitle("Import Settings")
|
|
||||||
.toolbar {
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
|
||||||
Button(action: { presentationMode.wrappedValue.dismiss() }) {
|
|
||||||
Text("Cancel")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ToolbarItem(placement: .confirmationAction) {
|
|
||||||
Button(action: {
|
|
||||||
fileModel?.performImport()
|
|
||||||
presentingCompletedAlert = true
|
|
||||||
ImportExportSettingsModel.shared.reset()
|
|
||||||
}) {
|
|
||||||
Text("Import")
|
|
||||||
}
|
|
||||||
.disabled(!canImport)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var completedAlert: Alert {
|
|
||||||
Alert(
|
|
||||||
title: Text("Import Completed"),
|
|
||||||
dismissButton: .default(Text("Close")) {
|
|
||||||
if accountsModel.isEmpty,
|
|
||||||
let account = InstancesModel.shared.all.first?.anonymousAccount
|
|
||||||
{
|
|
||||||
accountsModel.setCurrent(account)
|
|
||||||
}
|
|
||||||
presentationMode.wrappedValue.dismiss()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
var canImport: Bool {
|
|
||||||
return !model.selectedAccounts.isEmpty || !model.selectedInstances.isEmpty || !importExportModel.selectedExportGroups.isEmpty
|
|
||||||
}
|
|
||||||
|
|
||||||
var fileModel: ImportSettingsFileModel? {
|
|
||||||
guard let settingsFile else { return nil }
|
|
||||||
|
|
||||||
return ImportSettingsFileModel(url: settingsFile)
|
|
||||||
}
|
|
||||||
|
|
||||||
var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? {
|
|
||||||
guard let fileModel else { return nil }
|
|
||||||
|
|
||||||
return fileModel.locationsSettingsGroupImporter
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ExportGroupRow: View {
|
|
||||||
let group: ImportExportSettingsModel.ExportGroup
|
|
||||||
|
|
||||||
@ObservedObject private var model = ImportExportSettingsModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: { model.toggleExportGroupSelection(group) }) {
|
|
||||||
HStack {
|
|
||||||
Text(group.label)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.opacity(isChecked ? 1 : 0)
|
|
||||||
}
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.animation(nil, value: isChecked)
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isChecked: Bool {
|
|
||||||
model.selectedExportGroups.contains(group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var importGroupView: some View {
|
|
||||||
Group {
|
|
||||||
Section(header: Text("Settings")) {
|
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.settingsGroups) { group in
|
|
||||||
ExportGroupRow(group: group)
|
|
||||||
.disabled(!fileModel!.isGroupIncludedInFile(group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Section(header: Text("Other")) {
|
|
||||||
ForEach(ImportExportSettingsModel.ExportGroup.otherGroups) { group in
|
|
||||||
ExportGroupRow(group: group)
|
|
||||||
.disabled(!fileModel!.isGroupIncludedInFile(group))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder var metadata: some View {
|
|
||||||
if let fileModel {
|
|
||||||
Section(header: Text("File information")) {
|
|
||||||
MetadataRow(name: Text("Name"), value: Text(fileModel.filename))
|
|
||||||
|
|
||||||
if let date = fileModel.metadataDate {
|
|
||||||
MetadataRow(name: Text("Date"), value: Text(date))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let build = fileModel.metadataBuild {
|
|
||||||
MetadataRow(name: Text("Build"), value: Text(build))
|
|
||||||
}
|
|
||||||
|
|
||||||
if let platform = fileModel.metadataPlatform {
|
|
||||||
MetadataRow(name: Text("Platform"), value: Text(platform))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MetadataRow: View {
|
|
||||||
let name: Text
|
|
||||||
let value: Text
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
name
|
|
||||||
.layoutPriority(2)
|
|
||||||
|
|
||||||
Spacer()
|
|
||||||
|
|
||||||
value
|
|
||||||
.layoutPriority(1)
|
|
||||||
.lineLimit(2)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var instances: [Instance] {
|
|
||||||
locationsSettingsGroupImporter?.instances ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
var accounts: [Account] {
|
|
||||||
locationsSettingsGroupImporter?.accounts ?? []
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportInstanceRow: View {
|
|
||||||
var instance: Instance
|
|
||||||
var accounts: [Account]
|
|
||||||
|
|
||||||
@ObservedObject private var model = ImportSettingsSheetViewModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
Button(action: { model.toggleInstance(instance, accounts: accounts) }) {
|
|
||||||
VStack {
|
|
||||||
Group {
|
|
||||||
HStack {
|
|
||||||
Text(instance.description)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "checkmark")
|
|
||||||
.opacity(isChecked ? 1 : 0)
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
if model.isInstanceAlreadyAdded(instance) {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "xmark.circle.fill")
|
|
||||||
.foregroundColor(.red)
|
|
||||||
Text("Custom Location already exists")
|
|
||||||
}
|
|
||||||
.font(.caption)
|
|
||||||
.padding(.vertical, 2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
}
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.foregroundColor(.primary)
|
|
||||||
.transaction { t in t.animation = nil }
|
|
||||||
}
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isChecked: Bool {
|
|
||||||
model.isImportable(instance) && model.selectedInstances.contains(instance.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder var importOptions: some View {
|
|
||||||
if let fileModel {
|
|
||||||
if fileModel.isPublicInstancesSettingsGroupInFile || !instances.isEmpty {
|
|
||||||
Section(header: Text("Locations")) {
|
|
||||||
if fileModel.isPublicInstancesSettingsGroupInFile {
|
|
||||||
ExportGroupRow(group: .locationsSettings)
|
|
||||||
}
|
|
||||||
|
|
||||||
ForEach(instances) { instance in
|
|
||||||
ImportInstanceRow(instance: instance, accounts: accounts)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !accounts.isEmpty {
|
|
||||||
Section(header: Text("Accounts")) {
|
|
||||||
ForEach(accounts) { account in
|
|
||||||
ImportSettingsAccountRow(account: account, fileModel: fileModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportSettingsSheetView_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
ImportSettingsSheetView(settingsFile: .constant(URL(string: "https://gist.githubusercontent.com/arekf/578668969c9fdef1b3828bea864c3956/raw/f794a95a20261bcb1145e656c8dda00bea339e2a/yattee-recents.yatteesettings")!))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
|
|
||||||
final class ImportSettingsSheetViewModel: ObservableObject {
|
|
||||||
static let shared = ImportSettingsSheetViewModel()
|
|
||||||
|
|
||||||
@Published var selectedInstances = Set<Instance.ID>()
|
|
||||||
@Published var selectedAccounts = Set<Account.ID>()
|
|
||||||
|
|
||||||
@Published var importableAccounts = Set<Account.ID>()
|
|
||||||
@Published var importableAccountsPasswords = [Account.ID: String]()
|
|
||||||
|
|
||||||
func toggleInstance(_ instance: Instance, accounts: [Account]) {
|
|
||||||
if selectedInstances.contains(instance.id) {
|
|
||||||
selectedInstances.remove(instance.id)
|
|
||||||
} else {
|
|
||||||
guard isImportable(instance) else { return }
|
|
||||||
selectedInstances.insert(instance.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
removeNonImportableFromSelectedAccounts(accounts: accounts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func toggleAccount(_ account: Account, accounts: [Account]) {
|
|
||||||
if selectedAccounts.contains(account.id) {
|
|
||||||
selectedAccounts.remove(account.id)
|
|
||||||
} else {
|
|
||||||
guard isImportable(account.id, accounts: accounts) else { return }
|
|
||||||
selectedAccounts.insert(account.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSelectedForImport(_ account: Account) -> Bool {
|
|
||||||
importableAccounts.contains(account.id) && selectedAccounts.contains(account.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isImportable(_ accountID: Account.ID, accounts: [Account]) -> Bool {
|
|
||||||
guard let account = accounts.first(where: { $0.id == accountID }),
|
|
||||||
let instanceID = account.instanceID,
|
|
||||||
AccountsModel.shared.find(accountID) == nil
|
|
||||||
else { return false }
|
|
||||||
|
|
||||||
return ((account.password != nil && !account.password!.isEmpty) ||
|
|
||||||
importableAccounts.contains(account.id)) && (
|
|
||||||
(InstancesModel.shared.find(instanceID) != nil) ||
|
|
||||||
selectedInstances.contains(instanceID)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isImportable(_ instance: Instance) -> Bool {
|
|
||||||
!isInstanceAlreadyAdded(instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isInstanceAlreadyAdded(_ instance: Instance) -> Bool {
|
|
||||||
InstancesModel.shared.find(instance.id) != nil || InstancesModel.shared.findByURLString(instance.apiURLString) != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func removeNonImportableFromSelectedAccounts(accounts: [Account]) {
|
|
||||||
selectedAccounts = Set(selectedAccounts.filter { isImportable($0, accounts: accounts) })
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset() {
|
|
||||||
selectedAccounts = []
|
|
||||||
selectedInstances = []
|
|
||||||
importableAccounts = []
|
|
||||||
}
|
|
||||||
|
|
||||||
func reset(_ importer: LocationsSettingsGroupImporter? = nil) {
|
|
||||||
reset()
|
|
||||||
|
|
||||||
guard let importer else { return }
|
|
||||||
|
|
||||||
selectedInstances = Set(importer.instances.filter { isImportable($0) }.map(\.id))
|
|
||||||
importableAccounts = Set(importer.accounts.filter { isImportable($0.id, accounts: importer.accounts) }.map(\.id))
|
|
||||||
selectedAccounts = importableAccounts
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import Foundation
|
|
||||||
import SwiftUI
|
|
||||||
import SwiftyJSON
|
|
||||||
|
|
||||||
struct ImportSettingsSheetViewModifier: ViewModifier {
|
|
||||||
@Binding var isPresented: Bool
|
|
||||||
@Binding var settingsFile: URL?
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
content
|
|
||||||
.sheet(isPresented: $isPresented) {
|
|
||||||
ImportSettingsSheetView(settingsFile: $settingsFile)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ImportSettingsSheetViewModifier_Previews: PreviewProvider {
|
|
||||||
static var previews: some View {
|
|
||||||
Text("")
|
|
||||||
.modifier(
|
|
||||||
ImportSettingsSheetViewModifier(
|
|
||||||
isPresented: .constant(true),
|
|
||||||
settingsFile: .constant(URL(string: "https://gist.githubusercontent.com/arekf/87b4d6702755b01139431dcb809f9fdc/raw/7bb5cdba3ffc0c479f5260430ddc43c4a79a7a72/yattee-177-iPhone.yatteesettings")!)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ struct PlayerControlsSettings: View {
|
|||||||
@Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration
|
@Default(.gestureBackwardSeekDuration) private var gestureBackwardSeekDuration
|
||||||
@Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration
|
@Default(.gestureForwardSeekDuration) private var gestureForwardSeekDuration
|
||||||
@Default(.systemControlsSeekDuration) private var systemControlsSeekDuration
|
@Default(.systemControlsSeekDuration) private var systemControlsSeekDuration
|
||||||
@Default(.playerActionsButtonLabelStyle) private var playerActionsButtonLabelStyle
|
|
||||||
@Default(.actionButtonShareEnabled) private var actionButtonShareEnabled
|
@Default(.actionButtonShareEnabled) private var actionButtonShareEnabled
|
||||||
@Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled
|
@Default(.actionButtonSubscribeEnabled) private var actionButtonSubscribeEnabled
|
||||||
@Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled
|
@Default(.actionButtonCloseEnabled) private var actionButtonCloseEnabled
|
||||||
@@ -118,15 +117,6 @@ struct PlayerControlsSettings: View {
|
|||||||
Section(header: SettingsHeader(text: "Actions Buttons".localized())) {
|
Section(header: SettingsHeader(text: "Actions Buttons".localized())) {
|
||||||
actionButtonToggles
|
actionButtonToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
|
||||||
Picker("Action button labels", selection: $playerActionsButtonLabelStyle) {
|
|
||||||
ForEach(ButtonLabelStyle.allCases, id: \.rawValue) { style in
|
|
||||||
Text(style.description).tag(style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.modifier(SettingsPickerModifier())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private var systemControlsCommandsPicker: some View {
|
private var systemControlsCommandsPicker: some View {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
private enum Tabs: Hashable {
|
private enum Tabs: Hashable {
|
||||||
case browsing, player, controls, quality, history, sponsorBlock, locations, advanced, importExport, help
|
case browsing, player, controls, quality, history, sponsorBlock, locations, advanced, help
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var selection: Tabs = .browsing
|
@State private var selection: Tabs = .browsing
|
||||||
@@ -24,22 +24,13 @@ struct SettingsView: View {
|
|||||||
|
|
||||||
@Default(.instances) private var instances
|
@Default(.instances) private var instances
|
||||||
|
|
||||||
@State private var filesToShare = []
|
|
||||||
|
|
||||||
@ObservedObject private var navigation = NavigationModel.shared
|
|
||||||
@ObservedObject private var settingsModel = SettingsModel.shared
|
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
settings
|
settings
|
||||||
#if !os(tvOS)
|
.alert(isPresented: $model.presentingAlert) { model.alert }
|
||||||
.modifier(ImportSettingsFileImporterViewModifier(isPresented: $navigation.presentingSettingsFileImporter))
|
|
||||||
.modifier(ImportSettingsSheetViewModifier(isPresented: $settingsModel.presentingSettingsImportSheet, settingsFile: $settingsModel.settingsImportURL))
|
|
||||||
#endif
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.backport
|
.backport
|
||||||
.scrollDismissesKeyboardInteractively()
|
.scrollDismissesKeyboardInteractively()
|
||||||
#endif
|
#endif
|
||||||
.alert(isPresented: $model.presentingAlert) { model.alert }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var settings: some View {
|
var settings: some View {
|
||||||
@@ -110,14 +101,6 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
.tag(Tabs.advanced)
|
.tag(Tabs.advanced)
|
||||||
|
|
||||||
Group {
|
|
||||||
ExportSettings()
|
|
||||||
}
|
|
||||||
.tabItem {
|
|
||||||
Label("Export", systemImage: "square.and.arrow.up")
|
|
||||||
}
|
|
||||||
.tag(Tabs.importExport)
|
|
||||||
|
|
||||||
Form {
|
Form {
|
||||||
Help()
|
Help()
|
||||||
}
|
}
|
||||||
@@ -127,7 +110,7 @@ struct SettingsView: View {
|
|||||||
.tag(Tabs.help)
|
.tag(Tabs.help)
|
||||||
}
|
}
|
||||||
.padding(20)
|
.padding(20)
|
||||||
.frame(width: 700, height: windowHeight)
|
.frame(width: 650, height: windowHeight)
|
||||||
#else
|
#else
|
||||||
NavigationView {
|
NavigationView {
|
||||||
settingsList
|
settingsList
|
||||||
@@ -223,8 +206,6 @@ struct SettingsView: View {
|
|||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
importView
|
|
||||||
|
|
||||||
Section(footer: helpFooter) {
|
Section(footer: helpFooter) {
|
||||||
NavigationLink {
|
NavigationLink {
|
||||||
Help()
|
Help()
|
||||||
@@ -279,35 +260,13 @@ struct SettingsView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var importView: some View {
|
|
||||||
Section {
|
|
||||||
Button(action: importSettings) {
|
|
||||||
Label("Import Settings...", systemImage: "square.and.arrow.down")
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
.foregroundColor(.accentColor)
|
|
||||||
.buttonStyle(.plain)
|
|
||||||
|
|
||||||
NavigationLink(destination: LazyView(ExportSettings())) {
|
|
||||||
Label("Export Settings", systemImage: "square.and.arrow.up")
|
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func importSettings() {
|
|
||||||
navigation.presentingSettingsFileImporter = true
|
|
||||||
}
|
|
||||||
|
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
private var windowHeight: Double {
|
private var windowHeight: Double {
|
||||||
switch selection {
|
switch selection {
|
||||||
case .browsing:
|
case .browsing:
|
||||||
return 800
|
return 800
|
||||||
case .player:
|
case .player:
|
||||||
return 550
|
return 500
|
||||||
case .controls:
|
case .controls:
|
||||||
return 920
|
return 920
|
||||||
case .quality:
|
case .quality:
|
||||||
@@ -319,9 +278,7 @@ struct SettingsView: View {
|
|||||||
case .locations:
|
case .locations:
|
||||||
return 600
|
return 600
|
||||||
case .advanced:
|
case .advanced:
|
||||||
return 500
|
return 380
|
||||||
case .importExport:
|
|
||||||
return 580
|
|
||||||
case .help:
|
case .help:
|
||||||
return 650
|
return 650
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ struct ControlsBar: View {
|
|||||||
@State private var shareURL: URL?
|
@State private var shareURL: URL?
|
||||||
@Binding var expansionState: ExpansionState
|
@Binding var expansionState: ExpansionState
|
||||||
|
|
||||||
@State var gestureThrottle = Throttle(interval: 0.25) // swiftlint:disable:this private_swiftui_state
|
@State var gestureThrottle = Throttle(interval: 0.25) // swiftlint:disable:this swiftui_state_private
|
||||||
|
|
||||||
var presentingControls = true
|
var presentingControls = true
|
||||||
var backgroundEnabled = true
|
var backgroundEnabled = true
|
||||||
|
|||||||
@@ -21,14 +21,6 @@ struct YatteeApp: App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var logsDirectory: URL {
|
static var logsDirectory: URL {
|
||||||
temporaryDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
static var settingsExportDirectory: URL {
|
|
||||||
temporaryDirectory
|
|
||||||
}
|
|
||||||
|
|
||||||
private static var temporaryDirectory: URL {
|
|
||||||
URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -307,7 +307,7 @@
|
|||||||
"Hardware decoder" = "وحدة فك ترميز الأجهزة";
|
"Hardware decoder" = "وحدة فك ترميز الأجهزة";
|
||||||
"Rate & Captions" = "معدل سرعة التشغيل و الترجمة";
|
"Rate & Captions" = "معدل سرعة التشغيل و الترجمة";
|
||||||
"Dropped frames" = "الإطارات المتساقطة";
|
"Dropped frames" = "الإطارات المتساقطة";
|
||||||
"Stream FPS" = "عدد الإطارات في الثانية في البث";
|
"Stream FPS" = "عدد الإطارات فى الثانية فى البث";
|
||||||
"Any format" = "أي شكل";
|
"Any format" = "أي شكل";
|
||||||
"%@ formats" = "تنسيقات %@";
|
"%@ formats" = "تنسيقات %@";
|
||||||
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "قائمة تشغيل فارغة\n\nالضغط مع الإستمرار على مقطع الفيديو ثم\n\"إضافة إلى قائمة تشغيل\"";
|
"Playlist is empty\n\nTap and hold on a video and then \n\"Add to Playlist\"" = "قائمة تشغيل فارغة\n\nالضغط مع الإستمرار على مقطع الفيديو ثم\n\"إضافة إلى قائمة تشغيل\"";
|
||||||
@@ -435,7 +435,7 @@
|
|||||||
|
|
||||||
/* Video date filter in search */
|
/* Video date filter in search */
|
||||||
"Today" = "اليوم";
|
"Today" = "اليوم";
|
||||||
"Trending" = "المحتوى الرائج";
|
"Trending" = "محتوى رائج";
|
||||||
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "عادةً ما تكون بالقرب من نهاية الفيديو عند ظهور قائمة الأسماء و / أو ظهور بطاقات النهاية.";
|
"Typically near or at the end of the video when the credits pop up and/or endcards are shown." = "عادةً ما تكون بالقرب من نهاية الفيديو عند ظهور قائمة الأسماء و / أو ظهور بطاقات النهاية.";
|
||||||
"Unsubscribe" = "إلغاء الإشتراك";
|
"Unsubscribe" = "إلغاء الإشتراك";
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
" subscribers" = " 人の登録者";
|
" subscribers" = " 人の登録者";
|
||||||
"%@ subscribers" = "%@ 人の登録者";
|
"%@ subscribers" = "%@ 人の登録者";
|
||||||
"Accounts are not supported for the application of this instance" = "このインスタンスはアカウントに対応していません";
|
"Accounts are not supported for the application of this instance" = "このインスタンスはアカウントに対応していません";
|
||||||
"%lld videos" = "%lld本の動画";
|
"%lld videos" = "本の動画";
|
||||||
"%@ Channel" = "%@ チャンネル";
|
"%@ Channel" = "%@ チャンネル";
|
||||||
"%@ Playlist" = "%@ 再生リスト";
|
"%@ Playlist" = "%@ 再生リスト";
|
||||||
"Add Location" = "場所を追加";
|
"Add Location" = "場所を追加";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "9899ef48b3ee49eae175e25421b8330438e40c30a266d96473b299a6ab7c4188",
|
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "activelabel.swift",
|
"identity" : "activelabel.swift",
|
||||||
@@ -60,8 +59,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/cxfksword/MPVKit.git",
|
"location" : "https://github.com/cxfksword/MPVKit.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "96825b3dc2b38e5550268156148d47798ce6aa74",
|
"revision" : "dca1e345a26d09a3d621d7656a94e6427f3f7b83"
|
||||||
"version" : "0.36.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,8 +85,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/ashleymills/Reachability.swift",
|
"location" : "https://github.com/ashleymills/Reachability.swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "c01127cb51f591045696128effe43c16840d08bf",
|
"revision" : "c01bbdf2d633cf049ae1ed1a68a2020a8bda32e2",
|
||||||
"version" : "5.2.0"
|
"version" : "5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -106,7 +104,7 @@
|
|||||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "master",
|
"branch" : "master",
|
||||||
"revision" : "a41be90abd89b125cd7588f20b9788108254091a"
|
"revision" : "59730af512c06fb569c119d737df4c1c499e001d"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -132,8 +130,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/SDWebImage/SDWebImageWebPCoder.git",
|
"location" : "https://github.com/SDWebImage/SDWebImageWebPCoder.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "acfb824ca5cd9dbde2c43dc6b5a008c6757dee85",
|
"revision" : "db4603921b31a6ce0f8c26d36d6a3fffc2dba481",
|
||||||
"version" : "0.14.3"
|
"version" : "0.14.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,16 +34,6 @@
|
|||||||
<string>public.file-url</string>
|
<string>public.file-url</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeName</key>
|
|
||||||
<string>Settings text</string>
|
|
||||||
<key>LSHandlerRank</key>
|
|
||||||
<string>Default</string>
|
|
||||||
<key>LSItemContentTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>public.json</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
@@ -78,31 +68,5 @@
|
|||||||
</array>
|
</array>
|
||||||
<key>UIFileSharingEnabled</key>
|
<key>UIFileSharingEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UTTypeConformsTo</key>
|
|
||||||
<array>
|
|
||||||
<string>public.json</string>
|
|
||||||
</array>
|
|
||||||
<key>UTTypeDescription</key>
|
|
||||||
<string>Yattee Settings</string>
|
|
||||||
<key>UTTypeIconFiles</key>
|
|
||||||
<array/>
|
|
||||||
<key>UTTypeIdentifier</key>
|
|
||||||
<string>stream.yattee.app-settings</string>
|
|
||||||
<key>UTTypeTagSpecification</key>
|
|
||||||
<dict>
|
|
||||||
<key>public.filename-extension</key>
|
|
||||||
<array>
|
|
||||||
<string>yatteesettings</string>
|
|
||||||
</array>
|
|
||||||
<key>public.mime-type</key>
|
|
||||||
<array>
|
|
||||||
<string>application/json</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Defaults
|
|||||||
import Logging
|
import Logging
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
enum Orientation {
|
struct Orientation {
|
||||||
static var logger = Logger(label: "stream.yattee.orientation")
|
static var logger = Logger(label: "stream.yattee.orientation")
|
||||||
|
|
||||||
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
|
static func lockOrientation(_ orientation: UIInterfaceOrientationMask) {
|
||||||
|
|||||||
@@ -16,18 +16,6 @@
|
|||||||
<string>public.mpeg-4</string>
|
<string>public.mpeg-4</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
<dict>
|
|
||||||
<key>CFBundleTypeName</key>
|
|
||||||
<string>Settings</string>
|
|
||||||
<key>CFBundleTypeRole</key>
|
|
||||||
<string>Editor</string>
|
|
||||||
<key>LSHandlerRank</key>
|
|
||||||
<string>Default</string>
|
|
||||||
<key>LSItemContentTypes</key>
|
|
||||||
<array>
|
|
||||||
<string>public.json</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
<array>
|
<array>
|
||||||
@@ -49,51 +37,5 @@
|
|||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true/>
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>UTExportedTypeDeclarations</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UTTypeConformsTo</key>
|
|
||||||
<array>
|
|
||||||
<string>public.json</string>
|
|
||||||
</array>
|
|
||||||
<key>UTTypeDescription</key>
|
|
||||||
<string>Yattee Settings</string>
|
|
||||||
<key>UTTypeIcons</key>
|
|
||||||
<dict/>
|
|
||||||
<key>UTTypeIdentifier</key>
|
|
||||||
<string>stream.yattee.app-settings</string>
|
|
||||||
<key>UTTypeTagSpecification</key>
|
|
||||||
<dict>
|
|
||||||
<key>public.filename-extension</key>
|
|
||||||
<array>
|
|
||||||
<string>yatteesettings</string>
|
|
||||||
</array>
|
|
||||||
<key>public.mime-type</key>
|
|
||||||
<array/>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
<key>UTImportedTypeDeclarations</key>
|
|
||||||
<array>
|
|
||||||
<dict>
|
|
||||||
<key>UTTypeConformsTo</key>
|
|
||||||
<array>
|
|
||||||
<string>public.json</string>
|
|
||||||
</array>
|
|
||||||
<key>UTTypeDescription</key>
|
|
||||||
<string>Yattee Settings</string>
|
|
||||||
<key>UTTypeIcons</key>
|
|
||||||
<dict/>
|
|
||||||
<key>UTTypeIdentifier</key>
|
|
||||||
<string>stream.yattee.app-settings</string>
|
|
||||||
<key>UTTypeTagSpecification</key>
|
|
||||||
<dict>
|
|
||||||
<key>public.filename-extension</key>
|
|
||||||
<array>
|
|
||||||
<string>yatteesettings</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</dict>
|
|
||||||
</array>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ final class PlayerViewController: NSViewController {
|
|||||||
return [ratio, 1.0].max()!
|
return [ratio, 1.0].max()!
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewDidDisappear() {
|
override func viewDidDisappear() {
|
||||||
super.viewDidDisappear()
|
super.viewDidDisappear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum Power {
|
struct Power {
|
||||||
static var hasInternalBattery: Bool {
|
static var hasInternalBattery: Bool {
|
||||||
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
let psInfo = IOPSCopyPowerSourcesInfo().takeRetainedValue()
|
||||||
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
|
let psList = IOPSCopyPowerSourcesList(psInfo).takeRetainedValue() as [CFTypeRef]
|
||||||
|
|||||||
Reference in New Issue
Block a user