mirror of
https://github.com/yattee/yattee.git
synced 2025-12-21 20:20:15 +00:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46725bf4d9 | ||
|
|
8697ec8faf | ||
|
|
8a015d29c3 | ||
|
|
4097d11b5e | ||
|
|
5323d53f9e | ||
|
|
e3e0c4a92f | ||
|
|
9e5efc1aa6 | ||
|
|
1ed4c20c3a | ||
|
|
ced9eb28d7 | ||
|
|
ea49758ed2 | ||
|
|
65ec675859 | ||
|
|
9a650799d3 | ||
|
|
ddd1f243f7 | ||
|
|
94f19d55c8 | ||
|
|
30cdaf88e1 | ||
|
|
8139bba31e |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -1,12 +1,8 @@
|
|||||||
## Build 185
|
## Build 186
|
||||||
* Fix thumbnails failing to load on tvOS by @patelhiren in https://github.com/yattee/yattee/pull/688
|
* tvOS: Refined Subscriptions View by @patelhiren in https://github.com/yattee/yattee/pull/697
|
||||||
* speed up sorting for Stream by @stonerl in https://github.com/yattee/yattee/pull/681
|
* More responsive UI when Favorites are used. by @stonerl in https://github.com/yattee/yattee/pull/695
|
||||||
* faster chapter extraction by @stonerl in https://github.com/yattee/yattee/pull/682
|
* Improved conditional proxying by @stonerl in https://github.com/yattee/yattee/pull/696
|
||||||
* Invidious: add images to chapters by @stonerl in https://github.com/yattee/yattee/pull/685
|
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/694
|
||||||
* Improved Captions handling by @stonerl in https://github.com/yattee/yattee/pull/684
|
|
||||||
* Add User-Agent to request by @stonerl in https://github.com/yattee/yattee/pull/680
|
|
||||||
* MPV: speed up playback start by @stonerl in https://github.com/yattee/yattee/pull/689
|
|
||||||
* Translations update from Hosted Weblate by @weblate in https://github.com/yattee/yattee/pull/683
|
|
||||||
|
|
||||||
## Previous builds
|
## Previous builds
|
||||||
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
* Add skip, play/pause, and fullscreen shortcuts to macOS player (by @rickykresslein)
|
||||||
@@ -44,6 +40,13 @@
|
|||||||
* HomeView: Changes to Favourites and History Widget by @stonerl in https://github.com/yattee/yattee/pull/672
|
* HomeView: Changes to Favourites and History Widget by @stonerl in https://github.com/yattee/yattee/pull/672
|
||||||
* Snappy UI - Offloading non UI task to background threads by @stonerl in https://github.com/yattee/yattee/pull/671
|
* Snappy UI - Offloading non UI task to background threads by @stonerl in https://github.com/yattee/yattee/pull/671
|
||||||
* Fix PiP Mode Not Working Using MPV by @stonerl in https://github.com/yattee/yattee/pull/676
|
* Fix PiP Mode Not Working Using MPV by @stonerl in https://github.com/yattee/yattee/pull/676
|
||||||
|
* Fix thumbnails failing to load on tvOS by @patelhiren in https://github.com/yattee/yattee/pull/688
|
||||||
|
* speed up sorting for Stream by @stonerl in https://github.com/yattee/yattee/pull/681
|
||||||
|
* faster chapter extraction by @stonerl in https://github.com/yattee/yattee/pull/682
|
||||||
|
* Invidious: add images to chapters by @stonerl in https://github.com/yattee/yattee/pull/685
|
||||||
|
* Improved Captions handling by @stonerl in https://github.com/yattee/yattee/pull/684
|
||||||
|
* Add User-Agent to request by @stonerl in https://github.com/yattee/yattee/pull/680
|
||||||
|
* MPV: speed up playback start by @stonerl in https://github.com/yattee/yattee/pull/689
|
||||||
* Advanced Settings: cache-pause-initial by @stonerl in https://github.com/yattee/yattee/pull/679
|
* Advanced Settings: cache-pause-initial by @stonerl in https://github.com/yattee/yattee/pull/679
|
||||||
* Changed description for Format reordering by @stonerl in https://github.com/yattee/yattee/pull/677
|
* Changed description for Format reordering by @stonerl in https://github.com/yattee/yattee/pull/677
|
||||||
* Add Chinese (Traditional) localization (by @rexcsk)
|
* Add Chinese (Traditional) localization (by @rexcsk)
|
||||||
|
|||||||
18
Gemfile.lock
18
Gemfile.lock
@@ -57,17 +57,17 @@ GEM
|
|||||||
artifactory (3.0.17)
|
artifactory (3.0.17)
|
||||||
atomos (0.1.3)
|
atomos (0.1.3)
|
||||||
aws-eventstream (1.3.0)
|
aws-eventstream (1.3.0)
|
||||||
aws-partitions (1.933.0)
|
aws-partitions (1.944.0)
|
||||||
aws-sdk-core (3.196.1)
|
aws-sdk-core (3.197.0)
|
||||||
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.82.0)
|
aws-sdk-kms (1.83.0)
|
||||||
aws-sdk-core (~> 3, >= 3.193.0)
|
aws-sdk-core (~> 3, >= 3.197.0)
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sdk-s3 (1.151.0)
|
aws-sdk-s3 (1.152.2)
|
||||||
aws-sdk-core (~> 3, >= 3.194.0)
|
aws-sdk-core (~> 3, >= 3.197.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)
|
||||||
@@ -153,7 +153,7 @@ GEM
|
|||||||
os (>= 0.9, < 2.0)
|
os (>= 0.9, < 2.0)
|
||||||
signet (>= 0.16, < 2.a)
|
signet (>= 0.16, < 2.a)
|
||||||
highline (2.0.3)
|
highline (2.0.3)
|
||||||
http-cookie (1.0.5)
|
http-cookie (1.0.6)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
jmespath (1.6.2)
|
jmespath (1.6.2)
|
||||||
@@ -177,8 +177,8 @@ GEM
|
|||||||
trailblazer-option (>= 0.1.1, < 0.2.0)
|
trailblazer-option (>= 0.1.1, < 0.2.0)
|
||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
retriable (3.1.2)
|
retriable (3.1.2)
|
||||||
rexml (3.2.8)
|
rexml (3.2.9)
|
||||||
strscan (>= 3.0.9)
|
strscan
|
||||||
rouge (2.0.7)
|
rouge (2.0.7)
|
||||||
ruby2_keywords (0.0.5)
|
ruby2_keywords (0.0.5)
|
||||||
rubyzip (2.3.2)
|
rubyzip (2.3.2)
|
||||||
|
|||||||
@@ -56,65 +56,85 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
|
func streamsWithInstance(instance: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
|
||||||
// Queue for stream processing
|
// Queue for stream processing
|
||||||
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue", qos: .userInitiated)
|
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue")
|
||||||
// Queue for accessing the processedStreams array
|
// Queue for accessing the processedStreams array
|
||||||
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
|
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
|
||||||
// DispatchGroup for managing multiple tasks
|
// DispatchGroup for managing multiple tasks
|
||||||
let streamProcessingGroup = DispatchGroup()
|
let streamProcessingGroup = DispatchGroup()
|
||||||
|
|
||||||
var processedStreams = [Stream]()
|
var processedStreams = [Stream]()
|
||||||
|
let instance = instance
|
||||||
|
|
||||||
|
var hasForbiddenAsset = false
|
||||||
|
var hasAllowedAsset = false
|
||||||
|
|
||||||
for stream in streams {
|
for stream in streams {
|
||||||
streamProcessingQueue.async(group: streamProcessingGroup) {
|
streamProcessingQueue.async(group: streamProcessingGroup) {
|
||||||
let forbiddenAssetTestGroup = DispatchGroup()
|
let forbiddenAssetTestGroup = DispatchGroup()
|
||||||
var hasForbiddenAsset = false
|
if !hasAllowedAsset, !hasForbiddenAsset, !instance.proxiesVideos, stream.format != Stream.Format.unknown {
|
||||||
|
|
||||||
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
|
let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
|
||||||
|
if let firstStream = nonHLSAssets.first {
|
||||||
|
let asset = firstStream.0
|
||||||
|
let url = firstStream.1
|
||||||
|
let requestRange = firstStream.2
|
||||||
|
|
||||||
if let randomStream = nonHLSAssets.randomElement() {
|
|
||||||
let instance = randomStream.0
|
|
||||||
let asset = randomStream.1
|
|
||||||
let url = randomStream.2
|
|
||||||
let requestRange = randomStream.3
|
|
||||||
|
|
||||||
// swiftlint:disable:next shorthand_optional_binding
|
|
||||||
if let asset = asset, let instance = instance, !instance.proxiesVideos {
|
|
||||||
if instance.app == .invidious {
|
if instance.app == .invidious {
|
||||||
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
|
||||||
hasForbiddenAsset = isForbidden
|
switch status {
|
||||||
|
case HTTPStatus.Forbidden:
|
||||||
|
hasForbiddenAsset = true
|
||||||
|
case HTTPStatus.PartialContent:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
case HTTPStatus.OK:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if instance.app == .piped {
|
} else if instance.app == .piped {
|
||||||
self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
self.testPipedAssets(asset: asset!, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
|
||||||
hasForbiddenAsset = isForbidden
|
switch status {
|
||||||
|
case HTTPStatus.Forbidden:
|
||||||
|
hasForbiddenAsset = true
|
||||||
|
case HTTPStatus.PartialContent:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
case HTTPStatus.OK:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let randomHLS = hlsURLs.randomElement() {
|
} else if let firstHLS = hlsURLs.first {
|
||||||
let instance = randomHLS.0
|
let asset = AVURLAsset(url: firstHLS)
|
||||||
let asset = AVURLAsset(url: randomHLS.1)
|
if instance.app == .piped {
|
||||||
|
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { status in
|
||||||
if instance?.app == .piped {
|
switch status {
|
||||||
self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
|
case HTTPStatus.Forbidden:
|
||||||
hasForbiddenAsset = isForbidden
|
hasForbiddenAsset = true
|
||||||
|
case HTTPStatus.PartialContent:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
case HTTPStatus.OK:
|
||||||
|
hasAllowedAsset = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
forbiddenAssetTestGroup.wait()
|
forbiddenAssetTestGroup.wait()
|
||||||
|
|
||||||
// Post-processing code
|
// Post-processing code
|
||||||
if let instance = stream.instance {
|
if instance.app == .invidious, hasForbiddenAsset || instance.proxiesVideos {
|
||||||
if instance.app == .invidious {
|
|
||||||
if hasForbiddenAsset || instance.proxiesVideos {
|
|
||||||
if let audio = stream.audioAsset {
|
if let audio = stream.audioAsset {
|
||||||
stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio)
|
stream.audioAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: audio)
|
||||||
}
|
}
|
||||||
if let video = stream.videoAsset {
|
if let video = stream.videoAsset {
|
||||||
stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video)
|
stream.videoAsset = InvidiousAPI.proxiedAsset(instance: instance, asset: video)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
|
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
|
||||||
if let hlsURL = stream.hlsURL {
|
if let hlsURL = stream.hlsURL {
|
||||||
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
|
PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
|
||||||
@@ -135,7 +155,6 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Append to processedStreams within the processedStreamsQueue
|
// Append to processedStreams within the processedStreamsQueue
|
||||||
processedStreamsQueue.sync {
|
processedStreamsQueue.sync {
|
||||||
@@ -152,21 +171,21 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(Instance?, AVURLAsset?, URL, String?)], hlsURLs: [(Instance?, URL)]) {
|
private func getAssets(from streams: [Stream]) -> (nonHLSAssets: [(AVURLAsset?, URL, String?)], hlsURLs: [URL]) {
|
||||||
var nonHLSAssets = [(Instance?, AVURLAsset?, URL, String?)]()
|
var nonHLSAssets = [(AVURLAsset?, URL, String?)]()
|
||||||
var hlsURLs = [(Instance?, URL)]()
|
var hlsURLs = [URL]()
|
||||||
|
|
||||||
for stream in streams {
|
for stream in streams {
|
||||||
if stream.isHLS {
|
if stream.isHLS {
|
||||||
if let url = stream.hlsURL?.url {
|
if let url = stream.hlsURL?.url {
|
||||||
hlsURLs.append((stream.instance, url))
|
hlsURLs.append(url)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let asset = stream.audioAsset {
|
if let asset = stream.audioAsset {
|
||||||
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange))
|
nonHLSAssets.append((asset, asset.url, stream.requestRange))
|
||||||
}
|
}
|
||||||
if let asset = stream.videoAsset {
|
if let asset = stream.videoAsset {
|
||||||
nonHLSAssets.append((stream.instance, asset, asset.url, stream.requestRange))
|
nonHLSAssets.append((asset, asset.url, stream.requestRange))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,24 +193,24 @@ extension PlayerModel {
|
|||||||
return (nonHLSAssets, hlsURLs)
|
return (nonHLSAssets, hlsURLs)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
|
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
|
||||||
// In case the range is nil, generate a random one.
|
// In case the range is nil, generate a random one.
|
||||||
let randomEnd = Int.random(in: 200 ... 800)
|
let randomEnd = Int.random(in: 200 ... 800)
|
||||||
let requestRange = range ?? "0-\(randomEnd)"
|
let requestRange = range ?? "0-\(randomEnd)"
|
||||||
|
|
||||||
forbiddenAssetTestGroup.enter()
|
forbiddenAssetTestGroup.enter()
|
||||||
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
|
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
|
||||||
completion(statusCode == HTTPStatus.Forbidden)
|
completion(statusCode)
|
||||||
forbiddenAssetTestGroup.leave()
|
forbiddenAssetTestGroup.leave()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
|
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Int) -> Void) {
|
||||||
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
|
PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
|
||||||
if let nonProxiedAsset = possibleNonProxiedAsset {
|
if let nonProxiedAsset = possibleNonProxiedAsset {
|
||||||
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
|
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
|
||||||
} else {
|
} else {
|
||||||
completion(false)
|
completion(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,28 +88,40 @@ struct FavoriteItemView: View {
|
|||||||
reloadVisibleWatches()
|
reloadVisibleWatches()
|
||||||
} else {
|
} else {
|
||||||
resource?.addObserver(store)
|
resource?.addObserver(store)
|
||||||
loadCacheAndResource()
|
DispatchQueue.main.async {
|
||||||
|
self.loadCacheAndResource()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
resource?.removeObservers(ownedBy: store)
|
resource?.removeObservers(ownedBy: store)
|
||||||
}
|
}
|
||||||
.onChange(of: player.currentVideo) { _ in reloadVisibleWatches() }
|
.onChange(of: player.currentVideo) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
|
||||||
.onChange(of: hideShorts) { _ in reloadVisibleWatches() }
|
.onChange(of: hideShorts) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
|
||||||
.onChange(of: hideWatched) { _ in reloadVisibleWatches() }
|
.onChange(of: hideWatched) { _ in if !player.presentingPlayer { reloadVisibleWatches() } }
|
||||||
.onChange(of: favoritesChanged) { _ in reloadVisibleWatches() }
|
// Delay is necessary to update the list with the new items.
|
||||||
|
.onChange(of: favoritesChanged) { _ in if !player.presentingPlayer { Delay.by(1.0) { reloadVisibleWatches() } } }
|
||||||
|
.onChange(of: player.presentingPlayer) { _ in
|
||||||
|
if player.presentingPlayer {
|
||||||
|
resource?.removeObservers(ownedBy: store)
|
||||||
|
} else {
|
||||||
|
resource?.addObserver(store)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.id(watchModel.historyToken)
|
.id(watchModel.historyToken)
|
||||||
.onChange(of: accounts.current) { _ in
|
.onChange(of: accounts.current) { _ in
|
||||||
resource?.removeObservers(ownedBy: store)
|
DispatchQueue.main.async {
|
||||||
resource?.addObserver(store)
|
|
||||||
loadCacheAndResource(force: true)
|
loadCacheAndResource(force: true)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.onChange(of: watchModel.historyToken) { _ in
|
.onChange(of: watchModel.historyToken) { _ in
|
||||||
|
if !player.presentingPlayer {
|
||||||
reloadVisibleWatches()
|
reloadVisibleWatches()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var emptyItemsText: String {
|
var emptyItemsText: String {
|
||||||
var filterText = ""
|
var filterText = ""
|
||||||
@@ -159,6 +171,7 @@ struct FavoriteItemView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reloadVisibleWatches() {
|
func reloadVisibleWatches() {
|
||||||
|
DispatchQueue.main.async {
|
||||||
guard item.section == .history else { return }
|
guard item.section == .history else { return }
|
||||||
|
|
||||||
visibleWatches = []
|
visibleWatches = []
|
||||||
@@ -181,6 +194,7 @@ struct FavoriteItemView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var limitedItems: [ContentItem] {
|
var limitedItems: [ContentItem] {
|
||||||
var items: [ContentItem]
|
var items: [ContentItem]
|
||||||
@@ -227,6 +241,9 @@ struct FavoriteItemView: View {
|
|||||||
onSuccess = { response in
|
onSuccess = { response in
|
||||||
if let videos: [Video] = response.typedContent() {
|
if let videos: [Video] = response.typedContent() {
|
||||||
FeedCacheModel.shared.storeFeed(account: accounts.current, videos: videos)
|
FeedCacheModel.shared.storeFeed(account: accounts.current, videos: videos)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
store.contentItems = contentItems
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .channel(_, id, name):
|
case let .channel(_, id, name):
|
||||||
@@ -239,6 +256,7 @@ struct FavoriteItemView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSuccess = { response in
|
onSuccess = { response in
|
||||||
|
DispatchQueue.main.async {
|
||||||
if let channel: Channel = response.typedContent() {
|
if let channel: Channel = response.typedContent() {
|
||||||
ChannelsCacheModel.shared.store(channel)
|
ChannelsCacheModel.shared.store(channel)
|
||||||
store.contentItems = ContentItem.array(of: channel.videos)
|
store.contentItems = ContentItem.array(of: channel.videos)
|
||||||
@@ -254,6 +272,7 @@ struct FavoriteItemView: View {
|
|||||||
store.contentItems = channelPage.results
|
store.contentItems = channelPage.results
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
case let .channelPlaylist(_, id, title):
|
case let .channelPlaylist(_, id, title):
|
||||||
if let cache = ChannelPlaylistsCacheModel.shared.retrievePlaylist(.init(id: id, title: title)),
|
if let cache = ChannelPlaylistsCacheModel.shared.retrievePlaylist(.init(id: id, title: title)),
|
||||||
!cache.videos.isEmpty
|
!cache.videos.isEmpty
|
||||||
@@ -264,6 +283,9 @@ struct FavoriteItemView: View {
|
|||||||
onSuccess = { response in
|
onSuccess = { response in
|
||||||
if let playlist: ChannelPlaylist = response.typedContent() {
|
if let playlist: ChannelPlaylist = response.typedContent() {
|
||||||
ChannelPlaylistsCacheModel.shared.storePlaylist(playlist: playlist)
|
ChannelPlaylistsCacheModel.shared.storePlaylist(playlist: playlist)
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
store.contentItems = contentItems
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case let .playlist(_, id):
|
case let .playlist(_, id):
|
||||||
@@ -272,13 +294,17 @@ struct FavoriteItemView: View {
|
|||||||
if let playlist = playlists.first(where: { $0.id == id }) {
|
if let playlist = playlists.first(where: { $0.id == id }) {
|
||||||
contentItems = ContentItem.array(of: playlist.videos)
|
contentItems = ContentItem.array(of: playlist.videos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
store.contentItems = contentItems
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
contentItems = []
|
contentItems = []
|
||||||
}
|
|
||||||
|
|
||||||
if !contentItems.isEmpty {
|
DispatchQueue.main.async {
|
||||||
store.contentItems = contentItems
|
store.contentItems = contentItems
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if force {
|
if force {
|
||||||
resource.load().onSuccess(onSuccess)
|
resource.load().onSuccess(onSuccess)
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import UniformTypeIdentifiers
|
|||||||
|
|
||||||
struct HomeView: View {
|
struct HomeView: View {
|
||||||
@ObservedObject private var accounts = AccountsModel.shared
|
@ObservedObject private var accounts = AccountsModel.shared
|
||||||
|
@ObservedObject private var player = PlayerModel.shared
|
||||||
|
|
||||||
@State private var presentingHomeSettings = false
|
@State private var presentingHomeSettings = false
|
||||||
@State private var favoritesChanged = false
|
@State private var favoritesChanged = false
|
||||||
|
@State private var updateTask: Task<Void, Never>?
|
||||||
|
|
||||||
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
|
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
|
||||||
var watches: FetchedResults<Watch>
|
var watches: FetchedResults<Watch>
|
||||||
@@ -16,8 +18,6 @@ struct HomeView: View {
|
|||||||
@State private var recentDocumentsID = UUID()
|
@State private var recentDocumentsID = UUID()
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var favoritesObserver: Any?
|
|
||||||
|
|
||||||
#if !os(tvOS)
|
#if !os(tvOS)
|
||||||
@Default(.favorites) private var favorites
|
@Default(.favorites) private var favorites
|
||||||
@Default(.widgetsSettings) private var widgetsSettings
|
@Default(.widgetsSettings) private var widgetsSettings
|
||||||
@@ -124,6 +124,24 @@ struct HomeView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
updateTask?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
.onChange(of: player.presentingPlayer) { _ in
|
||||||
|
if player.presentingPlayer {
|
||||||
|
updateTask?.cancel()
|
||||||
|
} else {
|
||||||
|
Task {
|
||||||
|
for await _ in Defaults.updates(.favorites) {
|
||||||
|
favoritesChanged.toggle()
|
||||||
|
}
|
||||||
|
for await _ in Defaults.updates(.widgetsSettings) {
|
||||||
|
favoritesChanged.toggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.redrawOn(change: favoritesChanged)
|
.redrawOn(change: favoritesChanged)
|
||||||
|
|
||||||
|
|||||||
@@ -179,10 +179,6 @@ struct AdvancedSettings: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
if mpvEnableLogging {
|
|
||||||
logButton
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack {
|
HStack {
|
||||||
Text("demuxer-lavf-probe-info")
|
Text("demuxer-lavf-probe-info")
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import SwiftUI
|
|||||||
|
|
||||||
struct FeedView: View {
|
struct FeedView: View {
|
||||||
@ObservedObject private var feed = FeedModel.shared
|
@ObservedObject private var feed = FeedModel.shared
|
||||||
|
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared
|
||||||
|
@ObservedObject private var feedCount = UnwatchedFeedCountModel.shared
|
||||||
|
|
||||||
@Default(.showCacheStatus) private var showCacheStatus
|
@Default(.showCacheStatus) private var showCacheStatus
|
||||||
|
|
||||||
@@ -12,10 +14,155 @@ struct FeedView: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
var videos: [ContentItem] {
|
var videos: [ContentItem] {
|
||||||
ContentItem.array(of: feed.videos)
|
guard let selectedChannel = selectedChannel else {
|
||||||
|
return ContentItem.array(of: feed.videos)
|
||||||
|
}
|
||||||
|
return ContentItem.array(of: feed.videos.filter {
|
||||||
|
$0.channel.id == selectedChannel.id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var channels: [Channel] {
|
||||||
|
feed.videos.map {
|
||||||
|
$0.channel
|
||||||
|
}.unique()
|
||||||
|
}
|
||||||
|
|
||||||
|
@State private var selectedChannel: Channel?
|
||||||
|
#if os(tvOS)
|
||||||
|
@FocusState private var focusedChannel: String?
|
||||||
|
#endif
|
||||||
|
@State private var feedChannelsViewVisible = false
|
||||||
|
private var navigation = NavigationModel.shared
|
||||||
|
private let dismiss_channel_list_id = "dismiss_channel_list_id"
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
#if os(tvOS)
|
||||||
|
GeometryReader { geometry in
|
||||||
|
ZStack {
|
||||||
|
// selected channel feed view
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
// sidebar - show channels
|
||||||
|
if feedChannelsViewVisible {
|
||||||
|
Spacer()
|
||||||
|
.frame(width: geometry.size.width * 0.3)
|
||||||
|
}
|
||||||
|
selectedFeedView
|
||||||
|
}
|
||||||
|
.disabled(feedChannelsViewVisible)
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
|
||||||
|
if feedChannelsViewVisible {
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
// sidebar - show channels
|
||||||
|
feedChannelsView
|
||||||
|
.padding(.all)
|
||||||
|
.frame(width: geometry.size.width * 0.3)
|
||||||
|
.background()
|
||||||
|
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: 16))
|
||||||
|
Rectangle()
|
||||||
|
.fill(.clear)
|
||||||
|
.id(dismiss_channel_list_id)
|
||||||
|
.focusable()
|
||||||
|
.focused(self.$focusedChannel, equals: dismiss_channel_list_id)
|
||||||
|
}
|
||||||
|
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||||
|
.clipped()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
selectedFeedView
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#if os(tvOS)
|
||||||
|
var feedChannelsView: some View {
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
VStack {
|
||||||
|
Text("Channels")
|
||||||
|
.font(.subheadline)
|
||||||
|
if #available(tvOS 17.0, *) {
|
||||||
|
List(selection: $selectedChannel) {
|
||||||
|
Button(action: {
|
||||||
|
self.selectedChannel = nil
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
Image(systemName: RecentsModel.symbolSystemImage("A"))
|
||||||
|
.imageScale(.large)
|
||||||
|
.foregroundColor(.accentColor)
|
||||||
|
.frame(width: 35, height: 35)
|
||||||
|
Text("All")
|
||||||
|
Spacer()
|
||||||
|
feedCount.unwatchedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
|
.fill(self.selectedChannel == nil ? Color.secondary : Color.clear))
|
||||||
|
.font(.caption)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.focused(self.$focusedChannel, equals: "all")
|
||||||
|
|
||||||
|
ForEach(channels, id: \.self) { channel in
|
||||||
|
Button(action: {
|
||||||
|
self.selectedChannel = channel
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}) {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
ChannelAvatarView(channel: channel, subscribedBadge: false)
|
||||||
|
.frame(width: 50, height: 50)
|
||||||
|
Text(channel.name)
|
||||||
|
.lineLimit(1)
|
||||||
|
Spacer()
|
||||||
|
if let unwatchedCount = feedCount.unwatchedByChannelText(channel) {
|
||||||
|
unwatchedCount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.all)
|
||||||
|
.background(RoundedRectangle(cornerRadius: 8.0)
|
||||||
|
.fill(self.selectedChannel == channel ? Color.secondary : Color.clear))
|
||||||
|
.font(.caption)
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.focused(self.$focusedChannel, equals: channel.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onChange(of: self.focusedChannel) {
|
||||||
|
if self.focusedChannel == "all" {
|
||||||
|
withAnimation {
|
||||||
|
self.selectedChannel = nil
|
||||||
|
}
|
||||||
|
} else if self.focusedChannel == dismiss_channel_list_id {
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
} else {
|
||||||
|
withAnimation {
|
||||||
|
self.selectedChannel = channels.first {
|
||||||
|
$0.id == self.focusedChannel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onAppear {
|
||||||
|
guard let selectedChannel = self.selectedChannel else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
proxy.scrollTo(selectedChannel, anchor: .top)
|
||||||
|
}
|
||||||
|
.onExitCommand {
|
||||||
|
withAnimation {
|
||||||
|
self.feedChannelsViewVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
var selectedFeedView: some View {
|
||||||
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
|
VerticalCells(items: videos) { if shouldDisplayHeader { header } }
|
||||||
.environment(\.loadMoreContentHandler) { feed.loadNextPage() }
|
.environment(\.loadMoreContentHandler) { feed.loadNextPage() }
|
||||||
.onAppear {
|
.onAppear {
|
||||||
@@ -49,17 +196,34 @@ struct FeedView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var header: some View {
|
var header: some View {
|
||||||
HStack {
|
HStack(spacing: 16) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
SubscriptionsPageButton()
|
Button(action: {
|
||||||
|
withAnimation {
|
||||||
|
self.feedChannelsViewVisible = true
|
||||||
|
self.focusedChannel = selectedChannel?.id ?? "all"
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Label("Channels", systemImage: "filemenu.and.selection")
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
.imageScale(.small)
|
||||||
|
.font(.caption)
|
||||||
|
}
|
||||||
|
.opacity(feedChannelsViewVisible ? 0 : 1)
|
||||||
|
.frame(minWidth: feedChannelsViewVisible ? 0 : nil, maxWidth: feedChannelsViewVisible ? 0 : nil)
|
||||||
|
channelHeaderView
|
||||||
|
if selectedChannel == nil {
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
|
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
|
||||||
HideWatchedButtons()
|
HideWatchedButtons()
|
||||||
HideShortsButtons()
|
HideShortsButtons()
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
if showCacheStatus {
|
if showCacheStatus {
|
||||||
Spacer()
|
|
||||||
|
|
||||||
CacheStatusHeader(
|
CacheStatusHeader(
|
||||||
refreshTime: feed.formattedFeedTime,
|
refreshTime: feed.formattedFeedTime,
|
||||||
isLoading: feed.isLoading
|
isLoading: feed.isLoading
|
||||||
@@ -77,6 +241,7 @@ struct FeedView: View {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.padding(.leading, 30)
|
.padding(.leading, 30)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.padding(.bottom, 15)
|
.padding(.bottom, 15)
|
||||||
@@ -84,6 +249,46 @@ struct FeedView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var channelHeaderView: some View {
|
||||||
|
guard let selectedChannel = selectedChannel else {
|
||||||
|
return AnyView(
|
||||||
|
Text("All Channels")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.padding(0)
|
||||||
|
.padding(.leading, 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnyView(
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
ChannelAvatarView(channel: selectedChannel, subscribedBadge: false)
|
||||||
|
.id("channel-avatar-\(selectedChannel.id)")
|
||||||
|
.frame(width: 80, height: 80)
|
||||||
|
Text("\(selectedChannel.name)")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
|
Spacer()
|
||||||
|
if feedChannelsViewVisible == false {
|
||||||
|
Button(action: {
|
||||||
|
navigation.openChannel(selectedChannel, navigationStyle: .tab)
|
||||||
|
}) {
|
||||||
|
Text("Visit Channel")
|
||||||
|
.font(.caption)
|
||||||
|
.frame(alignment: .leading)
|
||||||
|
.lineLimit(1)
|
||||||
|
.fixedSize(horizontal: true, vertical: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(0)
|
||||||
|
.padding(.leading, 16)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var shouldDisplayHeader: Bool {
|
var shouldDisplayHeader: Bool {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
true
|
true
|
||||||
|
|||||||
@@ -264,7 +264,7 @@
|
|||||||
"Don't use public locations" = "Ne pas utiliser d'instances publiques";
|
"Don't use public locations" = "Ne pas utiliser d'instances publiques";
|
||||||
"Enable Return YouTube Dislike" = "Activer Return YouTube Dislike";
|
"Enable Return YouTube Dislike" = "Activer Return YouTube Dislike";
|
||||||
"Enter fullscreen in landscape" = "Entrer en plein écran en mode paysage";
|
"Enter fullscreen in landscape" = "Entrer en plein écran en mode paysage";
|
||||||
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Rappels d'aimer la vidéo, de s'abonner ou d'interagir avec le créateur sur une plateforme gratuite ou payante.\n";
|
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "Rappels d'aimer la vidéo, de s'abonner ou d'interagir avec le créateur sur une plateforme gratuite ou payante.";
|
||||||
"Frontend URL" = "URL frontale";
|
"Frontend URL" = "URL frontale";
|
||||||
"Public Locations" = "Instances publiques";
|
"Public Locations" = "Instances publiques";
|
||||||
"Public Manifest" = "Manifeste publique";
|
"Public Manifest" = "Manifeste publique";
|
||||||
|
|||||||
43
Shared/ko.lproj/Localizable.strings
Normal file
43
Shared/ko.lproj/Localizable.strings
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
|
||||||
|
|
||||||
|
"%@ Playlist" = "%@ 플레이리스트";
|
||||||
|
"%@ subscribers" = "%@ 구독자";
|
||||||
|
"10 seconds forwards/backwards" = "10초 건너뛰기/뒤로 가기";
|
||||||
|
"Accounts" = "계정";
|
||||||
|
"Accounts are not supported for the application of this instance" = "해당 애플리케이션 인스턴스는 계정을 지원하지 않습니다";
|
||||||
|
"Add Location" = "주소 추가";
|
||||||
|
"Add Location..." = "주소 추가...";
|
||||||
|
"Add profile..." = "프로필 추가...";
|
||||||
|
"Add Quality Profile" = "품질 프로필 추가";
|
||||||
|
"Add to %@" = "%@에 추가";
|
||||||
|
"Add to Favorites" = "즐겨찾기에 추가";
|
||||||
|
"Add to Playlist" = "플레이리스트에 추가";
|
||||||
|
"Add to Playlist..." = "플레이리스트에 추가...";
|
||||||
|
"Advanced" = "고급";
|
||||||
|
|
||||||
|
/* Trending category, section containing all kinds of videos */
|
||||||
|
"All" = "전체";
|
||||||
|
"Always use AVPlayer for live videos" = "라이브 동영상에 항상 AVPlayer 사용";
|
||||||
|
"Anonymous" = "익명";
|
||||||
|
|
||||||
|
/* Video date filter in search
|
||||||
|
Video duration filter in search */
|
||||||
|
"Any" = "모두";
|
||||||
|
"Apply to all" = "모두 적용";
|
||||||
|
"Are you sure you want to clear search history?" = "검색 기록을 삭제하시겠습니까?";
|
||||||
|
"Are you sure you want to delete playlist?" = "플레이리스트를 삭제하시겠습니까?";
|
||||||
|
"Are you sure you want to restore default quality profiles?" = "기본 품질 프로필로 복구하시겠습니까?";
|
||||||
|
"Are you sure you want to unsubscribe from %@?" = "%@을/를 구독 해제하시겠습니까?";
|
||||||
|
"Autoplaying Next" = "다음으로 자동재생";
|
||||||
|
"Backend" = "백엔드";
|
||||||
|
"Badge" = "배지";
|
||||||
|
"Battery" = "배터리";
|
||||||
|
"Cellular" = "셀룰러";
|
||||||
|
" subscribers" = " 구독자";
|
||||||
|
"%@ Channel" = "%@ 채널";
|
||||||
|
"%lld videos" = "%lld 동영상";
|
||||||
|
"Are you sure you want to clear history of watched videos?" = "동영상 시청 기록을 삭제하시겠습니까?";
|
||||||
|
"Add Account" = "계정 추가";
|
||||||
|
"Add Account..." = "계정 추가...";
|
||||||
|
"Automatic" = "자동";
|
||||||
|
"Close" = "닫기";
|
||||||
@@ -4103,7 +4103,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
INFOPLIST_KEY_CFBundleDisplayName = "Open in Yattee";
|
||||||
@@ -4134,7 +4134,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||||
@@ -4165,7 +4165,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
@@ -4185,7 +4185,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||||
@@ -4349,7 +4349,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "iOS/Yattee (iOS).entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
"DEBUG=1",
|
"DEBUG=1",
|
||||||
@@ -4402,7 +4402,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
GCC_PREPROCESSOR_DEFINITIONS = "GLES_SILENCE_DEPRECATION=1";
|
||||||
@@ -4454,7 +4454,7 @@
|
|||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
ENABLE_HARDENED_RUNTIME = YES;
|
ENABLE_HARDENED_RUNTIME = YES;
|
||||||
@@ -4493,7 +4493,7 @@
|
|||||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=macosx*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_APP_SANDBOX = YES;
|
ENABLE_APP_SANDBOX = YES;
|
||||||
@@ -4528,7 +4528,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4552,7 +4552,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4578,7 +4578,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4603,7 +4603,7 @@
|
|||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEAD_CODE_STRIPPING = YES;
|
DEAD_CODE_STRIPPING = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
@@ -4629,7 +4629,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -4669,7 +4669,7 @@
|
|||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "iPhone Distribution";
|
||||||
CODE_SIGN_STYLE = Manual;
|
CODE_SIGN_STYLE = Manual;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
DEVELOPMENT_ASSET_PATHS = "";
|
DEVELOPMENT_ASSET_PATHS = "";
|
||||||
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -4710,7 +4710,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@@ -4734,7 +4734,7 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 185;
|
CURRENT_PROJECT_VERSION = 186;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"location" : "https://github.com/hyperoslo/Cache.git",
|
"location" : "https://github.com/hyperoslo/Cache.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"branch" : "master",
|
"branch" : "master",
|
||||||
"revision" : "f44a8f6b5ec27730198725ccc542fef0d1cc6b3d"
|
"revision" : "fd97438a85d44aa4f7186145cdba90e3c36f1c94"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,8 +87,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/ashleymills/Reachability.swift",
|
"location" : "https://github.com/ashleymills/Reachability.swift",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "57da4b1270cab7c2228919eabc0e4e1bf93e48ea",
|
"revision" : "7cbd73f46a7dfaeca079e18df7324c6de6d1834a",
|
||||||
"version" : "5.2.2"
|
"version" : "5.2.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -105,8 +105,8 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/SDWebImage/SDWebImage",
|
"location" : "https://github.com/SDWebImage/SDWebImage",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "5642d1ffe3dbe628592443bd14154e31929727b4",
|
"revision" : "b8523c1642f3c142b06dd98443ea7c48343a4dfd",
|
||||||
"version" : "5.19.2"
|
"version" : "5.19.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user