Compare commits

..

65 Commits

Author SHA1 Message Date
Arkadiusz Fal
16b25df3bc Fix method and property access 2023-06-09 18:03:42 +02:00
Arkadiusz Fal
a66e59a282 Bump build number to 155 2023-06-09 17:50:56 +02:00
Arkadiusz Fal
aeeedf3d63 Update CHANGELOG 2023-06-09 17:50:30 +02:00
Arkadiusz Fal
3d35a60c7a Performance improvements 2023-06-09 17:46:31 +02:00
Arkadiusz Fal
8ffdd4d51f Fix crash 2023-06-09 17:45:51 +02:00
Arkadiusz Fal
f871c7aaf5 Bump build number to 154 2023-06-08 12:27:26 +02:00
Arkadiusz Fal
32af2b385b Update CHANGELOG 2023-06-08 12:25:12 +02:00
Arkadiusz Fal
f78545baf9 Fix issue with AVPlayer rate restore 2023-06-08 12:25:12 +02:00
Arkadiusz Fal
d95bcc4065 Fix #492 2023-06-08 12:17:16 +02:00
Arkadiusz Fal
1efd9e2b90 Fix hiding overlays 2023-06-08 12:11:44 +02:00
Arkadiusz Fal
59e5fcb37d Fix properties access 2023-06-07 23:51:17 +02:00
Arkadiusz Fal
47d68d7948 Update packages 2023-06-07 23:51:09 +02:00
Arkadiusz Fal
3e22c1ebde Bump build number to 153 2023-06-07 23:38:24 +02:00
Arkadiusz Fal
ede5d85693 Update CHANGELOG 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
4d5390ce2d Fixed video player background 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
91290d4736 Fix #486 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
7e7225c59f Fix build on tvOS 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
7b9bbd8974 Fix #473 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
c36dc67a72 Fix issue with AVPlayer rate 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
71b25afa28 Use method to find instance 2023-06-07 23:37:28 +02:00
Arkadiusz Fal
2c5eb18bc9 Fix crash 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
56c2e552f7 Revert "Add loading status to vertical cells"
This reverts commit c6cff4dee4.
2023-06-07 23:37:27 +02:00
Arkadiusz Fal
5ee869c02c Fix lists padding in favorites 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
6f91eedf4c Update watch time on close item 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
0328656a44 Fix AVPlayer layout 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
8d11a92f97 Fix switching to AVPlayer in fullscreen 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
f3a8a0977c Do not reload channels cache if not needed 2023-06-07 23:37:27 +02:00
Arkadiusz Fal
3bbc2df431 Merge pull request #484 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-06-07 21:35:50 +02:00
maboroshin
5faa5f0a48 Translated using Weblate (Japanese)
Currently translated at 98.8% (522 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-07 11:52:11 +02:00
maboroshin
ca8586ce7f Translated using Weblate (Japanese)
Currently translated at 98.6% (521 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-06-05 13:51:46 +02:00
joaooliva
8efa68e65c Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-06-03 18:49:46 +02:00
Ophiushi
04c15ed59a Translated using Weblate (French)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-06-03 18:49:46 +02:00
Bharathi
75772533fd Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-06-03 18:49:45 +02:00
mere
3ca0f4cf2d Translated using Weblate (Romanian)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-06-01 08:48:29 +02:00
Arkadiusz Fal
5f745afecb Translated using Weblate (Polish)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pl/
2023-05-30 10:28:57 +02:00
Anonymous
e8c8c6b5b4 Translated using Weblate (English)
Currently translated at 100.0% (528 of 528 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:28:57 +02:00
Arkadiusz Fal
0585120bd8 Translated using Weblate (English)
Currently translated at 100.0% (527 of 527 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:26:30 +02:00
Anonymous
1f43e11b8e Translated using Weblate (English)
Currently translated at 100.0% (527 of 527 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/en/
2023-05-30 10:26:26 +02:00
Arkadiusz Fal
792a2c1c6c Fix method access 2023-05-29 16:50:40 +02:00
Arkadiusz Fal
12ef9e091c Bump build number to 152 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
101ecb6892 Update CHANGELOG 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
be2e4acedd Simplify channels view 2023-05-29 16:42:20 +02:00
Arkadiusz Fal
c6cff4dee4 Add loading status to vertical cells 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
eaeaa45422 Fix #479 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
6ddf1113bf Fix fullscreen exit 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
15f3e11a78 Simplify playlists view 2023-05-29 16:42:19 +02:00
Arkadiusz Fal
713570dfd6 Fix navigatable in favorite item view 2023-05-29 16:13:13 +02:00
Arkadiusz Fal
50eb0be1d7 Change subscriptions picker labels to text 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
3adbea1897 Fix #443 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
80a644eb7a Fix Invidious trending categories 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
b1637c5ef1 Fix buttons on tvOS 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
166482601d Fix home settings buttons colors 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
1d61dec8eb Revert "Minor improvements"
This reverts commit 24241d3485.
2023-05-29 16:13:12 +02:00
Arkadiusz Fal
ca7195caba Add remove item to search recents items 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
947f216fac Fix closing video on error 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
a054d343a9 Fix music mode in AVPlayer 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
48263ae7db Add tap on search to focus search field on iOS 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
562df2d9ba Add advanced setting "Show video context menu options to force selected backend" 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
e5f137a2d2 Add setting "Keep channels with unwatched videos on top of subscriptions list" 2023-05-29 16:13:12 +02:00
Arkadiusz Fal
6856506834 Merge pull request #478 from weblate/weblate-yattee-localizable-strings
Translations update from Hosted Weblate
2023-05-29 16:12:54 +02:00
maboroshin
a604382a3d Translated using Weblate (Japanese)
Currently translated at 99.4% (521 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ja/
2023-05-29 09:48:10 +02:00
Bharathi
0bbbf0907d Translated using Weblate (German)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/de/
2023-05-29 09:48:09 +02:00
mere
921987be5d Translated using Weblate (Romanian)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/ro/
2023-05-27 06:50:18 +02:00
joaooliva
b47156ba5e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/pt_BR/
2023-05-27 06:50:18 +02:00
Ophiushi
5ab06d0e09 Translated using Weblate (French)
Currently translated at 100.0% (524 of 524 strings)

Translation: Yattee/Localizable.strings
Translate-URL: https://hosted.weblate.org/projects/yattee/localizable-strings/fr/
2023-05-27 06:50:17 +02:00
46 changed files with 588 additions and 234 deletions

View File

@@ -1,4 +1,33 @@
## Build 151 ## Build 155
* Fixed reported crashes
* Minor performance improvements
## Previous Builds
* Fixed issue where AVPlayer would pause playing on exiting fullscreen
* Fixed issue with empty button appearing on subscriptions list on tvOS
* Fixed issue with AVPlayer not always using full width
* Fixed issue with layout when switching backend while in fullscreen
* Fixed issue with updating watched time on closing video
* Fixed issue with adjusting AVPlayer playback rate when using system controls
* Fixed issue where comments would load indefinitely
* Improved Home buttons layout on tvOS
* Reverted change to placeholders as were causing issues to properly display loading status, will be revisited in future
* Fixed performance issue with swiping back to subscribed channels list
* Fixed reported crashes
* Tapping second time on search tab button focuses the input field and selects entered query text (iOS)
* Added Browsing setting "Keep channels with unwatched videos on top of subscriptions list"
* Improved buttons and layout on tvOS
* Fixed issue with trending categories (Invidious) not working when using non-English language
* Fixed issue with search query suggestions not being displayed properly in some languages
* Changed subscriptions page picker label from icon to text
* Views will display information if there is no videos to show instead of always showing placeholders
* Fixed AVPlayer issue with music mode playing video track
* Added remove context menu option for all types of recent items in Search
* Added advanced setting "Show video context menu options to force selected backend"
* Fixed reported crashes
* Improved Home * Improved Home
- Added menu with view options on iOS and toolbar buttons on macOS/tvOS - Added menu with view options on iOS and toolbar buttons on macOS/tvOS
- Added Home Settings - Added Home Settings
@@ -20,9 +49,7 @@
* Fixed issue with playlists view showing duplicated buttons when "Show cache status" is enabled * Fixed issue with playlists view showing duplicated buttons when "Show cache status" is enabled
* Fixed issue where navigating to channel from list view in Playlists and Search would immediately go back * Fixed issue where navigating to channel from list view in Playlists and Search would immediately go back
* Fixed issue where first URL would fail to open * Fixed issue where first URL would fail to open
* Other minor fixes and improvements
## Previous Builds
* Added support for AVPlayer native system controls on iOS and macOS * Added support for AVPlayer native system controls on iOS and macOS
- Use system features such as AirPlay, subtitles switching (Piped with HLS), text detection and copy and more - Use system features such as AirPlay, subtitles switching (Piped with HLS), text detection and copy and more
- Added Controls setting: "Use system controls with AVPlayer" - Added Controls setting: "Use system controls with AVPlayer"

View File

@@ -0,0 +1,14 @@
import Foundation
extension String {
var replacingHTMLEntities: String {
do {
return try NSAttributedString(data: Data(utf8), options: [
.documentType: NSAttributedString.DocumentType.html,
.characterEncoding: String.Encoding.utf8.rawValue
], documentAttributes: nil).string
} catch {
return self
}
}
}

View File

@@ -53,7 +53,7 @@ struct Account: Defaults.Serializable, Hashable, Identifiable {
} }
var instance: Instance! { var instance: Instance! {
Defaults[.instances].first { $0.id == instanceID } ?? Instance(app: app ?? .invidious, name: urlString, apiURLString: urlString) InstancesModel.shared.find(instanceID) ?? Instance(app: app ?? .invidious, name: urlString, apiURLString: urlString)
} }
var isPublic: Bool { var isPublic: Bool {

View File

@@ -79,7 +79,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in configureTransformer(pathPattern("search/suggestions"), requestMethods: [.get]) { (content: Entity<JSON>) -> [String] in
if let suggestions = content.json.dictionaryValue["suggestions"] { if let suggestions = content.json.dictionaryValue["suggestions"] {
return suggestions.arrayValue.map(String.init) return suggestions.arrayValue.map { $0.stringValue.replacingHTMLEntities }
} }
return [] return []
@@ -236,7 +236,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
func trending(country: Country, category: TrendingCategory?) -> Resource { func trending(country: Country, category: TrendingCategory?) -> Resource {
resource(baseURL: account.url, path: "\(Self.basePath)/trending") resource(baseURL: account.url, path: "\(Self.basePath)/trending")
.withParam("type", category?.name) .withParam("type", category?.type)
.withParam("region", country.rawValue) .withParam("region", country.rawValue)
} }

View File

@@ -23,6 +23,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
@Published var error: RequestError? @Published var error: RequestError?
var accounts: AccountsModel { .shared } var accounts: AccountsModel { .shared }
var unwatchedFeedCount: UnwatchedFeedCountModel { .shared }
var resource: Resource? { var resource: Resource? {
accounts.api.subscriptions accounts.api.subscriptions
@@ -32,6 +33,19 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
channels.sorted { $0.name.lowercased() < $1.name.lowercased() } channels.sorted { $0.name.lowercased() < $1.name.lowercased() }
} }
var allByUnwatchedCount: [Channel] {
if let account = accounts.current {
return all.sorted { c1, c2 in
let c1HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c1.id] ?? -1) > 0
let c2HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c2.id] ?? -1) > 0
let nameIncreasing = c1.name.lowercased() < c2.name.lowercased()
return c1HasUnwatched ? (c2HasUnwatched ? nameIncreasing : true) : (c2HasUnwatched ? false : nameIncreasing)
}
}
return all
}
func subscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) { func subscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) {
accounts.api.subscribe(channelID) { accounts.api.subscribe(channelID) {
self.scheduleLoad(onSuccess: onSuccess) self.scheduleLoad(onSuccess: onSuccess)
@@ -54,15 +68,14 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
return return
} }
loadCachedChannels(account)
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { return } guard let self else { return }
let request = force ? self.resource?.load() : self.resource?.loadIfNeeded() let request = force ? self.resource?.load() : self.resource?.loadIfNeeded()
guard request != nil else { return }
if request != nil { self.loadCachedChannels(account)
self.isLoading = true
} self.isLoading = true
request? request?
.onCompletion { [weak self] _ in .onCompletion { [weak self] _ in

View File

@@ -69,6 +69,7 @@ final class CommentsModel: ObservableObject {
} }
func loadNextPage() { func loadNextPage() {
guard nextPageAvailable else { return }
load(page: nextPage) load(page: nextPage)
} }

View File

@@ -46,11 +46,11 @@ extension PlayerModel {
} }
} }
func updateWatch(finished: Bool = false) { func updateWatch(finished: Bool = false, time: CMTime? = nil) {
guard let currentVideo, saveHistory else { return } guard let currentVideo, saveHistory else { return }
let id = currentVideo.videoID let id = currentVideo.videoID
let time = backend.currentTime let time = time ?? backend.currentTime
let seconds = time?.seconds ?? 0 let seconds = time?.seconds ?? 0
let duration = playerTime.duration.seconds let duration = playerTime.duration.seconds
if seconds < 3 { if seconds < 3 {
@@ -63,7 +63,7 @@ extension PlayerModel {
let results = try? backgroundContext.fetch(watchFetchRequest) let results = try? backgroundContext.fetch(watchFetchRequest)
backgroundContext.perform { [weak self] in backgroundContext.perform { [weak self] in
guard let self, finished || self.backend.isPlaying else { guard let self, finished || time != nil || self.backend.isPlaying else {
return return
} }

View File

@@ -63,7 +63,9 @@ final class NavigationModel: ObservableObject {
} }
} }
@Published var tabSelection: TabSelection! @Published var tabSelection: TabSelection! { didSet {
if oldValue == tabSelection { multipleTapHandler() }
}}
@Published var presentingAddToPlaylist = false @Published var presentingAddToPlaylist = false
@Published var videoToAddToPlaylist: Video! @Published var videoToAddToPlaylist: Video!
@@ -295,6 +297,15 @@ final class NavigationModel: ObservableObject {
channelPresentedInSheet = channel channelPresentedInSheet = channel
presentingChannelSheet = true presentingChannelSheet = true
} }
func multipleTapHandler() {
switch tabSelection {
case .search:
self.search.focused = true
default:
print("not implemented")
}
}
} }
typealias TabSelection = NavigationModel.TabSelection typealias TabSelection = NavigationModel.TabSelection

View File

@@ -333,6 +333,10 @@ final class AVPlayerBackend: PlayerBackend {
return return
} }
if self.model.musicMode {
self.startMusicMode()
}
if !preservingTime, if !preservingTime,
!self.model.transitioningToPiP, !self.model.transitioningToPiP,
let segment = self.model.sponsorBlock.segments.first, let segment = self.model.sponsorBlock.segments.first,
@@ -349,9 +353,11 @@ final class AVPlayerBackend: PlayerBackend {
} }
self.model.lastSkipped = segment self.model.lastSkipped = segment
self.model.handleOnPlayStream(stream)
self.model.play() self.model.play()
} }
} else { } else {
self.model.handleOnPlayStream(stream)
self.model.play() self.model.play()
} }
} }
@@ -482,7 +488,9 @@ final class AVPlayerBackend: PlayerBackend {
if self.model.activeBackend == .appleAVPlayer, if self.model.activeBackend == .appleAVPlayer,
self.isAutoplaying(playerItem) self.isAutoplaying(playerItem)
{ {
self.model.updateAspectRatio() if self.model.aspectRatio != self.aspectRatio {
self.model.updateAspectRatio()
}
if self.startPictureInPictureOnPlay, if self.startPictureInPictureOnPlay,
let controller = self.model.pipController, let controller = self.model.pipController,
@@ -604,7 +612,7 @@ final class AVPlayerBackend: PlayerBackend {
} }
self.timeObserverThrottle.execute { self.timeObserverThrottle.execute {
self.model.updateWatch() self.model.updateWatch(time: self.currentTime)
} }
} }
} }
@@ -634,8 +642,18 @@ final class AVPlayerBackend: PlayerBackend {
if player.timeControlStatus == .playing { if player.timeControlStatus == .playing {
self.model.objectWillChange.send() self.model.objectWillChange.send()
if player.rate != Float(self.model.currentRate) {
player.rate = Float(self.model.currentRate) if let rate = self.model.rateToRestore, player.rate != rate {
player.rate = rate
self.model.rateToRestore = nil
}
if player.rate > 0, player.rate != Float(self.model.currentRate) {
if self.model.avPlayerUsesSystemControls {
self.model.currentRate = Double(player.rate)
} else {
player.rate = Float(self.model.currentRate)
}
} }
} }
@@ -648,7 +666,7 @@ final class AVPlayerBackend: PlayerBackend {
#endif #endif
self.timeObserverThrottle.execute { self.timeObserverThrottle.execute {
self.model.updateWatch() self.model.updateWatch(time: self.currentTime)
} }
} }
} }
@@ -704,6 +722,17 @@ final class AVPlayerBackend: PlayerBackend {
} else { } else {
stopMusicMode() stopMusicMode()
} }
#if os(iOS)
if model.playingFullScreen {
ControlOverlaysModel.shared.hide()
model.navigation.presentingPlaybackSettings = false
model.onPlayStream.append { _ in
self.controller.enterFullScreen(animated: true)
}
}
#endif
} }
var isStartingPiP: Bool { var isStartingPiP: Bool {

View File

@@ -267,9 +267,11 @@ final class MPVBackend: PlayerBackend {
self.model.lastSkipped = segment self.model.lastSkipped = segment
self.play() self.play()
self.model.handleOnPlayStream(stream)
} }
} else { } else {
self.play() self.play()
self.model.handleOnPlayStream(stream)
} }
} }
} }
@@ -423,7 +425,7 @@ final class MPVBackend: PlayerBackend {
} }
timeObserverThrottle.execute { timeObserverThrottle.execute {
self.model.updateWatch() self.model.updateWatch(time: self.currentTime)
} }
} }

