diff --git a/Backports/ScrollDismissesKeyboard+Backport.swift b/Backports/ScrollDismissesKeyboard+Backport.swift index 8f60cae6..adb8363b 100644 --- a/Backports/ScrollDismissesKeyboard+Backport.swift +++ b/Backports/ScrollDismissesKeyboard+Backport.swift @@ -11,10 +11,10 @@ extension Backport where Content: View { } @ViewBuilder func scrollDismissesKeyboardInteractively() -> some View { - if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) { - content.scrollDismissesKeyboard(.interactively) - } else { - content - } + if #available(iOS 16.0, macOS 13.0, tvOS 16.0, *) { + content.scrollDismissesKeyboard(.interactively) + } else { + content } + } } diff --git a/Model/Applications/InvidiousAPI.swift b/Model/Applications/InvidiousAPI.swift index 1b7bd4df..cdb339bd 100644 --- a/Model/Applications/InvidiousAPI.swift +++ b/Model/Applications/InvidiousAPI.swift @@ -461,6 +461,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI { } let description = json["description"].stringValue + let length = json["lengthSeconds"].doubleValue return Video( instanceID: account.instanceID, @@ -470,7 +471,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI { videoID: videoID, title: json["title"].stringValue, author: json["author"].stringValue, - length: json["lengthSeconds"].doubleValue, + length: length, published: published, views: json["viewCount"].intValue, description: description, @@ -480,6 +481,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI { indexID: indexID, live: json["liveNow"].boolValue, upcoming: json["isUpcoming"].boolValue, + short: length <= Video.shortLength, publishedAt: publishedAt, likes: json["likeCount"].int, dislikes: json["dislikeCount"].int, diff --git a/Model/Applications/PipedAPI.swift b/Model/Applications/PipedAPI.swift index 6b5a8b22..061f35cb 100644 --- a/Model/Applications/PipedAPI.swift +++ b/Model/Applications/PipedAPI.swift @@ -481,6 +481,8 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { chapters = extractChapters(from: description) } + let length = details["duration"]?.double ?? 0 + return Video( instanceID: account.instanceID, app: .piped, @@ -488,13 +490,14 @@ final class PipedAPI: Service, ObservableObject, VideosAPI { videoID: extractID(from: content), title: details["title"]?.string ?? "", author: author, - length: details["duration"]?.double ?? 0, + length: length, published: published ?? "", views: details["views"]?.int ?? 0, description: description, channel: Channel(app: .piped, id: channelId, name: author, thumbnailURL: authorThumbnailURL, subscriptionsCount: subscriptionsCount), thumbnails: thumbnails, live: live, + short: details["isShort"]?.bool ?? (length <= Video.shortLength), likes: details["likes"]?.int, dislikes: details["dislikes"]?.int, streams: extractStreams(from: content), diff --git a/Model/FeedModel.swift b/Model/FeedModel.swift index 7f107a41..e68a79a9 100644 --- a/Model/FeedModel.swift +++ b/Model/FeedModel.swift @@ -1,5 +1,6 @@ import Cache import CoreData +import Defaults import Foundation import Siesta import SwiftyJSON @@ -237,6 +238,10 @@ final class FeedModel: ObservableObject, CacheModel { let watches = watchFetchRequestResult(videos, context: backgroundContext) let watchesIDs = watches.map(\.videoID) let unwatched = videos.filter { video in + if Defaults[.hideShorts], video.short { + return false + } + if !watchesIDs.contains(video.videoID) { return true } diff --git a/Model/Video.swift b/Model/Video.swift index 172a5826..9920ca63 100644 --- a/Model/Video.swift +++ b/Model/Video.swift @@ -5,6 +5,8 @@ import SwiftUI import SwiftyJSON struct Video: Identifiable, Equatable, Hashable { + static let shortLength = 61.0 + enum VideoID { static func isValid(_ id: Video.ID) -> Bool { isYouTube(id) || isPeerTube(id) @@ -40,6 +42,7 @@ struct Video: Identifiable, Equatable, Hashable { var live: Bool var upcoming: Bool + var short: Bool var streams = [Stream]() @@ -74,6 +77,7 @@ struct Video: Identifiable, Equatable, Hashable { indexID: String? = nil, live: Bool = false, upcoming: Bool = false, + short: Bool = false, publishedAt: Date? = nil, likes: Int? = nil, dislikes: Int? = nil, @@ -101,6 +105,7 @@ struct Video: Identifiable, Equatable, Hashable { self.indexID = indexID self.live = live self.upcoming = upcoming + self.short = short self.publishedAt = publishedAt self.likes = likes self.dislikes = dislikes @@ -154,6 +159,7 @@ struct Video: Identifiable, Equatable, Hashable { "indexID": indexID ?? "", "live": live, "upcoming": upcoming, + "short": short, "publishedAt": publishedAt ] } @@ -180,6 +186,7 @@ struct Video: Identifiable, Equatable, Hashable { indexID: json["indexID"].stringValue, live: json["live"].boolValue, upcoming: json["upcoming"].boolValue, + short: json["short"].boolValue, publishedAt: dateFormatter.date(from: json["publishedAt"].stringValue) ) } diff --git a/Shared/Channels/ChannelPlaylistView.swift b/Shared/Channels/ChannelPlaylistView.swift index 24cdf585..12971592 100644 --- a/Shared/Channels/ChannelPlaylistView.swift +++ b/Shared/Channels/ChannelPlaylistView.swift @@ -14,6 +14,7 @@ struct ChannelPlaylistView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.navigationStyle) private var navigationStyle @Default(.channelPlaylistListingStyle) private var channelPlaylistListingStyle + @Default(.hideShorts) private var hideShorts @ObservedObject private var accounts = AccountsModel.shared var player = PlayerModel.shared @@ -104,6 +105,7 @@ struct ChannelPlaylistView: View { ToolbarItem(placement: playlistButtonsPlacement) { HStack { ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) + HideShortsButtons(hide: $hideShorts) ShareButton(contentItem: contentItem) favoriteButton @@ -131,6 +133,10 @@ struct ChannelPlaylistView: View { ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) + Section { + HideShortsButtons(hide: $hideShorts) + } + Section { SettingsButtons() } diff --git a/Shared/Channels/ChannelVideosView.swift b/Shared/Channels/ChannelVideosView.swift index d7daf7b1..01286c2b 100644 --- a/Shared/Channels/ChannelVideosView.swift +++ b/Shared/Channels/ChannelVideosView.swift @@ -32,6 +32,7 @@ struct ChannelVideosView: View { @Default(.channelPlaylistListingStyle) private var channelPlaylistListingStyle @Default(.expandChannelDescription) private var expandChannelDescription + @Default(.hideShorts) private var hideShorts var presentedChannel: Channel? { store.item ?? channel ?? recents.presentedChannel @@ -100,6 +101,7 @@ struct ChannelVideosView: View { } .environment(\.inChannelView, true) .environment(\.listingStyle, channelPlaylistListingStyle) + .environment(\.hideShorts, hideShorts) #if os(tvOS) .prefersDefaultFocus(in: focusNamespace) #endif @@ -131,6 +133,9 @@ struct ChannelVideosView: View { ToolbarItem { ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) } + ToolbarItem { + HideShortsButtons(hide: $hideShorts) + } ToolbarItem { contentTypePicker } @@ -271,6 +276,10 @@ struct ChannelVideosView: View { } ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) + + Section { + HideShortsButtons(hide: $hideShorts) + } } } label: { HStack(spacing: 12) { diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index da88c33e..ad3ca554 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -238,6 +238,8 @@ extension Defaults.Keys { static let openWatchNextOnFinishedWatching = Key("openWatchNextOnFinishedWatching", default: true) static let openWatchNextOnClose = Key("openWatchNextOnClose", default: false) static let openWatchNextOnFinishedWatchingDelay = Key("openWatchNextOnFinishedWatchingDelay", default: "5") + + static let hideShorts = Key("hideShorts", default: false) } enum ResolutionSetting: String, CaseIterable, Defaults.Serializable { diff --git a/Shared/EnvironmentValues.swift b/Shared/EnvironmentValues.swift index 7509ef07..04d231db 100644 --- a/Shared/EnvironmentValues.swift +++ b/Shared/EnvironmentValues.swift @@ -66,6 +66,10 @@ private struct ScrollViewBottomPaddingKey: EnvironmentKey { static let defaultValue: Double = 30 } +private struct HideShortsKey: EnvironmentKey { + static let defaultValue = false +} + extension EnvironmentValues { var inChannelView: Bool { get { self[InChannelViewKey.self] } @@ -121,4 +125,9 @@ extension EnvironmentValues { get { self[NoListingDividersKey.self] } set { self[NoListingDividersKey.self] = newValue } } + + var hideShorts: Bool { + get { self[HideShortsKey.self] } + set { self[HideShortsKey.self] = newValue } + } } diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index b7ed8984..92012a06 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -24,6 +24,7 @@ struct PlaylistsView: View { @Default(.playlistListingStyle) private var playlistListingStyle @Default(.showCacheStatus) private var showCacheStatus + @Default(.hideShorts) private var hideShorts var items: [ContentItem] { var videos = currentPlaylist?.videos ?? [] @@ -95,6 +96,7 @@ struct PlaylistsView: View { } .environment(\.currentPlaylistID, currentPlaylist?.id) .environment(\.listingStyle, playlistListingStyle) + .environment(\.hideShorts, hideShorts) } } } @@ -167,6 +169,9 @@ struct PlaylistsView: View { ToolbarItem { ListingStyleButtons(listingStyle: $playlistListingStyle) } + ToolbarItem { + HideShortsButtons(hide: $hideShorts) + } } #else .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in @@ -234,6 +239,10 @@ struct PlaylistsView: View { ListingStyleButtons(listingStyle: $playlistListingStyle) + Section { + HideShortsButtons(hide: $hideShorts) + } + Section { SettingsButtons() } diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index f5533258..7ea83be8 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -30,6 +30,7 @@ struct SearchView: View { @Default(.saveRecents) private var saveRecents @Default(.showHome) private var showHome @Default(.searchListingStyle) private var searchListingStyle + @Default(.hideShorts) private var hideShorts private var videos = [Video]() @@ -70,10 +71,12 @@ struct SearchView: View { #endif } .environment(\.listingStyle, searchListingStyle) + .environment(\.hideShorts, hideShorts) .toolbar { #if os(macOS) ToolbarItemGroup(placement: toolbarPlacement) { ListingStyleButtons(listingStyle: $searchListingStyle) + HideShortsButtons(hide: $hideShorts) FavoriteButton(item: favoriteItem) .id(favoriteItem?.id) @@ -210,6 +213,10 @@ struct SearchView: View { ListingStyleButtons(listingStyle: $searchListingStyle) + Section { + HideShortsButtons(hide: $hideShorts) + } + Section { SettingsButtons() } diff --git a/Shared/Subscriptions/FeedView.swift b/Shared/Subscriptions/FeedView.swift index 15eac61d..800bf4a6 100644 --- a/Shared/Subscriptions/FeedView.swift +++ b/Shared/Subscriptions/FeedView.swift @@ -10,6 +10,7 @@ struct FeedView: View { #if os(tvOS) @Default(.subscriptionsListingStyle) private var subscriptionsListingStyle + @Default(.hideShorts) private var hideShorts #endif var videos: [ContentItem] { @@ -54,6 +55,7 @@ struct FeedView: View { #if os(tvOS) SubscriptionsPageButton() ListingStyleButtons(listingStyle: $subscriptionsListingStyle) + HideShortsButtons(hide: $hideShorts) #endif if showCacheStatus { @@ -82,6 +84,7 @@ struct FeedView: View { .padding(.leading, 30) #if os(tvOS) .padding(.bottom, 15) + .padding(.trailing, 30) #endif } @@ -94,7 +97,7 @@ struct FeedView: View { } } -struct SubscriptonsView_Previews: PreviewProvider { +struct FeedView_Previews: PreviewProvider { static var previews: some View { NavigationView { FeedView() diff --git a/Shared/Subscriptions/SubscriptionsView.swift b/Shared/Subscriptions/SubscriptionsView.swift index f95e2e70..1bd77d8f 100644 --- a/Shared/Subscriptions/SubscriptionsView.swift +++ b/Shared/Subscriptions/SubscriptionsView.swift @@ -10,6 +10,7 @@ struct SubscriptionsView: View { @Default(.subscriptionsViewPage) private var subscriptionsViewPage @Default(.subscriptionsListingStyle) private var subscriptionsListingStyle + @Default(.hideShorts) private var hideShorts @ObservedObject private var feed = FeedModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared @@ -27,6 +28,7 @@ struct SubscriptionsView: View { } } .environment(\.listingStyle, subscriptionsListingStyle) + .environment(\.hideShorts, hideShorts) #if os(iOS) .navigationBarTitleDisplayMode(.inline) @@ -46,6 +48,10 @@ struct SubscriptionsView: View { ListingStyleButtons(listingStyle: $subscriptionsListingStyle) } + ToolbarItem { + HideShortsButtons(hide: $hideShorts) + } + ToolbarItem { toggleWatchedButton } @@ -73,6 +79,10 @@ struct SubscriptionsView: View { ListingStyleButtons(listingStyle: $subscriptionsListingStyle) } + Section { + HideShortsButtons(hide: $hideShorts) + } + playUnwatchedButton toggleWatchedButton diff --git a/Shared/Trending/TrendingView.swift b/Shared/Trending/TrendingView.swift index 6cac0182..fab45a49 100644 --- a/Shared/Trending/TrendingView.swift +++ b/Shared/Trending/TrendingView.swift @@ -10,6 +10,7 @@ struct TrendingView: View { @Default(.trendingCountry) private var country @Default(.trendingListingStyle) private var trendingListingStyle + @Default(.hideShorts) private var hideShorts @State private var presentingCountrySelection = false @@ -51,6 +52,7 @@ struct TrendingView: View { #endif } .environment(\.listingStyle, trendingListingStyle) + .environment(\.hideShorts, hideShorts) } .toolbar { @@ -133,6 +135,10 @@ struct TrendingView: View { ToolbarItem { ListingStyleButtons(listingStyle: $trendingListingStyle) } + + ToolbarItem { + HideShortsButtons(hide: $hideShorts) + } } #else .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in @@ -182,6 +188,10 @@ struct TrendingView: View { ListingStyleButtons(listingStyle: $trendingListingStyle) + Section { + HideShortsButtons(hide: $hideShorts) + } + Section { SettingsButtons() } diff --git a/Shared/Views/ContentItemView.swift b/Shared/Views/ContentItemView.swift index 400fada1..1ac584d3 100644 --- a/Shared/Views/ContentItemView.swift +++ b/Shared/Views/ContentItemView.swift @@ -5,21 +5,32 @@ struct ContentItemView: View { let item: ContentItem @Environment(\.listingStyle) private var listingStyle @Environment(\.noListingDividers) private var noListingDividers + @Environment(\.hideShorts) private var hideShorts - var body: some View { - Group { - switch item.contentType { - case .video: - videoItem(item.video) - case .channel: - channelItem(item.channel) - case .playlist: - playlistItem(item.playlist) - default: - placeholderItem() + @ViewBuilder var body: some View { + if itemVisible { + Group { + switch item.contentType { + case .video: + videoItem(item.video) + case .channel: + channelItem(item.channel) + case .playlist: + playlistItem(item.playlist) + default: + placeholderItem() + } } + .id(item.cacheKey) } - .id(item.cacheKey) + } + + var itemVisible: Bool { + guard hideShorts, item.contentType == .video, let video = item.video else { + return true + } + + return !video.short } @ViewBuilder func videoItem(_ video: Video) -> some View { diff --git a/Shared/Views/HideShortsButtons.swift b/Shared/Views/HideShortsButtons.swift new file mode 100644 index 00000000..8d1d394e --- /dev/null +++ b/Shared/Views/HideShortsButtons.swift @@ -0,0 +1,33 @@ +import SwiftUI + +struct HideShortsButtons: View { + @Binding var hide: Bool + + var body: some View { + Button { + hide.toggle() + } label: { + Group { + if hide { + Label("Short videos: hidden", systemImage: "bolt.slash.fill") + .help("Short videos: hidden") + } else { + Label("Short videos: visible", systemImage: "bolt.fill") + .help("Short videos: visible") + } + } + #if os(tvOS) + .font(.caption2) + .imageScale(.small) + #endif + } + } +} + +struct HideShortsButtons_Previews: PreviewProvider { + static var previews: some View { + VStack { + HideShortsButtons(hide: .constant(true)) + } + } +} diff --git a/Shared/Views/PopularView.swift b/Shared/Views/PopularView.swift index 4c6ae139..6517aa74 100644 --- a/Shared/Views/PopularView.swift +++ b/Shared/Views/PopularView.swift @@ -10,6 +10,7 @@ struct PopularView: View { @State private var error: RequestError? @Default(.popularListingStyle) private var popularListingStyle + @Default(.hideShorts) private var hideShorts var resource: Resource? { accounts.api.popular @@ -69,6 +70,10 @@ struct PopularView: View { ToolbarItem { ListingStyleButtons(listingStyle: $popularListingStyle) } + + ToolbarItem { + HideShortsButtons(hide: $hideShorts) + } } #else .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in @@ -84,6 +89,10 @@ struct PopularView: View { Menu { ListingStyleButtons(listingStyle: $popularListingStyle) + Section { + HideShortsButtons(hide: $hideShorts) + } + Section { SettingsButtons() } diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 9df8bf83..51db31ce 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -679,6 +679,9 @@ 379DC3D128BA4EB400B09677 /* 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 */; }; + 379EF9E029AA585F009FE6C6 /* 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 */; }; 379F141F289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; }; 379F1420289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; }; 379F1421289ECE7F00DE48B5 /* QualitySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */; }; @@ -1364,6 +1367,7 @@ 37992DC726CC50BC003D4C27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 379B0252287A1CDF001015B5 /* OrientationTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrientationTracker.swift; sourceTree = ""; }; 379DC3D028BA4EB400B09677 /* Seek.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Seek.swift; sourceTree = ""; }; + 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */ = {isa = PBXFileReference; indentWidth = 3; lastKnownFileType = sourcecode.swift; path = HideShortsButtons.swift; sourceTree = ""; }; 379F141E289ECE7F00DE48B5 /* QualitySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QualitySettings.swift; sourceTree = ""; }; 37A2B345294723850050933E /* CacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CacheModel.swift; sourceTree = ""; }; 37A362B92953707F00BDF328 /* ClearQueueButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearQueueButton.swift; sourceTree = ""; }; @@ -1872,6 +1876,7 @@ 372CFD14285F2E2A00B0B54B /* ControlsBar.swift */, 3748186D26A769D60084E870 /* DetailBadge.swift */, 37599F37272B4D740087F250 /* FavoriteButton.swift */, + 379EF9DF29AA585F009FE6C6 /* HideShortsButtons.swift */, 37152EE926EFEB95004FB96D /* LazyView.swift */, 371CC7732946963000979C1A /* ListingStyleButtons.swift */, 37030FF627B0347C00ECDDAA /* MPVPlayerView.swift */, @@ -3306,6 +3311,7 @@ 37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */, 377692562946476F0055EC18 /* ChannelPlaylistsCacheModel.swift in Sources */, 3769C02E2779F18600DDB3EA /* PlaceholderProgressView.swift in Sources */, + 379EF9E029AA585F009FE6C6 /* HideShortsButtons.swift in Sources */, 37F5E8B6291BE9D0006C15F5 /* URLBookmarkModel.swift in Sources */, 37579D5D27864F5F00FD0B98 /* Help.swift in Sources */, 37030FFB27B0398000ECDDAA /* MPVClient.swift in Sources */, @@ -3434,6 +3440,7 @@ 372CFD16285F2E2A00B0B54B /* ControlsBar.swift in Sources */, 37FFC441272734C3009FFD26 /* Throttle.swift in Sources */, 37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */, + 379EF9E129AA585F009FE6C6 /* HideShortsButtons.swift in Sources */, 37BA793C26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */, 378E510026FE8EEE00F49626 /* AccountViewButton.swift in Sources */, 370F4FA927CC163A001B35DC /* PlayerBackend.swift in Sources */, @@ -3848,6 +3855,7 @@ 373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */, 37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, 37F4AD2128612DFD004D0F66 /* Buffering.swift in Sources */, + 379EF9E229AA585F009FE6C6 /* HideShortsButtons.swift in Sources */, 37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */, 37FAE000272ED58000330459 /* EditFavorites.swift in Sources */, 37F961A127BD90BB00058149 /* PlayerBackendType.swift in Sources */,