View File

@@ -55,6 +55,7 @@ final class PlayerModel: ObservableObject {
@Published var presentingPlayer = false { didSet { handlePresentationChange() } } @Published var presentingPlayer = false { didSet { handlePresentationChange() } }
@Published var activeBackend = PlayerBackendType.mpv @Published var activeBackend = PlayerBackendType.mpv
@Published var forceBackendOnPlay: PlayerBackendType?
var avPlayerBackend = AVPlayerBackend() var avPlayerBackend = AVPlayerBackend()
var mpvBackend = MPVBackend() var mpvBackend = MPVBackend()
@@ -181,6 +182,8 @@ final class PlayerModel: ObservableObject {
private var currentArtwork: MPMediaItemArtwork? private var currentArtwork: MPMediaItemArtwork?
var onPresentPlayer = [() -> Void]() var onPresentPlayer = [() -> Void]()
var onPlayStream = [(Stream) -> Void]()
var rateToRestore: Float?
private var remoteCommandCenterConfigured = false private var remoteCommandCenterConfigured = false
init() { init() {
@@ -235,16 +238,11 @@ final class PlayerModel: ObservableObject {
} }
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.exitFullScreen(showControls: false) Delay.by(0.3) {
self?.exitFullScreen(showControls: false)
}
} }
#if os(iOS)
if Defaults[.lockPortraitWhenBrowsing] {
Orientation.lockOrientation(.portrait, andRotateTo: .portrait)
} else {
Orientation.lockOrientation(.allButUpsideDown)
}
#endif
#if os(macOS) #if os(macOS)
Windows.player.hide() Windows.player.hide()
#endif #endif
@@ -387,7 +385,7 @@ final class PlayerModel: ObservableObject {
if !upgrading, !video.isLocal { if !upgrading, !video.isLocal {
resetSegments() resetSegments()
DispatchQueue.global(qos: .userInitiated).async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.sponsorBlock.loadSegments( self?.sponsorBlock.loadSegments(
videoID: video.videoID, videoID: video.videoID,
categories: Defaults[.sponsorBlockCategories] categories: Defaults[.sponsorBlockCategories]
@@ -407,19 +405,19 @@ final class PlayerModel: ObservableObject {
resetSegments() resetSegments()
} }
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.25) { (withBackend ?? backend).playStream(
(withBackend ?? self.backend).playStream( stream,
stream, of: video,
of: video, preservingTime: preservingTime,
preservingTime: preservingTime, upgrading: upgrading
upgrading: upgrading )
)
DispatchQueue.main.async {
self.forceBackendOnPlay = nil
} }
if !upgrading { if !upgrading {
DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.5) { updateCurrentArtwork()
self.updateCurrentArtwork()
}
} }
} }
@@ -452,7 +450,7 @@ final class PlayerModel: ObservableObject {
return return
} }
if let backend = (live && forceAVPlayerForLiveStreams) ? PlayerBackendType.appleAVPlayer : qualityProfile?.backend, if let backend = forceBackendOnPlay ?? ((live && forceAVPlayerForLiveStreams) ? PlayerBackendType.appleAVPlayer : qualityProfile?.backend),
backend != activeBackend, backend != activeBackend,
backend == .appleAVPlayer || !(avPlayerBackend.startPictureInPictureOnPlay || playingInPictureInPicture) backend == .appleAVPlayer || !(avPlayerBackend.startPictureInPictureOnPlay || playingInPictureInPicture)
{ {
@@ -545,6 +543,9 @@ final class PlayerModel: ObservableObject {
if !self.backend.canPlayAtRate(currentRate) { if !self.backend.canPlayAtRate(currentRate) {
currentRate = self.backend.suggestedPlaybackRates.last { $0 < currentRate } ?? 1.0 currentRate = self.backend.suggestedPlaybackRates.last { $0 < currentRate } ?? 1.0
} }
self.rateToRestore = Float(currentRate)
self.backend.didChangeTo() self.backend.didChangeTo()
if wasPlaying { if wasPlaying {
@@ -621,17 +622,20 @@ final class PlayerModel: ObservableObject {
func closeCurrentItem(finished: Bool = false) { func closeCurrentItem(finished: Bool = false) {
pause() pause()
videoBeingOpened = nil videoBeingOpened = nil
advancing = false
forceBackendOnPlay = nil
closing = true closing = true
controls.presentingControls = false controls.presentingControls = false
self.prepareCurrentItemForHistory(finished: finished)
self.hide() self.hide()
Delay.by(0.8) { [weak self] in Delay.by(0.8) { [weak self] in
guard let self else { return } guard let self else { return }
self.closePiP() self.closePiP()
self.prepareCurrentItemForHistory(finished: finished)
withAnimation { withAnimation {
self.currentItem = nil self.currentItem = nil
} }
@@ -1080,7 +1084,9 @@ final class PlayerModel: ObservableObject {
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self else { return } guard let self else { return }
self.aspectRatio = self.backend.aspectRatio withAnimation {
self.aspectRatio = self.backend.aspectRatio
}
} }
#endif #endif
} }
@@ -1095,4 +1101,11 @@ final class PlayerModel: ObservableObject {
guard let videoWidth = backend?.videoWidth, let videoHeight = backend?.videoHeight else { return "unknown" } guard let videoWidth = backend?.videoWidth, let videoHeight = backend?.videoHeight else { return "unknown" }
return "\(String(format: "%.2f", videoWidth))\u{d7}\(String(format: "%.2f", videoHeight))" return "\(String(format: "%.2f", videoWidth))\u{d7}\(String(format: "%.2f", videoHeight))"
} }
func handleOnPlayStream(_ stream: Stream) {
backend.setRate(currentRate)
onPlayStream.forEach { $0(stream) }
onPlayStream.removeAll()
}
} }

View File

@@ -257,7 +257,7 @@ extension PlayerModel {
if let video = currentVideo, !historyVideos.contains(where: { $0 == video }) { if let video = currentVideo, !historyVideos.contains(where: { $0 == video }) {
historyVideos.append(video) historyVideos.append(video)
} }
updateWatch(finished: finished) updateWatch(finished: finished, time: backend.currentTime)
} }
if let video = currentItem.video, if let video = currentItem.video,
@@ -364,10 +364,7 @@ extension PlayerModel {
message: Text(message), message: Text(message),
primaryButton: .cancel { [weak self] in primaryButton: .cancel { [weak self] in
guard let self else { return } guard let self else { return }
self.advancing = false self.closeCurrentItem()
self.videoBeingOpened = nil
self.currentItem = nil
self.hide()
}, },
secondaryButton: retryButton secondaryButton: retryButton
) )

View File

@@ -39,7 +39,9 @@ final class RecentsModel: ObservableObject {
func addQuery(_ query: String) { func addQuery(_ query: String) {
if !query.isEmpty { if !query.isEmpty {
NavigationModel.shared.tabSelection = .search if NavigationModel.shared.tabSelection != .search {
NavigationModel.shared.tabSelection = .search
}
add(.init(from: query)) add(.init(from: query))
} }
} }

View File

@@ -16,9 +16,23 @@ final class SearchModel: ObservableObject {
@Published var querySuggestions = [String]() @Published var querySuggestions = [String]()
private var suggestionsDebouncer = Debouncer(.milliseconds(200)) private var suggestionsDebouncer = Debouncer(.milliseconds(200))
@Published var focused = false
var accounts: AccountsModel { .shared } var accounts: AccountsModel { .shared }
private var resource: Resource! private var resource: Resource!
init() {
#if os(iOS)
addKeyboardDidHideNotificationObserver()
#endif
}
deinit {
#if os(iOS)
removeKeyboardDidHideNotificationObserver()
#endif
}
var isLoading: Bool { var isLoading: Bool {
resource?.isLoading ?? false resource?.isLoading ?? false
} }
@@ -136,4 +150,18 @@ final class SearchModel: ObservableObject {
} }
} }
} }
#if os(iOS)
private func addKeyboardDidHideNotificationObserver() {
NotificationCenter.default.addObserver(self, selector: #selector(onKeyboardDidHide), name: UIResponder.keyboardDidHideNotification, object: nil)
}
@objc func onKeyboardDidHide() {
focused = false
}
private func removeKeyboardDidHideNotificationObserver() {
NotificationCenter.default.removeObserver(self, name: UIResponder.keyboardDidHideNotification, object: nil)
}
#endif
} }

View File

@@ -75,7 +75,7 @@ final class SponsorBlockAPI: ObservableObject {
self.videoID = videoID self.videoID = videoID
DispatchQueue.global(qos: .userInitiated).async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.requestSegments(categories: categories, completionHandler: completionHandler) self?.requestSegments(categories: categories, completionHandler: completionHandler)
} }
} }

View File

@@ -41,4 +41,8 @@ enum TrendingCategory: String, CaseIterable, Identifiable, Defaults.Serializable
var controlLabel: String { var controlLabel: String {
id == "default" ? "All".localized() : title id == "default" ? "All".localized() : title
} }
var type: String {
rawValue.capitalized
}
} }

View File

@@ -3,7 +3,7 @@ import Siesta
import SwiftUI import SwiftUI
struct ChannelPlaylistView: View { struct ChannelPlaylistView: View {
var playlist: ChannelPlaylist? var playlist: ChannelPlaylist
var showCloseButton = false var showCloseButton = false
@StateObject private var store = Store<ChannelPlaylist>() @StateObject private var store = Store<ChannelPlaylist>()
@@ -19,15 +19,7 @@ struct ChannelPlaylistView: View {
ContentItem.array(of: store.item?.videos ?? []) ContentItem.array(of: store.item?.videos ?? [])
} }
private var presentedPlaylist: ChannelPlaylist? {
playlist ?? recents.presentedPlaylist
}
private var resource: Resource? { private var resource: Resource? {
guard let playlist = presentedPlaylist else {
return nil
}
let resource = accounts.api.channelPlaylist(playlist.id) let resource = accounts.api.channelPlaylist(playlist.id)
resource?.addObserver(store) resource?.addObserver(store)
@@ -38,21 +30,19 @@ struct ChannelPlaylistView: View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
#if os(tvOS) #if os(tvOS)
HStack { HStack {
if let playlist = presentedPlaylist { ThumbnailView(url: store.item?.thumbnailURL ?? playlist.thumbnailURL)
ThumbnailView(url: store.item?.thumbnailURL ?? playlist.thumbnailURL) .frame(width: 140, height: 80)
.frame(width: 140, height: 80) .clipShape(RoundedRectangle(cornerRadius: 2))
.clipShape(RoundedRectangle(cornerRadius: 2))
Text(playlist.title) Text(playlist.title)
.font(.headline) .font(.headline)
.frame(alignment: .leading) .frame(alignment: .leading)
.lineLimit(1) .lineLimit(1)
Spacer() Spacer()
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title))) FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
.labelStyle(.iconOnly) .labelStyle(.iconOnly)
}
playButtons playButtons
.labelStyle(.iconOnly) .labelStyle(.iconOnly)
@@ -63,9 +53,7 @@ struct ChannelPlaylistView: View {
} }
.environment(\.listingStyle, channelPlaylistListingStyle) .environment(\.listingStyle, channelPlaylistListingStyle)
.onAppear { .onAppear {
if let playlist = presentedPlaylist, if let cache = ChannelPlaylistsCacheModel.shared.retrievePlaylist(playlist) {
let cache = ChannelPlaylistsCacheModel.shared.retrievePlaylist(playlist)
{
store.replace(cache) store.replace(cache)
} }
resource?.loadIfNeeded()?.onSuccess { response in resource?.loadIfNeeded()?.onSuccess { response in
@@ -111,14 +99,12 @@ struct ChannelPlaylistView: View {
} }
} }
} }
.navigationTitle(label) .navigationTitle(playlist.title)
#endif #endif
} }
@ViewBuilder private var favoriteButton: some View { @ViewBuilder private var favoriteButton: some View {
if let playlist = presentedPlaylist { FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
}
} }
#if os(iOS) #if os(iOS)
@@ -140,13 +126,13 @@ struct ChannelPlaylistView: View {
} }
} label: { } label: {
HStack(spacing: 12) { HStack(spacing: 12) {
if let url = store.item?.thumbnailURL ?? playlist?.thumbnailURL { if let url = store.item?.thumbnailURL ?? playlist.thumbnailURL {
ThumbnailView(url: url) ThumbnailView(url: url)
.frame(width: 60, height: 30) .frame(width: 60, height: 30)
.clipShape(RoundedRectangle(cornerRadius: 2)) .clipShape(RoundedRectangle(cornerRadius: 2))
} }
Text(label) Text(playlist.title)
.font(.headline) .font(.headline)
.foregroundColor(.primary) .foregroundColor(.primary)
@@ -160,10 +146,6 @@ struct ChannelPlaylistView: View {
} }
#endif #endif
private var label: String {
presentedPlaylist?.title ?? ""
}
private var playlistButtonsPlacement: ToolbarItemPlacement { private var playlistButtonsPlacement: ToolbarItemPlacement {
#if os(iOS) #if os(iOS)
.navigationBarTrailing .navigationBarTrailing

View File

@@ -4,7 +4,7 @@ import Siesta
import SwiftUI import SwiftUI
struct ChannelVideosView: View { struct ChannelVideosView: View {
var channel: Channel? var channel: Channel
var showCloseButton = false var showCloseButton = false
var inNavigationView = true var inNavigationView = true
@@ -32,7 +32,7 @@ struct ChannelVideosView: View {
@Default(.expandChannelDescription) private var expandChannelDescription @Default(.expandChannelDescription) private var expandChannelDescription
var presentedChannel: Channel? { var presentedChannel: Channel? {
store.item?.channel ?? channel ?? recents.presentedChannel store.item?.channel ?? channel
} }
var contentItems: [ContentItem] { var contentItems: [ContentItem] {
@@ -165,10 +165,7 @@ struct ChannelVideosView: View {
.onAppear { .onAppear {
descriptionExpanded = expandChannelDescription descriptionExpanded = expandChannelDescription
if let channel, if let cache = ChannelsCacheModel.shared.retrieve(channel.cacheKey), store.item.isNil {
let cache = ChannelsCacheModel.shared.retrieve(channel.cacheKey),
store.item.isNil
{
store.replace(cache) store.replace(cache)
} }

View File

@@ -51,6 +51,7 @@ extension Defaults.Keys {
static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone) static let lockPortraitWhenBrowsing = Key<Bool>("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone)
#endif #endif
static let showUnwatchedFeedBadges = Key<Bool>("showUnwatchedFeedBadges", default: false) static let showUnwatchedFeedBadges = Key<Bool>("showUnwatchedFeedBadges", default: false)
static let keepChannelsWithUnwatchedFeedOnTop = Key<Bool>("keepChannelsWithUnwatchedFeedOnTop", default: true)
static let showToggleWatchedStatusButton = Key<Bool>("showToggleWatchedStatusButton", default: false) static let showToggleWatchedStatusButton = Key<Bool>("showToggleWatchedStatusButton", default: false)
static let expandChannelDescription = Key<Bool>("expandChannelDescription", default: false) static let expandChannelDescription = Key<Bool>("expandChannelDescription", default: false)
static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false) static let channelOnThumbnail = Key<Bool>("channelOnThumbnail", default: false)
@@ -197,6 +198,7 @@ extension Defaults.Keys {
#endif #endif
static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false) static let showMPVPlaybackStats = Key<Bool>("showMPVPlaybackStats", default: false)
static let showPlayNowInBackendContextMenu = Key<Bool>("showPlayNowInBackendContextMenu", default: false)
#if os(macOS) #if os(macOS)
static let playerDetailsPageButtonLabelStyleDefault = ButtonLabelStyle.iconAndText static let playerDetailsPageButtonLabelStyleDefault = ButtonLabelStyle.iconAndText

View File

@@ -23,6 +23,7 @@ struct FavoriteItemView: View {
@Default(.hideShorts) private var hideShorts @Default(.hideShorts) private var hideShorts
@Default(.hideWatched) private var hideWatched @Default(.hideWatched) private var hideWatched
@Default(.widgetsSettings) private var widgetsSettings @Default(.widgetsSettings) private var widgetsSettings
@Default(.visibleSections) private var visibleSections
init(item: FavoriteItem) { init(item: FavoriteItem) {
self.item = item self.item = item
@@ -72,7 +73,7 @@ struct FavoriteItemView: View {
#if os(tvOS) #if os(tvOS)
.padding(.leading, 40) .padding(.leading, 40)
#else #else
.padding(.leading, 15) .padding(.horizontal, 15)
#endif #endif
} }
} }
@@ -203,7 +204,8 @@ struct FavoriteItemView: View {
} }
func watch(_ item: ContentItem) -> Watch? { func watch(_ item: ContentItem) -> Watch? {
watches.first { $0.videoID == item.video.videoID } guard let id = item.video?.videoID else { return nil }
return watches.first { $0.videoID == id }
} }
var widgetListingStyle: WidgetListingStyle { var widgetListingStyle: WidgetListingStyle {
@@ -285,7 +287,18 @@ struct FavoriteItemView: View {
} }
var navigatableItem: Bool { var navigatableItem: Bool {
item.section != .history switch item.section {
case .history:
return false
case .trending:
return visibleSections.contains(.trending)
case .subscriptions:
return visibleSections.contains(.subscriptions) && accounts.signedIn
case .popular:
return visibleSections.contains(.popular) && accounts.app.supportsPopular
default:
return true
}
} }
var inChannelView: Bool { var inChannelView: Bool {
@@ -321,7 +334,9 @@ struct FavoriteItemView: View {
itemLabel itemLabel
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
} }
#if !os(tvOS)
.buttonStyle(.plain) .buttonStyle(.plain)
#endif
} }
var itemNavigationLink: some View { var itemNavigationLink: some View {

View File

@@ -32,23 +32,8 @@ struct HomeView: View {
var body: some View { var body: some View {
ScrollView(.vertical, showsIndicators: false) { ScrollView(.vertical, showsIndicators: false) {
VStack { VStack {
HStack { #if !os(tvOS)
#if os(tvOS) HStack {
Group {
if showOpenActionsInHome {
AccentButton(text: "Open Video", imageSystemName: "globe") {
NavigationModel.shared.presentingOpenVideos = true
}
}
AccentButton(text: "Locations", imageSystemName: "globe") {
NavigationModel.shared.presentingAccounts = true
}
AccentButton(text: "Settings", imageSystemName: "gear") {
NavigationModel.shared.presentingSettings = true
}
}
#else
if showOpenActionsInHome { if showOpenActionsInHome {
AccentButton(text: "Files", imageSystemName: "folder") { AccentButton(text: "Files", imageSystemName: "folder") {
NavigationModel.shared.presentingFileImporter = true NavigationModel.shared.presentingFileImporter = true
@@ -61,16 +46,36 @@ struct HomeView: View {
} }
.frame(maxWidth: 40) .frame(maxWidth: 40)
} }
#endif }
} #endif
#if os(tvOS) #if os(tvOS)
HStack { HStack {
if showOpenActionsInHome {
Button {
NavigationModel.shared.presentingOpenVideos = true
} label: {
Label("Open Video", systemImage: "globe")
}
}
Button {
NavigationModel.shared.presentingAccounts = true
} label: {
Label("Locations", systemImage: "globe")
}
Spacer() Spacer()
HideWatchedButtons() HideWatchedButtons()
HideShortsButtons() HideShortsButtons()
HomeSettingsButton() Button {
NavigationModel.shared.presentingSettings = true
} label: {
Label("Settings", systemImage: "gear")
}
} }
#if os(tvOS)
.font(.caption)
.imageScale(.small)
#endif
#endif #endif
} }
.padding(.top, 15) .padding(.top, 15)

View File

@@ -7,6 +7,7 @@ struct AppTabNavigation: View {
private var player = PlayerModel.shared private var player = PlayerModel.shared
@ObservedObject private var feed = FeedModel.shared @ObservedObject private var feed = FeedModel.shared
@ObservedObject private var feedCount = UnwatchedFeedCountModel.shared @ObservedObject private var feedCount = UnwatchedFeedCountModel.shared
private var recents = RecentsModel.shared
@Default(.showHome) private var showHome @Default(.showHome) private var showHome
@Default(.showDocuments) private var showDocuments @Default(.showDocuments) private var showDocuments
@@ -175,9 +176,9 @@ struct AppTabNavigation: View {
} }
@ViewBuilder private var channelView: some View { @ViewBuilder private var channelView: some View {
if navigation.presentingChannel { if navigation.presentingChannel, let channel = recents.presentedChannel {
NavigationView { NavigationView {
ChannelVideosView(showCloseButton: true) ChannelVideosView(channel: channel, showCloseButton: true)
} }
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environment(\.inChannelView, true) .environment(\.inChannelView, true)
@@ -189,9 +190,9 @@ struct AppTabNavigation: View {
} }
@ViewBuilder private var playlistView: some View { @ViewBuilder private var playlistView: some View {
if navigation.presentingPlaylist { if navigation.presentingPlaylist, let playlist = recents.presentedPlaylist {
NavigationView { NavigationView {
ChannelPlaylistView(showCloseButton: true) ChannelPlaylistView(playlist: playlist, showCloseButton: true)
} }
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.id("channelPlaylist") .id("channelPlaylist")

View File

@@ -15,8 +15,8 @@ import SwiftUI
false false
} }
func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) { #if os(iOS)
#if os(iOS) func playerViewController(_: AVPlayerViewController, willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator) {
guard rotateToLandscapeOnEnterFullScreen.isRotating else { return } guard rotateToLandscapeOnEnterFullScreen.isRotating else { return }
if PlayerModel.shared.currentVideoIsLandscape { if PlayerModel.shared.currentVideoIsLandscape {
let delay = PlayerModel.shared.activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0 let delay = PlayerModel.shared.activeBackend == .appleAVPlayer && avPlayerUsesSystemControls ? 0.8 : 0
@@ -27,34 +27,40 @@ import SwiftUI
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation) Orientation.lockOrientation(.allButUpsideDown, andRotateTo: orientation)
} }
} }
#endif }
}
func playerViewController(_: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) { func playerViewController(_: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator) {
let wasPlaying = player.isPlaying let wasPlaying = player.isPlaying
coordinator.animate(alongsideTransition: nil) { context in coordinator.animate(alongsideTransition: nil) { context in
#if os(iOS)
if wasPlaying { if wasPlaying {
self.player.play() self.player.play()
} }
#endif if !context.isCancelled {
if !context.isCancelled { #if os(iOS)
#if os(iOS) self.player.lockedOrientation = nil
self.player.lockedOrientation = nil
if Constants.isIPhone { if Constants.isIPhone {
Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait) Orientation.lockOrientation(.allButUpsideDown, andRotateTo: .portrait)
} }
if wasPlaying { if wasPlaying {
self.player.play() self.player.play()
} }
self.player.playingFullScreen = false self.player.playingFullScreen = false
#endif #endif
}
} }
} }
}
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForFullScreenExitWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
withAnimation(nil) {
player.presentingPlayer = true
}
completionHandler(true)
}
#endif
func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {} func playerViewControllerWillStartPictureInPicture(_: AVPlayerViewController) {}
@@ -86,14 +92,6 @@ import SwiftUI
} }
} }
} }
func playerViewController(_: AVPlayerViewController, restoreUserInterfaceForFullScreenExitWithCompletionHandler completionHandler: @escaping (Bool) -> Void) {
withAnimation(nil) {
player.presentingPlayer = true
}
completionHandler(true)
}
} }
#endif #endif

View File

@@ -93,15 +93,17 @@ extension AppleAVPlayerViewController: AVPlayerViewControllerDelegate {
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {} func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {}
func playerViewController( #if os(iOS)
_: AVPlayerViewController, func playerViewController(
willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator _: AVPlayerViewController,
) {} willBeginFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) {}
func playerViewController( func playerViewController(
_: AVPlayerViewController, _: AVPlayerViewController,
willEndFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator willEndFullScreenPresentationWithAnimationCoordinator _: UIViewControllerTransitionCoordinator
) {} ) {}
#endif
func playerViewController( func playerViewController(
_: AVPlayerViewController, _: AVPlayerViewController,

View File

@@ -28,13 +28,7 @@ struct PlaybackSettings: View {
#endif #endif
var body: some View { var body: some View {
#if DEBUG ScrollView {
// TODO: remove
if #available(iOS 15.0, macOS 12.0, *) {
Self._printChanges()
}
#endif
return ScrollView {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
HStack { HStack {
Button { Button {
@@ -69,21 +63,24 @@ struct PlaybackSettings: View {
} }
.padding(.vertical, 10) .padding(.vertical, 10)
HStack { if player.activeBackend == .mpv || !player.avPlayerUsesSystemControls {
controlsHeader("Rate".localized()) HStack {
Spacer() controlsHeader("Rate".localized())
HStack(spacing: rateButtonsSpacing) { Spacer()
decreaseRateButton HStack(spacing: rateButtonsSpacing) {
#if os(tvOS) decreaseRateButton
.focused($focusedField, equals: .decreaseRate) #if os(tvOS)
#endif .focused($focusedField, equals: .decreaseRate)
rateButton #endif
increaseRateButton rateButton
#if os(tvOS) increaseRateButton
.focused($focusedField, equals: .increaseRate) #if os(tvOS)
#endif .focused($focusedField, equals: .increaseRate)
#endif
}
} }
} }
if player.activeBackend == .mpv { if player.activeBackend == .mpv {
HStack { HStack {
controlsHeader("Captions".localized()) controlsHeader("Captions".localized())

View File

@@ -25,7 +25,7 @@ struct VideoPlayerSizeModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.frame(maxWidth: geometry.size.width) .frame(width: geometry.size.width)
.frame(maxHeight: maxHeight) .frame(maxHeight: maxHeight)
#if !os(macOS) #if !os(macOS)

View File

@@ -90,13 +90,7 @@ struct VideoPlayerView: View {
} }
var videoPlayer: some View { var videoPlayer: some View {
#if DEBUG GeometryReader { geometry in
// TODO: remove
if #available(iOS 15.0, macOS 12.0, *) {
Self._printChanges()
}
#endif
return GeometryReader { geometry in
HStack(spacing: 0) { HStack(spacing: 0) {
content content
.onAppear { .onAppear {
@@ -382,7 +376,7 @@ struct VideoPlayerView: View {
.listStyle(.plain) .listStyle(.plain)
#endif #endif
.frame(maxWidth: 350) .frame(maxWidth: 350)
.background(colorScheme == .dark ? Color.black : Color.white) .background((colorScheme == .dark ? Color.black : Color.white).ignoresSafeArea())
.transition(.move(edge: .bottom)) .transition(.move(edge: .bottom))
} }
#elseif os(macOS) #elseif os(macOS)

View File

@@ -0,0 +1,29 @@
import Introspect
import Repeat
import SwiftUI
@available(iOS 15.0, macOS 12, *)
struct FocusableSearchTextField: View {
@ObservedObject private var state = SearchModel.shared
#if os(iOS)
@State private var textField: UITextField?
#elseif os(macOS)
@State private var textField: NSTextField?
#endif
var body: some View {
SearchTextField()
#if os(iOS)
.introspectTextField { field in
textField = field
}
.onChange(of: state.focused) { newValue in
if newValue, let textField, !textField.isFirstResponder {
textField.becomeFirstResponder()
textField.selectedTextRange = textField.textRange(from: textField.beginningOfDocument, to: textField.endOfDocument)
}
}
#endif
}
}

View File

@@ -95,7 +95,11 @@ struct SearchView: View {
filtersMenu filtersMenu
} }
SearchTextField() if #available(macOS 12, *) {
FocusableSearchTextField()
} else {
SearchTextField()
}
} }
#endif #endif
} }
@@ -175,7 +179,11 @@ struct SearchView: View {
searchMenu searchMenu
} }
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
SearchTextField() if #available(iOS 15, *) {
FocusableSearchTextField()
} else {
SearchTextField()
}
} }
} }
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
@@ -325,6 +333,7 @@ struct SearchView: View {
NavigationLink(destination: recentItemNavigationLinkDestination(item)) { NavigationLink(destination: recentItemNavigationLinkDestination(item)) {
recentItemLabel(item) recentItemLabel(item)
} }
.contextMenu { recentItemContextMenu(item) }
} }
@ViewBuilder private func recentItemNavigationLinkDestination(_ item: RecentItem) -> some View { @ViewBuilder private func recentItemNavigationLinkDestination(_ item: RecentItem) -> some View {
@@ -385,7 +394,11 @@ struct SearchView: View {
} label: { } label: {
recentItemLabel(item) recentItemLabel(item)
} }
.contextMenu { .contextMenu { recentItemContextMenu(item) }
}
private func recentItemContextMenu(_ item: RecentItem) -> some View {
Group {
removeButton(item) removeButton(item)
#if os(tvOS) #if os(tvOS)

View File

@@ -8,6 +8,7 @@ struct AdvancedSettings: View {
@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
@Default(.showPlayNowInBackendContextMenu) private var showPlayNowInBackendContextMenu
@State private var filesToShare = [MPVClient.logFile] @State private var filesToShare = [MPVClient.logFile]
@State private var presentingShareSheet = false @State private var presentingShareSheet = false
@@ -56,6 +57,10 @@ struct AdvancedSettings: View {
} }
@ViewBuilder var advancedSettings: some View { @ViewBuilder var advancedSettings: some View {
Section(header: SettingsHeader(text: "Advanced")) {
showPlayNowInBackendButtonsToggle
}
Section(header: SettingsHeader(text: "MPV"), footer: mpvFooter) { Section(header: SettingsHeader(text: "MPV"), footer: mpvFooter) {
showMPVPlaybackStatsToggle showMPVPlaybackStatsToggle
#if !os(tvOS) #if !os(tvOS)
@@ -115,6 +120,10 @@ struct AdvancedSettings: View {
.foregroundColor(.secondary) .foregroundColor(.secondary)
} }
var showPlayNowInBackendButtonsToggle: some View {
Toggle("Show video context menu options to force selected backend", isOn: $showPlayNowInBackendContextMenu)
}
var showMPVPlaybackStatsToggle: some View { var showMPVPlaybackStatsToggle: some View {
Toggle("Show playback statistics", isOn: $showMPVPlaybackStats) Toggle("Show playback statistics", isOn: $showMPVPlaybackStats)
} }

View File

@@ -8,6 +8,7 @@ struct BrowsingSettings: View {
#endif #endif
@Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts @Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts
@Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges @Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges
@Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop
#if os(iOS) #if os(iOS)
@Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing @Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing
@Default(.showDocuments) private var showDocuments @Default(.showDocuments) private var showDocuments
@@ -180,9 +181,11 @@ struct BrowsingSettings: View {
FeedModel.shared.calculateUnwatchedFeed() FeedModel.shared.calculateUnwatchedFeed()
} }
} }
Toggle("Open channels with description expanded", isOn: $expandChannelDescription)
} }
Toggle("Open channels with description expanded", isOn: $expandChannelDescription) Toggle("Keep channels with unwatched videos on top of subscriptions list", isOn: $keepChannelsWithUnwatchedFeedOnTop)
} }
} }

View File

@@ -124,6 +124,8 @@ struct FavoriteItemEditor: View {
@State private var presentingRemoveAlert = false @State private var presentingRemoveAlert = false
@Default(.favorites) private var favorites
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
HStack { HStack {
@@ -132,13 +134,13 @@ struct FavoriteItemEditor: View {
Spacer() Spacer()
HStack(spacing: 10) { HStack(spacing: 10) {
FavoriteItemEditorButton { FavoriteItemEditorButton(color: model.canMoveUp(item) ? .accentColor : .secondary) {
Label("Move Up", systemImage: "arrow.up") Label("Move Up", systemImage: "arrow.up")
} onTapGesture: { } onTapGesture: {
model.moveUp(item) model.moveUp(item)
} }
FavoriteItemEditorButton { FavoriteItemEditorButton(color: model.canMoveDown(item) ? .accentColor : .secondary) {
Label("Move Down", systemImage: "arrow.down") Label("Move Down", systemImage: "arrow.down")
} onTapGesture: { } onTapGesture: {
model.moveDown(item) model.moveDown(item)

View File

@@ -11,20 +11,28 @@ struct ChannelsView: View {
@Default(.showCacheStatus) private var showCacheStatus @Default(.showCacheStatus) private var showCacheStatus
@Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges @Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges
@Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop
@State private var channelLinkActive = false
@State private var channelForLink: Channel?
var body: some View { var body: some View {
List { List {
Section(header: header) { Section(header: header) {
ForEach(subscriptions.all) { channel in ForEach(channels) { channel in
let label = HStack { let label = HStack {
if let url = channel.thumbnailURLOrCached { if let url = channel.thumbnailURLOrCached {
ThumbnailView(url: url) ThumbnailView(url: url)
.frame(width: 35, height: 35) .frame(width: 35, height: 35)
.clipShape(RoundedRectangle(cornerRadius: 35)) .clipShape(RoundedRectangle(cornerRadius: 35))
Text(channel.name)
} else { } else {
Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name)) Image(systemName: RecentsModel.symbolSystemImage(channel.name))
.imageScale(.large)
.foregroundColor(.accentColor)
.frame(width: 35, height: 35)
} }
Text(channel.name)
.lineLimit(1)
} }
.backport .backport
.badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil) .badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil)
@@ -37,9 +45,15 @@ struct ChannelsView: View {
label label
} }
#else #else
NavigationLink(destination: ChannelVideosView(channel: channel)) { Button {
channelForLink = channel
channelLinkActive = channelForLink != nil
} label: {
label label
.contentShape(Rectangle())
.foregroundColor(.primary)
} }
.buttonStyle(.plain)
#endif #endif
} }
.contextMenu { .contextMenu {
@@ -63,6 +77,11 @@ struct ChannelsView: View {
.listRowSeparator(false) .listRowSeparator(false)
} }
} }
#if !os(tvOS)
.background(
NavigationLink(destination: ChannelVideosView(channel: channelForLink ?? Video.fixture.channel), isActive: $channelLinkActive, label: EmptyView.init)
)
#endif
.onAppear { .onAppear {
subscriptions.load() subscriptions.load()
} }
@@ -99,6 +118,10 @@ struct ChannelsView: View {
#endif #endif
} }
var channels: [Channel] {
keepChannelsWithUnwatchedFeedOnTop ? subscriptions.allByUnwatchedCount : subscriptions.all
}
var header: some View { var header: some View {
HStack { HStack {
#if os(tvOS) #if os(tvOS)

View File

@@ -37,6 +37,7 @@ struct SubscriptionsView: View {
Label("Channels", systemImage: "person.3.fill").tag(Page.channels) Label("Channels", systemImage: "person.3.fill").tag(Page.channels)
} }
.pickerStyle(.segmented) .pickerStyle(.segmented)
.labelStyle(.titleOnly)
subscriptionsMenu subscriptionsMenu
} }

View File

@@ -37,22 +37,22 @@ struct VideoBanner: View {
var body: some View { var body: some View {
HStack(alignment: .top, spacing: 12) { HStack(alignment: .top, spacing: 12) {
ZStack(alignment: .bottom) { VStack(alignment: .trailing, spacing: 2) {
VStack(alignment: .trailing, spacing: 2) { ZStack(alignment: .bottom) {
smallThumbnail smallThumbnail
.layoutPriority(1)
if !timeOnThumbnail, let timeLabel { ProgressView(value: watch?.progress ?? 44, total: 100)
Text(timeLabel) .frame(maxHeight: 4)
.font(.caption.monospacedDigit()) .progressViewStyle(LinearProgressViewStyle(tint: Color("AppRedColor")))
.foregroundColor(.secondary) .opacity(watch?.isShowingProgress ?? false ? 1 : 0)
}
} }
.layoutPriority(1)
ProgressView(value: watch?.progress ?? 44, total: 100) if !timeOnThumbnail, let timeLabel {
.frame(maxHeight: 4) Text(timeLabel)
.progressViewStyle(LinearProgressViewStyle(tint: Color("AppRedColor"))) .font(.caption.monospacedDigit())
.opacity(watch?.isShowingProgress ?? false ? 1 : 0) .foregroundColor(.secondary)
}
} }
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {

View File

@@ -27,9 +27,11 @@ struct AccentButton: View {
.frame(maxWidth: maxWidth) .frame(maxWidth: maxWidth)
.contentShape(Rectangle()) .contentShape(Rectangle())
} }
#if !os(tvOS)
.foregroundColor(.accentColor) .foregroundColor(.accentColor)
.buttonStyle(.plain) .buttonStyle(.plain)
.background(buttonBackground) .background(buttonBackground)
#endif
} }
var buttonBackground: some View { var buttonBackground: some View {
@@ -40,6 +42,11 @@ struct AccentButton: View {
struct OpenVideosButton_Previews: PreviewProvider { struct OpenVideosButton_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
AccentButton(text: "Open Videos", imageSystemName: "play.circle.fill") VStack {
AccentButton(text: "Open Videos", imageSystemName: "play.circle.fill")
.padding(.horizontal, 100)
AccentButton(text: "Open Videos", imageSystemName: "play.circle.fill")
.padding(.horizontal, 100)
}
} }
} }

View File

@@ -20,7 +20,7 @@ struct VideoContextMenuView: View {
@FetchRequest private var watchRequest: FetchedResults<Watch> @FetchRequest private var watchRequest: FetchedResults<Watch>
@Default(.saveHistory) private var saveHistory @Default(.showPlayNowInBackendContextMenu) private var showPlayNowInBackendContextMenu
private var backgroundContext = PersistenceController.shared.container.newBackgroundContext() private var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
@@ -43,7 +43,7 @@ struct VideoContextMenuView: View {
removeAllFromQueueButton() removeAllFromQueueButton()
} }
if !video.localStreamIsDirectory { if !video.localStreamIsDirectory {
if saveHistory { if Defaults[.saveHistory] {
Section { Section {
if let watchedAtString { if let watchedAtString {
Text(watchedAtString) Text(watchedAtString)
@@ -71,6 +71,14 @@ struct VideoContextMenuView: View {
#endif #endif
} }
if Defaults[.showPlayNowInBackendContextMenu] {
Section {
ForEach(PlayerBackendType.allCases, id: \.self) { backend in
playNowInBackendButton(backend)
}
}
}
Section { Section {
playNextButton playNextButton
addToQueueButton addToQueueButton
@@ -187,6 +195,20 @@ struct VideoContextMenuView: View {
} }
} }
private func playNowInBackendButton(_ backend: PlayerBackendType) -> some View {
Button {
if player.musicMode {
player.toggleMusicMode()
}
player.forceBackendOnPlay = backend
player.play(video)
} label: {
Label("Play Now in \(backend.label)", systemImage: "play")
}
}
private var playNowInPictureInPictureButton: some View { private var playNowInPictureInPictureButton: some View {
Button { Button {
player.avPlayerBackend.startPictureInPictureOnPlay = true player.avPlayerBackend.startPictureInPictureOnPlay = true

View File

@@ -579,3 +579,19 @@
"Landscape left" = "Querformat links"; "Landscape left" = "Querformat links";
"Landscape right" = "Querformat rechts"; "Landscape right" = "Querformat rechts";
"No rotation" = "Keine Drehung"; "No rotation" = "Keine Drehung";
"Available" = "Verfügbar";
"Startup section" = "Bereich Startup";
"Home Settings" = "Startseite Einstellungen";
"Watched: hidden" = "Beobachtet: versteckt";
"(watched and shorts hidden)" = "(beobachtet und shorts versteckt)";
"Watched: visible" = "Beobachtet: sichtbar";
"No videos to show" = "Keine Videos zu zeigen";
"(watched hidden)" = "(versteckt beobachtet)";
"(shorts hidden)" = "(shorts versteckt)";
"Disable filters" = "Filter deaktivieren";
"Limit" = "Grenze";
"Are you sure you want to remove %@ from Favorites?" = "Möchten Sie %@ wirklich aus den Favoriten entfernen?";
"Play Now in MPV" = "Jetzt im MPV abspielen";
"Keep channels with unwatched videos on top of subscriptions list" = "Kanäle mit ungesehenen Videos oben in der Abonnementliste halten";
"Show video context menu options to force selected backend" = "Video-Kontextmenüoptionen anzeigen, um die Auswahl des Backends zu erzwingen";
"Play Now in AVPlayer" = "Jetzt in AVPlayer abspielen";

View File

@@ -589,3 +589,7 @@
"Disable filters" = "Disable filters"; "Disable filters" = "Disable filters";
"Limit" = "Limit"; "Limit" = "Limit";
"Are you sure you want to remove %@ from Favorites?" = "Are you sure you want to remove %@ from Favorites?"; "Are you sure you want to remove %@ from Favorites?" = "Are you sure you want to remove %@ from Favorites?";
"Keep channels with unwatched videos on top of subscriptions list" = "Keep channels with unwatched videos on top of subscriptions list";
"Show video context menu options to force selected backend" = "Show video context menu options to force selected backend";
"Play Now in MPV" = "Play Now in MPV";
"Play Now in AVPlayer" = "Play Now in AVPlayer";

View File

@@ -579,3 +579,19 @@
"Landscape right" = "Paysage droit"; "Landscape right" = "Paysage droit";
"No rotation" = "Aucune rotation"; "No rotation" = "Aucune rotation";
"Use system controls with AVPlayer" = "Utiliser les contrôles du système avec AVPlayer"; "Use system controls with AVPlayer" = "Utiliser les contrôles du système avec AVPlayer";
"Available" = "Disponible";
"Startup section" = "Section de démarrage";
"Home Settings" = "Paramètres de l'accueil";
"Watched: hidden" = "Regardées : masquées";
"Watched: visible" = "Regardées : visibles";
"(watched and shorts hidden)" = "(regardées et shorts masqués)";
"Disable filters" = "Désactiver les filtres";
"(shorts hidden)" = "(shorts masqués)";
"No videos to show" = "Aucune vidéo à afficher";
"(watched hidden)" = "(regardées masquées)";
"Limit" = "Limite";
"Are you sure you want to remove %@ from Favorites?" = "Êtes-vous sûr de vouloir supprimer %@ des favoris ?";
"Keep channels with unwatched videos on top of subscriptions list" = "Conserver les chaînes dont les vidéos n'ont pas été visionnées en haut de la liste des abonnements";
"Show video context menu options to force selected backend" = "Afficher les options contextuelles de la vidéo pour forcer la sélection du backend";
"Play Now in AVPlayer" = "Lire maintenant dans AVPlayer";
"Play Now in MPV" = "Lire maintenant dans MPV";

View File

@@ -202,7 +202,7 @@
"Translations" = "翻訳"; "Translations" = "翻訳";
"Could not refresh Playlists" = "再生リストを更新できません"; "Could not refresh Playlists" = "再生リストを更新できません";
"No locations available at the moment" = "現時点で利用可能な場所がありません"; "No locations available at the moment" = "現時点で利用可能な場所がありません";
"Show Open Videos quick actions" = "クイック操作に動画を開くを表示"; "Show Open Videos quick actions" = "動画を開くクイック操作を表示";
"Show Documents" = "文書を表示"; "Show Documents" = "文書を表示";
"Pages toolbar position" = "ページのツールバー位置"; "Pages toolbar position" = "ページのツールバー位置";
"Buttons labels" = "ボタンのラベル"; "Buttons labels" = "ボタンのラベル";
@@ -510,12 +510,12 @@
"Segments typically found at the start of a video that include an animation, still frame or clip which are also seen in other videos by the same creator." = "動画の冒頭で、同じ作成者の他の動画でも見られるアニメーション、静止画やクリップを含む部分。"; "Segments typically found at the start of a video that include an animation, still frame or clip which are also seen in other videos by the same creator." = "動画の冒頭で、同じ作成者の他の動画でも見られるアニメーション、静止画やクリップを含む部分。";
"This information will be processed only on your device and used to connect you to the server in the specified country." = "この情報は、この端末上でのみ処理され、指定した国のサーバーに接続するために使用されます。"; "This information will be processed only on your device and used to connect you to the server in the specified country." = "この情報は、この端末上でのみ処理され、指定した国のサーバーに接続するために使用されます。";
"Videos" = "動画"; "Videos" = "動画";
"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." = "クレジットのポップアップや終了シーンが表示される、映像の最後のあたりに表示されます。";
"Verified" = "認証済み"; "Verified" = "認証済み";
"Mark channel feed as unwatched" = "チャンネルフィードを未視聴にする"; "Mark channel feed as unwatched" = "チャンネルフィードを未視聴にする";
"Open expanded" = "展開時に"; "Open expanded" = "展開したまま開く";
"Short videos: hidden" = "ショート動画: 非表示"; "Short videos: hidden" = "ショート動画: 非表示";
"Player Bar" = "プレイヤーバー"; "Player Bar" = "プレイヤーバー";
"Play all unwatched" = "未視聴をすべて再生"; "Play all unwatched" = "未視聴をすべて再生";
"Double tap gesture" = "ダブルタップ"; "Double tap gesture" = "ダブルタップ";
"Single tap gesture" = "シングルタップ"; "Single tap gesture" = "シングルタップ";
@@ -531,7 +531,7 @@
"Switch to public locations" = "公開された場所に切り替え"; "Switch to public locations" = "公開された場所に切り替え";
"Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "有料/無料のプラットフォームかを問わず、いいね、登録などを明示的に操作を促す(例: 動画をクリック)。"; "Explicit reminders to like, subscribe or interact with them on any paid or free platform(s) (e.g. click on a video)." = "有料/無料のプラットフォームかを問わず、いいね、登録などを明示的に操作を促す(例: 動画をクリック)。";
"Proxy videos" = "動画閲覧にプロキシ使用"; "Proxy videos" = "動画閲覧にプロキシ使用";
"Sections" = "表示部分"; "Sections" = "表示する部分";
"System controls show buttons for %@" = "システム制御「%@」用のボタンを表示"; "System controls show buttons for %@" = "システム制御「%@」用のボタンを表示";
"You need to create an instance and accounts\nto access %@ section" = "%@ セクションの利用には\nインスタンスとアカウントの作成が必要"; "You need to create an instance and accounts\nto access %@ section" = "%@ セクションの利用には\nインスタンスとアカウントの作成が必要";
"You need to select an account\nto access %@ section" = "%@ セクションの利用には\nアカウントの選択が必要"; "You need to select an account\nto access %@ section" = "%@ セクションの利用には\nアカウントの選択が必要";
@@ -577,3 +577,18 @@
"Show Next in Queue" = "「次の再生キュー」を表示"; "Show Next in Queue" = "「次の再生キュー」を表示";
"Next in Queue" = "次の再生キュー"; "Next in Queue" = "次の再生キュー";
"Inspector" = "ファイルの詳細情報"; "Inspector" = "ファイルの詳細情報";
"Available" = "選択候補";
"Startup section" = "起動時の表示";
"Home Settings" = "ホームの設定";
"Watched: hidden" = "視聴済み: 隠す";
"Watched: visible" = "視聴済み: 表示";
"No videos to show" = "表示する動画なし";
"(watched and shorts hidden)" = "(視聴済み/ショート非表示)";
"(shorts hidden)" = "(ショート非表示)";
"(watched hidden)" = "(視聴済み非表示)";
"Disable filters" = "絞り込み解除";
"Are you sure you want to remove %@ from Favorites?" = "お気に入りから %@ を除去しますか?";
"Show video context menu options to force selected backend" = "強制的にバックエンドを選択するための動画のコンテキストメニューを表示";
"Play Now in AVPlayer" = "AVPlayer で今すぐ再生";
"Play Now in MPV" = "MPV で今すぐ再生";
"Keep channels with unwatched videos on top of subscriptions list" = "未視聴の動画があるチャンネルをチャンネル一覧の上部に維持";

View File

@@ -582,7 +582,7 @@
"Landscape left" = "Obrót w lewo"; "Landscape left" = "Obrót w lewo";
"Available" = "Dostępne"; "Available" = "Dostępne";
"Startup section" = "Sekcja startowa"; "Startup section" = "Sekcja startowa";
"Home Settings" = "Ustawienia strony głównej"; "Home Settings" = "Ustawienia głównej";
"Watched: hidden" = "Obejrzane: ukryte"; "Watched: hidden" = "Obejrzane: ukryte";
"Watched: visible" = "Obejrzane: widoczne"; "Watched: visible" = "Obejrzane: widoczne";
"No videos to show" = "Brak wideo do pokazania"; "No videos to show" = "Brak wideo do pokazania";
@@ -592,3 +592,7 @@
"(watched hidden)" = "(obejrzane ukryte)"; "(watched hidden)" = "(obejrzane ukryte)";
"Limit" = "Limit"; "Limit" = "Limit";
"Are you sure you want to remove %@ from Favorites?" = "Czy na pewno chcesz usunąć element: %@ z Ulubionych?"; "Are you sure you want to remove %@ from Favorites?" = "Czy na pewno chcesz usunąć element: %@ z Ulubionych?";
"Play Now in AVPlayer" = "Odtwórz teraz w AVPlayerze";
"Play Now in MPV" = "Odtwórz teraz w MPV";
"Keep channels with unwatched videos on top of subscriptions list" = "Kanały z nieobejrzanymi filmami na górze listy subskrypcji";
"Show video context menu options to force selected backend" = "Pokaż opcje menu kontekstowego wideo, aby wymusić wybrany silnik";

View File

@@ -579,3 +579,19 @@
"Close video and player on end" = "Fechar vídeo e player ao final"; "Close video and player on end" = "Fechar vídeo e player ao final";
"Your Accounts" = "Suas Contas"; "Your Accounts" = "Suas Contas";
"Use system controls with AVPlayer" = "Usar controles do sistema com o AVPlayer"; "Use system controls with AVPlayer" = "Usar controles do sistema com o AVPlayer";
"Available" = "Disponível";
"Startup section" = "Seção ao iniciar";
"Home Settings" = "Ajustes da tela Início";
"Watched: hidden" = "Assistidos: ocultos";
"Watched: visible" = "Assistidos: visíveis";
"Disable filters" = "Desativar filtros";
"(watched hidden)" = "(assistidos ocultos)";
"(shorts hidden)" = "(shorts ocultos)";
"Limit" = "Limite";
"Are you sure you want to remove %@ from Favorites?" = "Tem certeza que deseja remover %@ dos Favoritos?";
"No videos to show" = "Nenhum vídeo para mostrar";
"(watched and shorts hidden)" = "(assistidos e shorts ocultos)";
"Show video context menu options to force selected backend" = "Mostrar opções do menu contextual do vídeo para forçar o backend selecionado";
"Keep channels with unwatched videos on top of subscriptions list" = "Manter canais com vídeos não vistos no topo da lista de inscrições";
"Play Now in AVPlayer" = "Tocar Agora no AVPlayer";
"Play Now in MPV" = "Tocar Agora em MPV";

View File

@@ -579,3 +579,19 @@
"Your Accounts" = "Conturi tale"; "Your Accounts" = "Conturi tale";
"Browse without account" = "Navigați fără cont"; "Browse without account" = "Navigați fără cont";
"Close video and player on end" = "Închideți videoclipul și player-ul la sfârșit"; "Close video and player on end" = "Închideți videoclipul și player-ul la sfârșit";
"Available" = "Disponibil";
"Startup section" = "Secțiunea de pornire";
"Watched: hidden" = "Vizionat: ascuns";
"Watched: visible" = "Vizionat: vizibil";
"Disable filters" = "Dezactivează filtrele";
"Home Settings" = "Setări de acasă";
"(watched and shorts hidden)" = "(vizionat și shorts ascunse)";
"No videos to show" = "fără videoclipuri de afișat";
"(watched hidden)" = "(vizionat ascuns)";
"(shorts hidden)" = "(shorts ascunse)";
"Limit" = "Limită";
"Are you sure you want to remove %@ from Favorites?" = "Sigur doriți să eliminați %@ din Favorite?";
"Keep channels with unwatched videos on top of subscriptions list" = "Păstrați canalele cu videoclipuri nevăzute în partea de sus a listei de abonamente";
"Play Now in AVPlayer" = "Redă acum în AVPlayer";
"Play Now in MPV" = "Redă acum în MPV";
"Show video context menu options to force selected backend" = "Afișați opțiunile din meniul contextual video pentru a forța backend-ul selectat";

View File

@@ -568,6 +568,10 @@
3776ADD6287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; 3776ADD6287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; };
3776ADD7287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; 3776ADD7287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; };
3776ADD8287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; }; 3776ADD8287381240078EBC4 /* Captions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3776ADD5287381240078EBC4 /* Captions.swift */; };
37772E0D2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */; };
37772E0E2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */; };
37772E0F2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */; };
37772E102A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */; };
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; 377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; 377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; }; 377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
@@ -686,6 +690,9 @@
379DC3D128BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; }; 379DC3D128BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; };
379DC3D228BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; }; 379DC3D228BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; };
379DC3D328BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; }; 379DC3D328BA4EB400B09677 /* Seek.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379DC3D028BA4EB400B09677 /* Seek.swift */; };
379E7C332A20FE3900AF8118 /* FocusableSearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379E7C322A20FE3900AF8118 /* FocusableSearchTextField.swift */; };
379E7C342A20FE3900AF8118 /* FocusableSearchTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379E7C322A20FE3900AF8118 /* FocusableSearchTextField.swift */; };
379E7C362A2105B900AF8118 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 379E7C352A2105B900AF8118 /* Introspect */; };
379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; }; 379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; };
379EF9E129AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; }; 379EF9E129AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; };
379EF9E229AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; }; 379EF9E229AA585F009FE6C6 /* HideShortsButtons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */; };
@@ -1350,6 +1357,7 @@
3776925129463C310055EC18 /* PlaylistsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistsCacheModel.swift; sourceTree = "<group>"; }; 3776925129463C310055EC18 /* PlaylistsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistsCacheModel.swift; sourceTree = "<group>"; };
377692552946476F0055EC18 /* ChannelPlaylistsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelPlaylistsCacheModel.swift; sourceTree = "<group>"; }; 377692552946476F0055EC18 /* ChannelPlaylistsCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelPlaylistsCacheModel.swift; sourceTree = "<group>"; };
3776ADD5287381240078EBC4 /* Captions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Captions.swift; path = Model/Captions.swift; sourceTree = SOURCE_ROOT; }; 3776ADD5287381240078EBC4 /* Captions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Captions.swift; path = Model/Captions.swift; sourceTree = SOURCE_ROOT; };
37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+ReplacingHTMLEntities.swift"; sourceTree = "<group>"; };
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = "<group>"; }; 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = "<group>"; };
377ABC3F286E4AD5009C986F /* InstancesManifest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstancesManifest.swift; sourceTree = "<group>"; }; 377ABC3F286E4AD5009C986F /* InstancesManifest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstancesManifest.swift; sourceTree = "<group>"; };
377ABC43286E4B74009C986F /* ManifestedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManifestedInstance.swift; sourceTree = "<group>"; }; 377ABC43286E4B74009C986F /* ManifestedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManifestedInstance.swift; sourceTree = "<group>"; };
@@ -1386,6 +1394,7 @@
379ACB502A1F8DB000E01914 /* HomeSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSettingsButton.swift; sourceTree = "<group>"; }; 379ACB502A1F8DB000E01914 /* HomeSettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSettingsButton.swift; sourceTree = "<group>"; };
379B0252287A1CDF001015B5 /* OrientationTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrientationTracker.swift; sourceTree = "<group>"; }; 379B0252287A1CDF001015B5 /* OrientationTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrientationTracker.swift; sourceTree = "<group>"; };
379DC3D028BA4EB400B09677 /* Seek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Seek.swift; sourceTree = "<group>"; }; 379DC3D028BA4EB400B09677 /* Seek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Seek.swift; sourceTree = "<group>"; };
379E7C322A20FE3900AF8118 /* FocusableSearchTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableSearchTextField.swift; sourceTree = "<group>"; };
379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */ = {isa = PBXFileReference; indentWidth = 3; lastKnownFileType = sourcecode.swift; path = HideShortsButtons.swift; sourceTree = "<group>"; }; 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */ = {isa = PBXFileReference; indentWidth = 3; lastKnownFileType = sourcecode.swift; path = HideShortsButtons.swift; sourceTree = "<group>"; };
379F141E289ECE7F00DE48B5 /* QualitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualitySettings.swift; sourceTree = "<group>"; }; 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualitySettings.swift; sourceTree = "<group>"; };
37A2B345294723850050933E /* CacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheModel.swift; sourceTree = "<group>"; }; 37A2B345294723850050933E /* CacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheModel.swift; sourceTree = "<group>"; };
@@ -1626,6 +1635,7 @@
37F7AB5228A94EB900FB46B5 /* IOKit.framework in Frameworks */, 37F7AB5228A94EB900FB46B5 /* IOKit.framework in Frameworks */,
370F4FDF27CC16CB001B35DC /* libxcb-shape.0.0.0.dylib in Frameworks */, 370F4FDF27CC16CB001B35DC /* libxcb-shape.0.0.0.dylib in Frameworks */,
370F4FE127CC16CB001B35DC /* libuchardet.0.0.7.dylib in Frameworks */, 370F4FE127CC16CB001B35DC /* libuchardet.0.0.7.dylib in Frameworks */,
379E7C362A2105B900AF8118 /* Introspect in Frameworks */,
370F4FDB27CC16CB001B35DC /* libswscale.6.4.100.dylib in Frameworks */, 370F4FDB27CC16CB001B35DC /* libswscale.6.4.100.dylib in Frameworks */,
370F4FDC27CC16CB001B35DC /* libavutil.57.17.100.dylib in Frameworks */, 370F4FDC27CC16CB001B35DC /* libavutil.57.17.100.dylib in Frameworks */,
370F4FE327CC16CB001B35DC /* libbrotlicommon.1.dylib in Frameworks */, 370F4FE327CC16CB001B35DC /* libbrotlicommon.1.dylib in Frameworks */,
@@ -2187,6 +2197,7 @@
3782B95527557A2400990149 /* Search */ = { 3782B95527557A2400990149 /* Search */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
379E7C322A20FE3900AF8118 /* FocusableSearchTextField.swift */,
3782B94E27553A6700990149 /* SearchSuggestions.swift */, 3782B94E27553A6700990149 /* SearchSuggestions.swift */,
374710042755291C00CE0F87 /* SearchTextField.swift */, 374710042755291C00CE0F87 /* SearchTextField.swift */,
37AAF27F26737550007FC770 /* SearchView.swift */, 37AAF27F26737550007FC770 /* SearchView.swift */,
@@ -2280,6 +2291,7 @@
377ABC47286E5887009C986F /* Sequence+Unique.swift */, 377ABC47286E5887009C986F /* Sequence+Unique.swift */,
3782B9512755667600990149 /* String+Format.swift */, 3782B9512755667600990149 /* String+Format.swift */,
37270F1B28E06E3E00856150 /* String+Localizable.swift */, 37270F1B28E06E3E00856150 /* String+Localizable.swift */,
37772E0C2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift */,
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */, 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */,
37F7AB4C28A9361F00FB46B5 /* UIDevice+Cellular.swift */, 37F7AB4C28A9361F00FB46B5 /* UIDevice+Cellular.swift */,
370B79CB286279BA0045DB77 /* UIViewController+HideHomeIndicator.swift */, 370B79CB286279BA0045DB77 /* UIViewController+HideHomeIndicator.swift */,
@@ -2686,6 +2698,7 @@
374D11E62943C56300CB4350 /* Cache */, 374D11E62943C56300CB4350 /* Cache */,
371AC0B1294D1C230085989E /* CachedAsyncImage */, 371AC0B1294D1C230085989E /* CachedAsyncImage */,
379325D629A265AE00181CF1 /* Logging */, 379325D629A265AE00181CF1 /* Logging */,
379E7C352A2105B900AF8118 /* Introspect */,
); );
productName = "Yattee (macOS)"; productName = "Yattee (macOS)";
productReference = 37D4B0CF2671614900C925CA /* Yattee.app */; productReference = 37D4B0CF2671614900C925CA /* Yattee.app */;
@@ -3122,6 +3135,7 @@
374924DA2921050B0017D862 /* LocationsSettings.swift in Sources */, 374924DA2921050B0017D862 /* LocationsSettings.swift in Sources */,
378FFBC428660172009E3FBE /* URLParser.swift in Sources */, 378FFBC428660172009E3FBE /* URLParser.swift in Sources */,
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */, 3784B23D2728B85300B09468 /* ShareButton.swift in Sources */,
379E7C332A20FE3900AF8118 /* FocusableSearchTextField.swift in Sources */,
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */, 37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
3743CA52270F284F00E4D32B /* View+Borders.swift in Sources */, 3743CA52270F284F00E4D32B /* View+Borders.swift in Sources */,
3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */, 3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */,
@@ -3225,6 +3239,7 @@
37B4E803277D0A72004BF56A /* AppDelegate.swift in Sources */, 37B4E803277D0A72004BF56A /* AppDelegate.swift in Sources */,
37FD77002932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */, 37FD77002932C4DA00D91A5F /* URL+ByReplacingYatteeProtocol.swift in Sources */,
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */, 373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
37772E0D2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */,
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */, 3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */, 37DCD3112A18E8150059A470 /* OrientationModel.swift in Sources */,
3782B9522755667600990149 /* String+Format.swift in Sources */, 3782B9522755667600990149 /* String+Format.swift in Sources */,
@@ -3440,6 +3455,7 @@
377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */, 377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */,
374924DB2921050B0017D862 /* LocationsSettings.swift in Sources */, 374924DB2921050B0017D862 /* LocationsSettings.swift in Sources */,
371AC0A0294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */, 371AC0A0294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */,
379E7C342A20FE3900AF8118 /* FocusableSearchTextField.swift in Sources */,
37F5C7E12A1E2AF300927B73 /* ListView.swift in Sources */, 37F5C7E12A1E2AF300927B73 /* ListView.swift in Sources */,
37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */, 37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */,
3784CDE327772EE40055BBF2 /* Watch.swift in Sources */, 3784CDE327772EE40055BBF2 /* Watch.swift in Sources */,
@@ -3481,6 +3497,7 @@
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */, 37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
377ABC45286E4B74009C986F /* ManifestedInstance.swift in Sources */, 377ABC45286E4B74009C986F /* ManifestedInstance.swift in Sources */,
3795593727B08538007FF8F4 /* StreamControl.swift in Sources */, 3795593727B08538007FF8F4 /* StreamControl.swift in Sources */,
37772E0E2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */,
375B8AB428B580D300397B31 /* KeychainModel.swift in Sources */, 375B8AB428B580D300397B31 /* KeychainModel.swift in Sources */,
37F7AB5528A951B200FB46B5 /* Power.swift in Sources */, 37F7AB5528A951B200FB46B5 /* Power.swift in Sources */,
372CFD16285F2E2A00B0B54B /* ControlsBar.swift in Sources */, 372CFD16285F2E2A00B0B54B /* ControlsBar.swift in Sources */,
@@ -3681,6 +3698,7 @@
3774124B27387D2300423605 /* ThumbnailsModel.swift in Sources */, 3774124B27387D2300423605 /* ThumbnailsModel.swift in Sources */,
3774125427387D2300423605 /* Store.swift in Sources */, 3774125427387D2300423605 /* Store.swift in Sources */,
37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */, 37DCD31A2A191A180059A470 /* AVPlayerViewController+FullScreen.swift in Sources */,
37772E102A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */,
3774125027387D2300423605 /* Video.swift in Sources */, 3774125027387D2300423605 /* Video.swift in Sources */,
37EF9A79275BEB8E0043B585 /* CommentView.swift in Sources */, 37EF9A79275BEB8E0043B585 /* CommentView.swift in Sources */,
3774125327387D2300423605 /* Country.swift in Sources */, 3774125327387D2300423605 /* Country.swift in Sources */,
@@ -3866,6 +3884,7 @@
3748186C26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */, 3748186C26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */,
3744A96228B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */, 3744A96228B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */,
3710A55729488C7D006F8025 /* PlaceholderListItem.swift in Sources */, 3710A55729488C7D006F8025 /* PlaceholderListItem.swift in Sources */,
37772E0F2A216F8600608BD9 /* String+ReplacingHTMLEntities.swift in Sources */,
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */, 377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */, 3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */,
377F9F7D294403F20043F856 /* VideosCacheModel.swift in Sources */, 377F9F7D294403F20043F856 /* VideosCacheModel.swift in Sources */,
@@ -4034,7 +4053,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 = 151; CURRENT_PROJECT_VERSION = 155;
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";
@@ -4065,7 +4084,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 = 151; CURRENT_PROJECT_VERSION = 155;
"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";
@@ -4096,7 +4115,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 = 151; CURRENT_PROJECT_VERSION = 155;
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;
@@ -4116,7 +4135,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 = 151; CURRENT_PROJECT_VERSION = 155;
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;
@@ -4276,7 +4295,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 = 151; CURRENT_PROJECT_VERSION = 155;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1", "DEBUG=1",
@@ -4329,7 +4348,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 = 151; CURRENT_PROJECT_VERSION = 155;
"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";
@@ -4381,7 +4400,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 = 151; CURRENT_PROJECT_VERSION = 155;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
ENABLE_APP_SANDBOX = YES; ENABLE_APP_SANDBOX = YES;
ENABLE_HARDENED_RUNTIME = YES; ENABLE_HARDENED_RUNTIME = YES;
@@ -4423,7 +4442,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 = 151; CURRENT_PROJECT_VERSION = 155;
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;
@@ -4461,7 +4480,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 = 151; CURRENT_PROJECT_VERSION = 155;
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 = (
@@ -4485,7 +4504,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 = 151; CURRENT_PROJECT_VERSION = 155;
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 = (
@@ -4511,7 +4530,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 = 151; CURRENT_PROJECT_VERSION = 155;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4536,7 +4555,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 = 151; CURRENT_PROJECT_VERSION = 155;
DEAD_CODE_STRIPPING = YES; DEAD_CODE_STRIPPING = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
@@ -4562,7 +4581,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 = 151; CURRENT_PROJECT_VERSION = 155;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@@ -4602,7 +4621,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 = 151; CURRENT_PROJECT_VERSION = 155;
DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_ASSET_PATHS = "";
"DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ; "DEVELOPMENT_TEAM[sdk=appletvos*]" = 78Z5H3M6RJ;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -4643,7 +4662,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 = 151; CURRENT_PROJECT_VERSION = 155;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -4667,7 +4686,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 = 151; CURRENT_PROJECT_VERSION = 155;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
LD_RUNPATH_SEARCH_PATHS = ( LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)", "$(inherited)",
@@ -5166,6 +5185,11 @@
package = 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */; package = 3799AC0728B03CEC001376F9 /* XCRemoteSwiftPackageReference "ActiveLabel.swift" */;
productName = ActiveLabel; productName = ActiveLabel;
}; };
379E7C352A2105B900AF8118 /* Introspect */ = {
isa = XCSwiftPackageProductDependency;
package = 37BD07C52698B27B003EBB87 /* XCRemoteSwiftPackageReference "SwiftUI-Introspect" */;
productName = Introspect;
};
37BADCA42699FB72009BE4FB /* Alamofire */ = { 37BADCA42699FB72009BE4FB /* Alamofire */ = {
isa = XCSwiftPackageProductDependency; isa = XCSwiftPackageProductDependency;
package = 37BADCA32699FB72009BE4FB /* XCRemoteSwiftPackageReference "Alamofire" */; package = 37BADCA32699FB72009BE4FB /* XCRemoteSwiftPackageReference "Alamofire" */;

View File

@@ -96,7 +96,7 @@
"location" : "https://github.com/SDWebImage/SDWebImage", "location" : "https://github.com/SDWebImage/SDWebImage",
"state" : { "state" : {
"branch" : "master", "branch" : "master",
"revision" : "7f9fb5d43ecd4aa714c00746f54873f354403438" "revision" : "90aa750c92973005f086263e44c6224a1456bb12"
} }
}, },
{ {
@@ -150,7 +150,7 @@
"location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image", "location" : "https://github.com/lorenzofiamingo/swiftui-cached-async-image",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "f94be38411297c71aa8df69c6875127e6d8d7a92" "revision" : "2abb11839f80ebb07a58ac5e146a1da664260c16"
} }
}, },
{ {
@@ -158,8 +158,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/siteline/SwiftUI-Introspect.git", "location" : "https://github.com/siteline/SwiftUI-Introspect.git",
"state" : { "state" : {
"revision" : "5b3f3996c7a2a84d5f4ba0e03cd7d584154778f2", "revision" : "c29b50a475120fdfa22573622464a7346aaf99a4",
"version" : "0.3.1" "version" : "0.6.0"
} }
}, },
{ {