diff --git a/Backports/Badge+Backport.swift b/Backports/Badge+Backport.swift new file mode 100644 index 00000000..4c12db48 --- /dev/null +++ b/Backports/Badge+Backport.swift @@ -0,0 +1,15 @@ +import SwiftUI + +extension Backport where Content: View { + @ViewBuilder func badge(_ count: Text?) -> some View { + #if os(tvOS) + content + #else + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + content.badge(count) + } else { + content + } + #endif + } +} diff --git a/Backports/ListRowSeparator+Backport.swift b/Backports/ListRowSeparator+Backport.swift new file mode 100644 index 00000000..78d17bc3 --- /dev/null +++ b/Backports/ListRowSeparator+Backport.swift @@ -0,0 +1,15 @@ +import Foundation +import SwiftUI + +extension Backport where Content: View { + @ViewBuilder func listRowSeparator(_ visible: Bool) -> some View { + if #available(iOS 15, macOS 13, *) { + content + #if !os(tvOS) + .listRowSeparator(visible ? .visible : .hidden) + #endif + } else { + content + } + } +} diff --git a/Backports/Refreshable+Backport.swift b/Backports/Refreshable+Backport.swift new file mode 100644 index 00000000..9f6da1c8 --- /dev/null +++ b/Backports/Refreshable+Backport.swift @@ -0,0 +1,11 @@ +import SwiftUI + +extension Backport where Content: View { + @ViewBuilder func refreshable(action: @Sendable @escaping () async -> Void) -> some View { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + content.refreshable(action: action) + } else { + content + } + } +} diff --git a/Backports/Tint+Backport.swift b/Backports/Tint+Backport.swift new file mode 100644 index 00000000..05683362 --- /dev/null +++ b/Backports/Tint+Backport.swift @@ -0,0 +1,11 @@ +import SwiftUI + +extension Backport where Content: View { + @ViewBuilder func tint(_ color: Color?) -> some View { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + content.tint(color) + } else { + content.foregroundColor(color) + } + } +} diff --git a/Shared/Channels/ChannelVideosView.swift b/Shared/Channels/ChannelVideosView.swift index 65994672..b9210952 100644 --- a/Shared/Channels/ChannelVideosView.swift +++ b/Shared/Channels/ChannelVideosView.swift @@ -40,7 +40,7 @@ struct ChannelVideosView: View { } var body: some View { - VStack { + let content = VStack { #if os(tvOS) VStack { HStack(spacing: 24) { @@ -181,12 +181,19 @@ struct ChannelVideosView: View { .navigationTitle(navigationTitle) #endif - #if os(tvOS) - .background(Color.background(scheme: colorScheme)) - #endif - #if !os(iOS) - .focusScope(focusNamespace) - #endif + return Group { + if #available(macOS 12.0, *) { + content + #if os(tvOS) + .background(Color.background(scheme: colorScheme)) + #endif + #if !os(iOS) + .focusScope(focusNamespace) + #endif + } else { + content + } + } } var verticalCellsEdgesIgnoringSafeArea: Edge.Set { diff --git a/Shared/Constants.swift b/Shared/Constants.swift index 00cf3e6c..ef85ff0e 100644 --- a/Shared/Constants.swift +++ b/Shared/Constants.swift @@ -63,9 +63,16 @@ struct Constants { static func seekIcon(_ type: String, _ interval: TimeInterval) -> String { let interval = Int(interval) - let allVersions = [5, 10, 15, 30, 45, 60, 75, 90] + let allVersions = [10, 15, 30, 45, 60, 75, 90] + let iOS15 = [5] let iconName = "go\(type).\(interval)" + if #available(iOS 15, macOS 12, *) { + if iOS15.contains(interval) { + return iconName + } + } + if allVersions.contains(interval) { return iconName } diff --git a/Shared/Documents/DocumentsView.swift b/Shared/Documents/DocumentsView.swift index 704e0b2a..46274335 100644 --- a/Shared/Documents/DocumentsView.swift +++ b/Shared/Documents/DocumentsView.swift @@ -34,7 +34,8 @@ struct DocumentsView: View { } .navigationTitle(directoryLabel) .padding(.horizontal) - .navigationBarTitleDisplayMode(.inline) + .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) + .backport .refreshable { DispatchQueue.main.async { self.refresh() diff --git a/Shared/Navigation/AccountViewButton.swift b/Shared/Navigation/AccountViewButton.swift index b4ff3044..34d587bf 100644 --- a/Shared/Navigation/AccountViewButton.swift +++ b/Shared/Navigation/AccountViewButton.swift @@ -15,10 +15,14 @@ struct AccountViewButton: View { } label: { HStack(spacing: 6) { if !accountPickerDisplaysUsername || !(model.current?.isPublic ?? true) { - if let name = model.current?.app?.rawValue.capitalized { - Image(name) - .resizable() - .frame(width: accountImageSize, height: accountImageSize) + if #available(iOS 15, macOS 12, *) { + if let name = model.current?.app?.rawValue.capitalized { + Image(name) + .resizable() + .frame(width: accountImageSize, height: accountImageSize) + } else { + Image(systemName: "globe") + } } else { Image(systemName: "globe") } diff --git a/Shared/Navigation/AppSidebarPlaylists.swift b/Shared/Navigation/AppSidebarPlaylists.swift index a58e6432..ced46dd6 100644 --- a/Shared/Navigation/AppSidebarPlaylists.swift +++ b/Shared/Navigation/AppSidebarPlaylists.swift @@ -38,6 +38,7 @@ struct AppSidebarPlaylists: View { if accounts.app.userPlaylistsEndpointIncludesVideos, !playlist.videos.isEmpty { label + .backport .badge(Text("\(playlist.videos.count)")) } else { label diff --git a/Shared/Navigation/AppSidebarSubscriptions.swift b/Shared/Navigation/AppSidebarSubscriptions.swift index 0bc53db5..8236bc0e 100644 --- a/Shared/Navigation/AppSidebarSubscriptions.swift +++ b/Shared/Navigation/AppSidebarSubscriptions.swift @@ -33,6 +33,7 @@ struct AppSidebarSubscriptions: View { Spacer() } .lineLimit(1) + .backport .badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil) } .contextMenu { diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 8c965596..12f6adc0 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -95,6 +95,7 @@ struct AppTabNavigation: View { .accessibility(label: Text("Subscriptions")) } .tag(TabSelection.subscriptions) + .backport .badge(showUnwatchedFeedBadges ? feedCount.unwatchedText : nil) } diff --git a/Shared/Navigation/Sidebar.swift b/Shared/Navigation/Sidebar.swift index 7498304f..8d3f8607 100644 --- a/Shared/Navigation/Sidebar.swift +++ b/Shared/Navigation/Sidebar.swift @@ -81,6 +81,7 @@ struct Sidebar: View { Label("Subscriptions", systemImage: "star.circle") .accessibility(label: Text("Subscriptions")) } + .backport .badge(showUnwatchedFeedBadges ? feedCount.unwatchedText : nil) .contextMenu { playUnwatchedButton diff --git a/Shared/Player/AppleAVPlayerViewController.swift b/Shared/Player/AppleAVPlayerViewController.swift index 0f428d73..c94ebed1 100644 --- a/Shared/Player/AppleAVPlayerViewController.swift +++ b/Shared/Player/AppleAVPlayerViewController.swift @@ -47,7 +47,7 @@ final class AppleAVPlayerViewController: UIViewController { infoViewControllers.append(infoViewController([.chapters], title: "Chapters")) infoViewControllers.append(infoViewController([.comments], title: "Comments")) - let queueSections = [NowPlayingView.ViewSection.playingNext] + var queueSections = [NowPlayingView.ViewSection.playingNext] infoViewControllers.append(contentsOf: [ infoViewController([.related], title: "Related"), diff --git a/Shared/Player/Controls/ControlBackgroundModifier.swift b/Shared/Player/Controls/ControlBackgroundModifier.swift index 6cb224ba..e2bbd315 100644 --- a/Shared/Player/Controls/ControlBackgroundModifier.swift +++ b/Shared/Player/Controls/ControlBackgroundModifier.swift @@ -7,8 +7,19 @@ struct ControlBackgroundModifier: ViewModifier { func body(content: Content) -> some View { if enabled { - content + if #available(iOS 15, macOS 12, *) { + content + .background(.thinMaterial) + } else { + content + #if os(macOS) + .background(VisualEffectBlur(material: .hudWindow)) + #elseif os(iOS) + .background(VisualEffectBlur(blurStyle: .systemThinMaterial).edgesIgnoringSafeArea(edgesIgnoringSafeArea)) + #else .background(.thinMaterial) + #endif + } } else { content } diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index 35da896f..05d2c6e3 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -255,6 +255,8 @@ struct PlayerControls: View { { ThumbnailView(url: url) .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.opacity) + .animation(.default) } else if player.videoForDisplay == nil { Color.black } diff --git a/Shared/Player/RelatedView.swift b/Shared/Player/RelatedView.swift index 81498297..1e411e38 100644 --- a/Shared/Player/RelatedView.swift +++ b/Shared/Player/RelatedView.swift @@ -18,9 +18,8 @@ struct RelatedView: View { Color.clear.padding(.bottom, 50) .listRowBackground(Color.clear) - #if os(iOS) - .listRowSeparator(.hidden) - #endif + .backport + .listRowSeparator(false) } } } diff --git a/Shared/Player/Video Details/CommentView.swift b/Shared/Player/Video Details/CommentView.swift index 0f3648dd..1cccafba 100644 --- a/Shared/Player/Video Details/CommentView.swift +++ b/Shared/Player/Video Details/CommentView.swift @@ -219,17 +219,25 @@ struct CommentView: View { } private var commentText: some View { - Text(comment.text) - #if !os(tvOS) - .textSelection(.enabled) - #endif - #if os(macOS) - .font(.system(size: 14)) - #elseif os(iOS) - .font(.system(size: 15)) - #endif - .lineSpacing(3) - .fixedSize(horizontal: false, vertical: true) + Group { + let text = Text(comment.text) + #if os(macOS) + .font(.system(size: 14)) + #elseif os(iOS) + .font(.system(size: 15)) + #endif + .lineSpacing(3) + .fixedSize(horizontal: false, vertical: true) + + if #available(iOS 15.0, macOS 12.0, *) { + text + #if !os(tvOS) + .textSelection(.enabled) + #endif + } else { + text + } + } } private func openChannelAction() { diff --git a/Shared/Player/Video Details/CommentsView.swift b/Shared/Player/Video Details/CommentsView.swift index b31733bc..04242bab 100644 --- a/Shared/Player/Video Details/CommentsView.swift +++ b/Shared/Player/Video Details/CommentsView.swift @@ -32,8 +32,13 @@ struct CommentsView: View { struct CommentsView_Previews: PreviewProvider { static var previews: some View { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + CommentsView() + .previewInterfaceOrientation(.landscapeRight) + .injectFixtureEnvironmentObjects() + } + CommentsView() - .previewInterfaceOrientation(.landscapeRight) .injectFixtureEnvironmentObjects() } } diff --git a/Shared/Player/Video Details/InspectorView.swift b/Shared/Player/Video Details/InspectorView.swift index 493af05f..997d27d7 100644 --- a/Shared/Player/Video Details/InspectorView.swift +++ b/Shared/Player/Video Details/InspectorView.swift @@ -79,10 +79,15 @@ struct InspectorView: View { Text(detail.localized()) .foregroundColor(.secondary) Spacer() - Text(value).lineLimit(1) - #if !os(tvOS) + let value = Text(value).lineLimit(1) + if #available(iOS 15.0, macOS 12.0, *) { + value + #if !os(tvOS) .textSelection(.enabled) - #endif + #endif + } else { + value + } } .font(.caption) } diff --git a/Shared/Player/Video Details/PlayerQueueView.swift b/Shared/Player/Video Details/PlayerQueueView.swift index 4a8371da..057f7d5d 100644 --- a/Shared/Player/Video Details/PlayerQueueView.swift +++ b/Shared/Player/Video Details/PlayerQueueView.swift @@ -30,9 +30,8 @@ struct PlayerQueueView: View { #endif Color.clear.padding(.bottom, 50) .listRowBackground(Color.clear) - #if os(iOS) - .listRowSeparator(.hidden) - #endif + .backport + .listRowSeparator(false) } .environment(\.inNavigationView, false) } diff --git a/Shared/Player/Video Details/VideoDescription.swift b/Shared/Player/Video Details/VideoDescription.swift index 4b7b161b..b704a26d 100644 --- a/Shared/Player/Video Details/VideoDescription.swift +++ b/Shared/Player/Video Details/VideoDescription.swift @@ -59,15 +59,23 @@ struct VideoDescription: View { @ViewBuilder var textDescription: some View { #if !os(iOS) - Text(description) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(shouldExpand ? 500 : Self.collapsedLines) - #if !os(tvOS) - .textSelection(.enabled) - #endif - .multilineTextAlignment(.leading) - .font(.system(size: 14)) - .lineSpacing(3) + Group { + if #available(macOS 12, *) { + Text(description) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(shouldExpand ? 500 : Self.collapsedLines) + #if !os(tvOS) + .textSelection(.enabled) + #endif + } else { + Text(description) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(shouldExpand ? 500 : Self.collapsedLines) + } + } + .multilineTextAlignment(.leading) + .font(.system(size: 14)) + .lineSpacing(3) #endif } diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index ae112aed..568a9a1f 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -294,6 +294,9 @@ struct VideoPlayerView: View { } }) #endif + + .background(Color.black) + if !detailsHiddenInFullScreen { VideoDetails( video: player.videoForDisplay, diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index 3d3adca6..9f927ee1 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -91,6 +91,13 @@ struct PlaylistsView: View { loadResource() } #if os(iOS) + .refreshControl { refreshControl in + model.load(force: true) { + model.reloadPlaylists.toggle() + refreshControl.endRefreshing() + } + } + .backport .refreshable { DispatchQueue.main.async { model.load(force: true) { model.reloadPlaylists.toggle() } diff --git a/Shared/Search/FocusableSearchTextField.swift b/Shared/Search/FocusableSearchTextField.swift index a40f96b6..39445b97 100644 --- a/Shared/Search/FocusableSearchTextField.swift +++ b/Shared/Search/FocusableSearchTextField.swift @@ -2,6 +2,7 @@ import Repeat import SwiftUI import SwiftUIIntrospect +@available(iOS 15.0, macOS 12, *) struct FocusableSearchTextField: View { @ObservedObject private var state = SearchModel.shared diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index e848f6c5..c5d47a3b 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -95,7 +95,11 @@ struct SearchView: View { filtersMenu } - FocusableSearchTextField() + if #available(macOS 12, *) { + FocusableSearchTextField() + } else { + SearchTextField() + } } #endif } @@ -175,7 +179,11 @@ struct SearchView: View { searchMenu } ToolbarItem(placement: .principal) { - FocusableSearchTextField() + if #available(iOS 15, *) { + FocusableSearchTextField() + } else { + SearchTextField() + } } } .navigationBarTitleDisplayMode(.inline) diff --git a/Shared/Settings/QualityProfileForm.swift b/Shared/Settings/QualityProfileForm.swift index 3a516f3e..daade631 100644 --- a/Shared/Settings/QualityProfileForm.swift +++ b/Shared/Settings/QualityProfileForm.swift @@ -201,7 +201,7 @@ struct QualityProfileForm: View { @ViewBuilder var formatsPicker: some View { #if os(macOS) - ForEach(QualityProfile.Format.allCases, id: \.self) { format in + let list = ForEach(QualityProfile.Format.allCases, id: \.self) { format in MultiselectRow( title: format.description, selected: isFormatSelected(format), @@ -210,8 +210,16 @@ struct QualityProfileForm: View { toggleFormat(format, value: value) } } - .listStyle(.inset(alternatesRowBackgrounds: true)) + Group { + if #available(macOS 12.0, *) { + list + .listStyle(.inset(alternatesRowBackgrounds: true)) + } else { + list + .listStyle(.inset) + } + } Spacer() #else ForEach(QualityProfile.Format.allCases, id: \.self) { format in diff --git a/Shared/Settings/QualitySettings.swift b/Shared/Settings/QualitySettings.swift index de3d873a..afe8ae8d 100644 --- a/Shared/Settings/QualitySettings.swift +++ b/Shared/Settings/QualitySettings.swift @@ -175,14 +175,24 @@ struct QualitySettings: View { } } - #if os(macOS) - List { + if #available(macOS 12.0, *) { + #if os(macOS) + List { + list + } + .listStyle(.inset(alternatesRowBackgrounds: true)) + #else list - } - .listStyle(.inset(alternatesRowBackgrounds: true)) - #else - list - #endif + #endif + } else { + #if os(macOS) + List { + list + } + #else + list + #endif + } } } diff --git a/Shared/Settings/SponsorBlockSettings.swift b/Shared/Settings/SponsorBlockSettings.swift index ca118e84..114a0154 100644 --- a/Shared/Settings/SponsorBlockSettings.swift +++ b/Shared/Settings/SponsorBlockSettings.swift @@ -50,8 +50,15 @@ struct SponsorBlockSettings: View { } } - list - .listStyle(.inset(alternatesRowBackgrounds: true)) + Group { + if #available(macOS 12.0, *) { + list + .listStyle(.inset(alternatesRowBackgrounds: true)) + } else { + list + .listStyle(.inset) + } + } Spacer() #else ForEach(SponsorBlockAPI.categories, id: \.self) { category in diff --git a/Shared/Subscriptions/ChannelsView.swift b/Shared/Subscriptions/ChannelsView.swift index a61f6c20..e7fd7e3c 100644 --- a/Shared/Subscriptions/ChannelsView.swift +++ b/Shared/Subscriptions/ChannelsView.swift @@ -34,9 +34,8 @@ struct ChannelsView: View { Text(channel.name) .lineLimit(1) } - #if !os(tvOS) + .backport .badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil) - #endif Group { #if os(tvOS) @@ -74,9 +73,8 @@ struct ChannelsView: View { Color.clear.padding(.bottom, 50) .listRowBackground(Color.clear) - #if os(iOS) - .listRowSeparator(.hidden) - #endif + .backport + .listRowSeparator(false) } } #if !os(tvOS) @@ -91,6 +89,12 @@ struct ChannelsView: View { subscriptions.load(force: true) } #if os(iOS) + .refreshControl { refreshControl in + subscriptions.load(force: true) { + refreshControl.endRefreshing() + } + } + .backport .refreshable { subscriptions.load(force: true) } diff --git a/Shared/Subscriptions/FeedView.swift b/Shared/Subscriptions/FeedView.swift index bfcc3c80..0a1c40ab 100644 --- a/Shared/Subscriptions/FeedView.swift +++ b/Shared/Subscriptions/FeedView.swift @@ -22,6 +22,12 @@ struct FeedView: View { feed.loadResources() } #if os(iOS) + .refreshControl { refreshControl in + feed.loadResources(force: true) { + refreshControl.endRefreshing() + } + } + .backport .refreshable { feed.loadResources(force: true) } diff --git a/Shared/Trending/TrendingCountry.swift b/Shared/Trending/TrendingCountry.swift index da54ab8a..04e22873 100644 --- a/Shared/Trending/TrendingCountry.swift +++ b/Shared/Trending/TrendingCountry.swift @@ -16,7 +16,11 @@ struct TrendingCountry: View { VStack { #if !os(tvOS) HStack { - TextField("Country", text: $query, prompt: Text(Self.prompt)) + if #available(iOS 15.0, macOS 12.0, *) { + TextField("Country", text: $query, prompt: Text(Self.prompt)) + } else { + TextField(Self.prompt, text: $query) + } Button("Done") { selectCountryAndDismiss() } .keyboardShortcut(.defaultAction) @@ -53,8 +57,12 @@ struct TrendingCountry: View { return Group { #if os(macOS) - list - .listStyle(.inset(alternatesRowBackgrounds: true)) + if #available(macOS 12.0, *) { + list + .listStyle(.inset(alternatesRowBackgrounds: true)) + } else { + list + } #else list #endif diff --git a/Shared/Trending/TrendingView.swift b/Shared/Trending/TrendingView.swift index d7e78bd4..73d15135 100644 --- a/Shared/Trending/TrendingView.swift +++ b/Shared/Trending/TrendingView.swift @@ -95,7 +95,12 @@ struct TrendingView: View { .navigationTitle("Trending") #endif #if os(iOS) - + .refreshControl { refreshControl in + resource.load().onCompletion { _ in + refreshControl.endRefreshing() + } + } + .backport .refreshable { DispatchQueue.main.async { resource.load() diff --git a/Shared/Videos/ThumbnailView.swift b/Shared/Videos/ThumbnailView.swift index 0c55f122..ed846848 100644 --- a/Shared/Videos/ThumbnailView.swift +++ b/Shared/Videos/ThumbnailView.swift @@ -48,19 +48,23 @@ struct ThumbnailView: View { } @ViewBuilder var asyncImageIfAvailable: some View { - CachedAsyncImage(url: url, urlCache: BaseCacheModel.imageCache, transaction: Transaction(animation: .default)) { phase in - switch phase { - case let .success(image): - image - .resizable() - case .failure: - placeholder.onAppear { - guard let url else { return } - thumbnails.insertUnloadable(url) + if #available(iOS 15, macOS 12, *) { + CachedAsyncImage(url: url, urlCache: BaseCacheModel.imageCache) { phase in + switch phase { + case let .success(image): + image + .resizable() + case .failure: + placeholder.onAppear { + guard let url else { return } + thumbnails.insertUnloadable(url) + } + default: + placeholder } - default: - placeholder } + } else { + webImage } } diff --git a/Shared/Views/OpenSettingsButton.swift b/Shared/Views/OpenSettingsButton.swift index f0af0c48..4bb3f98a 100644 --- a/Shared/Views/OpenSettingsButton.swift +++ b/Shared/Views/OpenSettingsButton.swift @@ -8,7 +8,7 @@ struct OpenSettingsButton: View { #endif var body: some View { - Button { + let button = Button { presentationMode.wrappedValue.dismiss() #if os(macOS) @@ -20,7 +20,13 @@ struct OpenSettingsButton: View { Label("Open Settings", systemImage: "gearshape.2") } .buttonStyle(.plain) - .buttonStyle(.borderedProminent) + + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + button + .buttonStyle(.borderedProminent) + } else { + button + } } } diff --git a/Shared/Views/PopularView.swift b/Shared/Views/PopularView.swift index 6ffe3d7f..8ca31a6b 100644 --- a/Shared/Views/PopularView.swift +++ b/Shared/Views/PopularView.swift @@ -47,6 +47,14 @@ struct PopularView: View { } } .navigationBarTitleDisplayMode(.inline) + .refreshControl { refreshControl in + resource?.load().onCompletion { _ in + refreshControl.endRefreshing() + } + .onFailure { self.error = $0 } + .onSuccess { _ in self.error = nil } + } + .backport .refreshable { DispatchQueue.main.async { resource?.load() @@ -54,7 +62,7 @@ struct PopularView: View { .onSuccess { _ in self.error = nil } } } - .navigationBarTitleDisplayMode(.inline) + .navigationBarTitleDisplayMode(RefreshControl.navigationBarTitleDisplayMode) #endif #if os(macOS) .toolbar { diff --git a/Shared/Views/VideoContextMenuView.swift b/Shared/Views/VideoContextMenuView.swift index 48195f83..45606e49 100644 --- a/Shared/Views/VideoContextMenuView.swift +++ b/Shared/Views/VideoContextMenuView.swift @@ -281,7 +281,11 @@ struct VideoContextMenuView: View { let label = Label("Remove…", systemImage: "trash.fill") .foregroundColor(Color("AppRedColor")) - Button(role: .destructive, action: action) { label } + if #available(iOS 15, macOS 12, *) { + Button(role: .destructive, action: action) { label } + } else { + Button(action: action) { label } + } } #endif diff --git a/Vendor/RefreshControl/Extensions/UIResponder+Extensions.swift b/Vendor/RefreshControl/Extensions/UIResponder+Extensions.swift new file mode 100644 index 00000000..ae87e27d --- /dev/null +++ b/Vendor/RefreshControl/Extensions/UIResponder+Extensions.swift @@ -0,0 +1,15 @@ +// +// UIResponder+Extensions.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 21/09/2021. +// + +import Foundation +import UIKit + +extension UIResponder { + var parentViewController: UIViewController? { + next as? UIViewController ?? next?.parentViewController + } +} diff --git a/Vendor/RefreshControl/Extensions/UIView+Extensions.swift b/Vendor/RefreshControl/Extensions/UIView+Extensions.swift new file mode 100644 index 00000000..c924d1a1 --- /dev/null +++ b/Vendor/RefreshControl/Extensions/UIView+Extensions.swift @@ -0,0 +1,70 @@ +// +// UIView+Extensions.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 19/09/2021. +// + +import Foundation +import UIKit + +extension UIView { + /// Returs frame in screen coordinates. + var globalFrame: CGRect { + if let window { + return convert(bounds, to: window.screen.coordinateSpace) + } else { + return .zero + } + } + + /// Returns with all the instances of the given view type in the view hierarchy. + func viewsInHierarchy() -> [ViewType]? { + var views: [ViewType] = [] + viewsInHierarchy(views: &views) + return views.isEmpty ? nil : views + } + + private func viewsInHierarchy(views: inout [ViewType]) { + subviews.forEach { eachSubView in + if let matchingView = eachSubView as? ViewType { + views.append(matchingView) + } + eachSubView.viewsInHierarchy(views: &views) + } + } + + /// Search ancestral view hierarcy for the given view type. + func searchViewAnchestorsFor( + _ onViewFound: (ViewType) -> Void + ) { + if let matchingView = superview as? ViewType { + onViewFound(matchingView) + } else { + superview?.searchViewAnchestorsFor(onViewFound) + } + } + + /// Search ancestral view hierarcy for the given view type. + func searchViewAnchestorsFor() -> ViewType? { + if let matchingView = superview as? ViewType { + return matchingView + } else { + return superview?.searchViewAnchestorsFor() + } + } + + func printViewHierarchyInformation(_ level: Int = 0) { + printViewInformation(level) + subviews.forEach { $0.printViewHierarchyInformation(level + 1) } + } + + func printViewInformation(_ level: Int) { + let leadingWhitespace = String(repeating: " ", count: level) + let className = "\(Self.self)" + let superclassName = "\(superclass!)" + let frame = "\(self.frame)" + let id = (accessibilityIdentifier == nil) ? "" : " `\(accessibilityIdentifier!)`" + print("\(leadingWhitespace)\(className): \(superclassName)\(id) \(frame)") + } +} diff --git a/Vendor/RefreshControl/RefreshControl.swift b/Vendor/RefreshControl/RefreshControl.swift new file mode 100644 index 00000000..67a8b3e1 --- /dev/null +++ b/Vendor/RefreshControl/RefreshControl.swift @@ -0,0 +1,56 @@ +// +// RefreshControl.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 18/09/2021. +// + +import Combine +import Foundation +import SwiftUI +import UIKit + +final class RefreshControl: ObservableObject { + static var navigationBarTitleDisplayMode: NavigationBarItem.TitleDisplayMode { + if #available(iOS 15.0, *) { + return .automatic + } + + return .inline + } + + let onValueChanged: (_ refreshControl: UIRefreshControl) -> Void + + internal init(onValueChanged: @escaping ((UIRefreshControl) -> Void)) { + self.onValueChanged = onValueChanged + } + + /// Adds a `UIRefreshControl` to the `UIScrollView` provided. + func add(to scrollView: UIScrollView) { + scrollView.refreshControl = UIRefreshControl().withTarget( + self, + action: #selector(onValueChangedAction), + for: .valueChanged + ) + .testable(as: "RefreshControl") + } + + @objc private func onValueChangedAction(sender: UIRefreshControl) { + onValueChanged(sender) + } +} + +extension UIRefreshControl { + /// Convinience method to assign target action inline. + func withTarget(_ target: Any?, action: Selector, for controlEvents: UIControl.Event) -> UIRefreshControl { + addTarget(target, action: action, for: controlEvents) + return self + } + + /// Convinience method to match refresh control for UI testing. + func testable(as id: String) -> UIRefreshControl { + isAccessibilityElement = true + accessibilityIdentifier = id + return self + } +} diff --git a/Vendor/RefreshControl/RefreshControlModifier.swift b/Vendor/RefreshControl/RefreshControlModifier.swift new file mode 100644 index 00000000..c85704e8 --- /dev/null +++ b/Vendor/RefreshControl/RefreshControlModifier.swift @@ -0,0 +1,46 @@ +// +// RefreshControlModifier.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 18/09/2021. +// + +import Foundation +import SwiftUI + +struct RefreshControlModifier: ViewModifier { + @State private var geometryReaderFrame: CGRect = .zero + let refreshControl: RefreshControl + + internal init(onValueChanged: @escaping (UIRefreshControl) -> Void) { + refreshControl = RefreshControl(onValueChanged: onValueChanged) + } + + func body(content: Content) -> some View { + if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) { + return content + } else { + return content + .background( + GeometryReader { geometry in + ScrollViewMatcher( + onResolve: { scrollView in + refreshControl.add(to: scrollView) + }, + geometryReaderFrame: $geometryReaderFrame + ) + .preference(key: FramePreferenceKey.self, value: geometry.frame(in: .global)) + .onPreferenceChange(FramePreferenceKey.self) { frame in + self.geometryReaderFrame = frame + } + } + ) + } + } +} + +extension View { + func refreshControl(onValueChanged: @escaping (_ refreshControl: UIRefreshControl) -> Void) -> some View { + modifier(RefreshControlModifier(onValueChanged: onValueChanged)) + } +} diff --git a/Vendor/RefreshControl/ScrollViewMatcher/FramePreferenceKey.swift b/Vendor/RefreshControl/ScrollViewMatcher/FramePreferenceKey.swift new file mode 100644 index 00000000..65f05cbc --- /dev/null +++ b/Vendor/RefreshControl/ScrollViewMatcher/FramePreferenceKey.swift @@ -0,0 +1,18 @@ +// +// FramePreferenceKey.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 21/09/2021. +// + +import Foundation +import SwiftUI + +struct FramePreferenceKey: PreferenceKey { + typealias Value = CGRect + static var defaultValue = CGRect.zero + + static func reduce(value: inout CGRect, nextValue: () -> CGRect) { + value = nextValue() + } +} diff --git a/Vendor/RefreshControl/ScrollViewMatcher/ScrollViewMatcher.swift b/Vendor/RefreshControl/ScrollViewMatcher/ScrollViewMatcher.swift new file mode 100644 index 00000000..f5fbd4df --- /dev/null +++ b/Vendor/RefreshControl/ScrollViewMatcher/ScrollViewMatcher.swift @@ -0,0 +1,106 @@ +// +// ScrollViewMatcher.swift +// SwiftUI_Pull_to_Refresh +// +// Created by Geri Borbás on 17/09/2021. +// + +import Foundation +import SwiftUI + +final class ScrollViewMatcher: UIViewControllerRepresentable { + let onMatch: (UIScrollView) -> Void + @Binding var geometryReaderFrame: CGRect + + init(onResolve: @escaping (UIScrollView) -> Void, geometryReaderFrame: Binding) { + onMatch = onResolve + _geometryReaderFrame = geometryReaderFrame + } + + func makeUIViewController(context _: Context) -> ScrollViewMatcherViewController { + ScrollViewMatcherViewController(onResolve: onMatch, geometryReaderFrame: geometryReaderFrame) + } + + func updateUIViewController(_ viewController: ScrollViewMatcherViewController, context _: Context) { + viewController.geometryReaderFrame = geometryReaderFrame + } +} + +final class ScrollViewMatcherViewController: UIViewController { + let onMatch: (UIScrollView) -> Void + private var scrollView: UIScrollView? { + didSet { + if oldValue != scrollView, + let scrollView + { + onMatch(scrollView) + } + } + } + + var geometryReaderFrame: CGRect { + didSet { + match() + } + } + + init(onResolve: @escaping (UIScrollView) -> Void, geometryReaderFrame: CGRect, debug _: Bool = false) { + onMatch = onResolve + self.geometryReaderFrame = geometryReaderFrame + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("Use init(onMatch:) to instantiate ScrollViewMatcherViewController.") + } + + func match() { + // matchUsingHierarchy() + matchUsingGeometry() + } + + func matchUsingHierarchy() { + if parent != nil { + // Lookup view ancestry for any `UIScrollView`. + view.searchViewAnchestorsFor { (scrollView: UIScrollView) in + self.scrollView = scrollView + } + } + } + + func matchUsingGeometry() { + if let parent { + if let scrollViewsInHierarchy: [UIScrollView] = parent.view.viewsInHierarchy() { + // Return first match if only a single scroll view is found in the hierarchy. + if scrollViewsInHierarchy.count == 1, + let firstScrollViewInHierarchy = scrollViewsInHierarchy.first + { + scrollView = firstScrollViewInHierarchy + + // Filter by frame origins if multiple matches found. + } else { + if let firstMatchingFrameOrigin = scrollViewsInHierarchy.filter({ + $0.globalFrame.origin.close(to: geometryReaderFrame.origin) + }).first { + scrollView = firstMatchingFrameOrigin + } + } + } + } + } + + override func didMove(toParent parent: UIViewController?) { + super.didMove(toParent: parent) + match() + } +} + +extension CGPoint { + /// Returns `true` if this point is close the other point (considering a ~1 pt tolerance). + func close(to point: CGPoint) -> Bool { + let inset = Double(1) + let rect = CGRect(x: x - inset, y: y - inset, width: inset * 2, height: inset * 2) + return rect.contains(point) + } +} diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 7cc581a5..cdf60565 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -172,7 +172,9 @@ 371F2F1A269B43D300E4A7AB /* NavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationModel.swift */; }; 371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationModel.swift */; }; 371F2F1C269B43D300E4A7AB /* NavigationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371F2F19269B43D300E4A7AB /* NavigationModel.swift */; }; + 3722AEBC274DA396005EA4D6 /* Badge+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */; }; 3722AEBE274DA401005EA4D6 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBD274DA401005EA4D6 /* Backport.swift */; }; + 3726386E2948A4B80043702D /* Badge+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */; }; 37270F1C28E06E3E00856150 /* String+Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37270F1B28E06E3E00856150 /* String+Localizable.swift */; }; 37270F1D28E06E3E00856150 /* String+Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37270F1B28E06E3E00856150 /* String+Localizable.swift */; }; 37270F1E28E06E3E00856150 /* String+Localizable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37270F1B28E06E3E00856150 /* String+Localizable.swift */; }; @@ -322,6 +324,7 @@ 37579D5E27864F5F00FD0B98 /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37579D5C27864F5F00FD0B98 /* Help.swift */; }; 37579D5F27864F5F00FD0B98 /* Help.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37579D5C27864F5F00FD0B98 /* Help.swift */; }; 3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; }; + 3759234628C26C7B00C052EC /* Refreshable+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3759234528C26C7B00C052EC /* Refreshable+Backport.swift */; }; 37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; }; 37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; }; 37599F32272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; }; @@ -500,6 +503,9 @@ 377ABC48286E5887009C986F /* Sequence+Unique.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377ABC47286E5887009C986F /* Sequence+Unique.swift */; }; 377ABC49286E5887009C986F /* Sequence+Unique.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377ABC47286E5887009C986F /* Sequence+Unique.swift */; }; 377ABC4A286E5887009C986F /* Sequence+Unique.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377ABC47286E5887009C986F /* Sequence+Unique.swift */; }; + 377E17142928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; }; + 377E17152928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; }; + 377E17162928265900894889 /* ListRowSeparator+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377E17132928265900894889 /* ListRowSeparator+Backport.swift */; }; 377F9F74294403770043F856 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 377F9F73294403770043F856 /* Cache */; }; 377F9F76294403880043F856 /* Cache in Frameworks */ = {isa = PBXBuildFile; productRef = 377F9F75294403880043F856 /* Cache */; }; 377F9F7B294403F20043F856 /* VideosCacheModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377F9F7A294403F20043F856 /* VideosCacheModel.swift */; }; @@ -543,8 +549,12 @@ 3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; 3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; 3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; }; + 378AE93A274EDFAF006A4EE1 /* Badge+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */; }; 378AE93C274EDFB2006A4EE1 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBD274DA401005EA4D6 /* Backport.swift */; }; 378AE93D274EDFB3006A4EE1 /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBD274DA401005EA4D6 /* Backport.swift */; }; + 378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */; }; + 378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */; }; + 378AE940274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */; }; 378AE943274EF00A006A4EE1 /* Color+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378AE942274EF00A006A4EE1 /* Color+Background.swift */; }; 378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378AE942274EF00A006A4EE1 /* Color+Background.swift */; }; 378AE945274EF00A006A4EE1 /* Color+Background.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378AE942274EF00A006A4EE1 /* Color+Background.swift */; }; @@ -826,6 +836,14 @@ 37DD9DA32785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; }; 37DD9DA42785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; }; 37DD9DA52785BBC900539416 /* NoCommentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DA22785BBC900539416 /* NoCommentsView.swift */; }; + 37DD9DB12785D58D00539416 /* RefreshControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DAF2785D58D00539416 /* RefreshControl.swift */; }; + 37DD9DB42785D58D00539416 /* RefreshControlModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DB02785D58D00539416 /* RefreshControlModifier.swift */; }; + 37DD9DBA2785D60300539416 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DB82785D60200539416 /* FramePreferenceKey.swift */; }; + 37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DB82785D60200539416 /* FramePreferenceKey.swift */; }; + 37DD9DBC2785D60300539416 /* FramePreferenceKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DB82785D60200539416 /* FramePreferenceKey.swift */; }; + 37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DB92785D60200539416 /* ScrollViewMatcher.swift */; }; + 37DD9DC62785D63A00539416 /* UIResponder+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DC22785D63A00539416 /* UIResponder+Extensions.swift */; }; + 37DD9DCB2785E28C00539416 /* UIView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD9DC12785D63A00539416 /* UIView+Extensions.swift */; }; 37E04C0F275940FB00172673 /* VerticalScrollingFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */; }; 37E084AC2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; }; 37E084AD2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; }; @@ -1065,7 +1083,9 @@ 371CC76F29468BDC00979C1A /* SettingsButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButtons.swift; sourceTree = ""; }; 371CC7732946963000979C1A /* ListingStyleButtons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListingStyleButtons.swift; sourceTree = ""; }; 371F2F19269B43D300E4A7AB /* NavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModel.swift; sourceTree = ""; }; + 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Badge+Backport.swift"; sourceTree = ""; }; 3722AEBD274DA401005EA4D6 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; + 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tint+Backport.swift"; sourceTree = ""; }; 37270F1B28E06E3E00856150 /* String+Localizable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Localizable.swift"; sourceTree = ""; }; 3728203F2945E4A8009A0E2D /* SubscriptionsPageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsPageButton.swift; sourceTree = ""; }; 3729037D2739E47400EA99F6 /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = ""; }; @@ -1124,6 +1144,7 @@ 3756C2A52861131100E4B059 /* NetworkState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkState.swift; sourceTree = ""; }; 3756C2A92861151C00E4B059 /* NetworkStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkStateModel.swift; sourceTree = ""; }; 37579D5C27864F5F00FD0B98 /* Help.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Help.swift; sourceTree = ""; }; + 3759234528C26C7B00C052EC /* Refreshable+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Refreshable+Backport.swift"; sourceTree = ""; }; 37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = ""; }; 37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = ""; }; 37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = ""; }; @@ -1185,6 +1206,7 @@ 377ABC3F286E4AD5009C986F /* InstancesManifest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InstancesManifest.swift; sourceTree = ""; }; 377ABC43286E4B74009C986F /* ManifestedInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManifestedInstance.swift; sourceTree = ""; }; 377ABC47286E5887009C986F /* Sequence+Unique.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Sequence+Unique.swift"; sourceTree = ""; }; + 377E17132928265900894889 /* ListRowSeparator+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ListRowSeparator+Backport.swift"; sourceTree = ""; }; 377F9F7A294403F20043F856 /* VideosCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideosCacheModel.swift; sourceTree = ""; }; 377F9F7E2944175F0043F856 /* FeedCacheModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedCacheModel.swift; sourceTree = ""; }; 377FF88A291A60310028EB0B /* OpenVideosModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenVideosModel.swift; sourceTree = ""; }; @@ -1321,6 +1343,12 @@ 37DCD3162A191A180059A470 /* AVPlayerViewController+FullScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AVPlayerViewController+FullScreen.swift"; sourceTree = ""; }; 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = ""; }; 37DD9DA22785BBC900539416 /* NoCommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoCommentsView.swift; sourceTree = ""; }; + 37DD9DAF2785D58D00539416 /* RefreshControl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControl.swift; sourceTree = ""; }; + 37DD9DB02785D58D00539416 /* RefreshControlModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RefreshControlModifier.swift; sourceTree = ""; }; + 37DD9DB82785D60200539416 /* FramePreferenceKey.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramePreferenceKey.swift; sourceTree = ""; }; + 37DD9DB92785D60200539416 /* ScrollViewMatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollViewMatcher.swift; sourceTree = ""; }; + 37DD9DC12785D63A00539416 /* UIView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIView+Extensions.swift"; sourceTree = ""; }; + 37DD9DC22785D63A00539416 /* UIResponder+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIResponder+Extensions.swift"; sourceTree = ""; }; 37E04C0E275940FB00172673 /* VerticalScrollingFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalScrollingFix.swift; sourceTree = ""; }; 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsNavigationLink.swift; sourceTree = ""; }; 37E64DD026D597EB00C71877 /* SubscribedChannelsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscribedChannelsModel.swift; sourceTree = ""; }; @@ -1651,9 +1679,13 @@ isa = PBXGroup; children = ( 3722AEBD274DA401005EA4D6 /* Backport.swift */, + 3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */, + 377E17132928265900894889 /* ListRowSeparator+Backport.swift */, 37136CAB286273060095C0CF /* PersistentSystemOverlays+Backport.swift */, + 3759234528C26C7B00C052EC /* Refreshable+Backport.swift */, 37E80F3F287B472300561799 /* ScrollContentBackground+Backport.swift */, 376E331128AD3B320070E30C /* ScrollDismissesKeyboard+Backport.swift */, + 3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */, 37B7CFE82A19603B001B0564 /* ToolbarBackground+Backport.swift */, 37B7CFEA2A1960EC001B0564 /* ToolbarColorScheme+Backport.swift */, ); @@ -2135,6 +2167,44 @@ path = Model; sourceTree = ""; }; + 37DD9DAE2785D58D00539416 /* RefreshControl */ = { + isa = PBXGroup; + children = ( + 37DD9DC02785D63A00539416 /* Extensions */, + 37DD9DB72785D60200539416 /* ScrollViewMatcher */, + 37DD9DAF2785D58D00539416 /* RefreshControl.swift */, + 37DD9DB02785D58D00539416 /* RefreshControlModifier.swift */, + ); + path = RefreshControl; + sourceTree = ""; + }; + 37DD9DB72785D60200539416 /* ScrollViewMatcher */ = { + isa = PBXGroup; + children = ( + 37DD9DB82785D60200539416 /* FramePreferenceKey.swift */, + 37DD9DB92785D60200539416 /* ScrollViewMatcher.swift */, + ); + path = ScrollViewMatcher; + sourceTree = ""; + }; + 37DD9DC02785D63A00539416 /* Extensions */ = { + isa = PBXGroup; + children = ( + 37DD9DC22785D63A00539416 /* UIResponder+Extensions.swift */, + 37DD9DC12785D63A00539416 /* UIView+Extensions.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + 37DD9DCC2785EE6F00539416 /* Vendor */ = { + isa = PBXGroup; + children = ( + 3749BF6C27ADA135000480FF /* mpv */, + 37DD9DAE2785D58D00539416 /* RefreshControl */, + ); + path = Vendor; + sourceTree = ""; + }; 37E6D79A2944ADCB00550C3D /* Subscriptions */ = { isa = PBXGroup; children = ( @@ -2778,10 +2848,12 @@ 37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */, 37D2E0D428B67EFC00F64D52 /* Delay.swift in Sources */, 3776925229463C310055EC18 /* PlaylistsCacheModel.swift in Sources */, + 3759234628C26C7B00C052EC /* Refreshable+Backport.swift in Sources */, 374924ED2921669B0017D862 /* PreferenceKeys.swift in Sources */, 37130A5B277657090033018A /* Yattee.xcdatamodeld in Sources */, 37152EEA26EFEB95004FB96D /* LazyView.swift in Sources */, 3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, + 37DD9DCB2785E28C00539416 /* UIView+Extensions.swift in Sources */, 377FF88F291A99580028EB0B /* HistoryView.swift in Sources */, 3782B94F27553A6700990149 /* SearchSuggestions.swift in Sources */, 378E50FF26FE8EEE00F49626 /* AccountViewButton.swift in Sources */, @@ -2816,6 +2888,7 @@ 37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */, 37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */, 37BC50A82778A84700510953 /* HistorySettings.swift in Sources */, + 37DD9DB12785D58D00539416 /* RefreshControl.swift in Sources */, 37BDFF1F29488117000C6404 /* ChannelPlaylistListItem.swift in Sources */, 371CC76C29466F5A00979C1A /* AccountsViewModel.swift in Sources */, 37B4E805277D0AB4004BF56A /* Orientation.swift in Sources */, @@ -2830,6 +2903,7 @@ 378AE943274EF00A006A4EE1 /* Color+Background.swift in Sources */, 37F4AE7226828F0900BD60EA /* VerticalCells.swift in Sources */, 376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */, + 3722AEBC274DA396005EA4D6 /* Badge+Backport.swift in Sources */, 3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */, 37599F34272B44000087F250 /* FavoritesModel.swift in Sources */, 3717407D2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */, @@ -2842,8 +2916,10 @@ 3751B4B227836902000B7DF4 /* SearchPage.swift in Sources */, 37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */, 37FFC440272734C3009FFD26 /* Throttle.swift in Sources */, + 37DD9DB42785D58D00539416 /* RefreshControlModifier.swift in Sources */, 3709528A29283E14001ECA40 /* NoDocumentsView.swift in Sources */, 3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */, + 378AE940274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */, 376BE50927347B5F009AD608 /* SettingsHeader.swift in Sources */, 376527BB285F60F700102284 /* PlayerTimeModel.swift in Sources */, 37270F1C28E06E3E00856150 /* String+Localizable.swift in Sources */, @@ -2891,6 +2967,7 @@ 37BC50AC2778BCBA00510953 /* HistoryModel.swift in Sources */, 37AAF29026740715007FC770 /* Channel.swift in Sources */, 37F5C7E02A1E2AF300927B73 /* ListView.swift in Sources */, + 37DD9DBA2785D60300539416 /* FramePreferenceKey.swift in Sources */, 3748186A26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */, 37B81AFF26D2CA3700675966 /* VideoDetails.swift in Sources */, 377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */, @@ -2939,6 +3016,7 @@ 37DCD3152A18F7630059A470 /* SafeAreaModel.swift in Sources */, 37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */, 37484C2526FC83E000287258 /* InstanceForm.swift in Sources */, + 37DD9DBD2785D60300539416 /* ScrollViewMatcher.swift in Sources */, 37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */, 371CC7742946963000979C1A /* ListingStyleButtons.swift in Sources */, 3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */, @@ -2948,8 +3026,10 @@ 373031F528383A89000CFD59 /* PiPDelegate.swift in Sources */, 37F5E8BA291BEF69006C15F5 /* BaseCacheModel.swift in Sources */, 371AC09F294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */, + 37DD9DC62785D63A00539416 /* UIResponder+Extensions.swift in Sources */, 370015A928BBAE7F000149FD /* ProgressBar.swift in Sources */, 37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, + 377E17142928265900894889 /* ListRowSeparator+Backport.swift in Sources */, 373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */, 37F7AB4D28A9361F00FB46B5 /* UIDevice+Cellular.swift in Sources */, 37141673267A8E10006CA35D /* Country.swift in Sources */, @@ -3029,6 +3109,7 @@ 374710062755291C00CE0F87 /* SearchTextField.swift in Sources */, 37B7CFEC2A197844001B0564 /* AppleAVPlayerView.swift in Sources */, 37F0F4EB286F397E00C06C2E /* SettingsModel.swift in Sources */, + 378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */, 379ACB4D2A1F8A4100E01914 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */, 37737786276F9858000521C1 /* Windows.swift in Sources */, @@ -3080,6 +3161,7 @@ 3784CDE327772EE40055BBF2 /* Watch.swift in Sources */, 371AC0B7294D1D6E0085989E /* PlayingIndicatorView.swift in Sources */, 37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */, + 37DD9DBB2785D60300539416 /* FramePreferenceKey.swift in Sources */, 375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */, 3705B183267B4E4900704544 /* TrendingCategory.swift in Sources */, 37D2E0D128B67DBC00F64D52 /* AnimationCompletionObserverModifier.swift in Sources */, @@ -3109,6 +3191,7 @@ 37EAD870267B9ED100D9E01B /* Segment.swift in Sources */, 3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */, 377FF88C291A60310028EB0B /* OpenVideosModel.swift in Sources */, + 378AE93A274EDFAF006A4EE1 /* Badge+Backport.swift in Sources */, 37599F35272B44000087F250 /* FavoritesModel.swift in Sources */, 376527BC285F60F700102284 /* PlayerTimeModel.swift in Sources */, 37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */, @@ -3170,6 +3253,7 @@ 3748186F26A769D60084E870 /* DetailBadge.swift in Sources */, 3744A96128B99ADD005DE0A7 /* PlayerControlsLayout.swift in Sources */, 372915E72687E3B900F5A35B /* Defaults.swift in Sources */, + 377E17152928265900894889 /* ListRowSeparator+Backport.swift in Sources */, 371CC76929466ED000979C1A /* AccountsView.swift in Sources */, 37C3A242272359900087A57A /* Double+Format.swift in Sources */, 37B795912771DAE0001CF27B /* OpenURLHandler.swift in Sources */, @@ -3357,6 +3441,7 @@ 375EC95F289EEEE000751258 /* QualityProfile.swift in Sources */, 37EAD871267B9ED100D9E01B /* Segment.swift in Sources */, 373C8FE6275B955100CB5936 /* CommentsPage.swift in Sources */, + 37DD9DBC2785D60300539416 /* FramePreferenceKey.swift in Sources */, 375EC974289F2ABF00751258 /* MultiselectRow.swift in Sources */, 37EBD8C827AF26B300F1C24B /* AVPlayerBackend.swift in Sources */, 378E9C3E2945565500B2D696 /* SubscriptionsView.swift in Sources */, @@ -3410,6 +3495,7 @@ 376BE50D27349108009AD608 /* BrowsingSettings.swift in Sources */, 37CFB48728AFE2510070024C /* VideoDescription.swift in Sources */, 37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */, + 378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */, 37FFC442272734C3009FFD26 /* Throttle.swift in Sources */, 377FF88D291A60310028EB0B /* OpenVideosModel.swift in Sources */, 37F4AD2828613B81004D0F66 /* Color+Debug.swift in Sources */, @@ -3550,6 +3636,7 @@ 37D9BA0829507F69002586BD /* PlayerControlsSettings.swift in Sources */, 37599F32272B42810087F250 /* FavoriteItem.swift in Sources */, 37E8B0EE27B326C00024006F /* TimelineView.swift in Sources */, + 3726386E2948A4B80043702D /* Badge+Backport.swift in Sources */, 37141675267A8E10006CA35D /* Country.swift in Sources */, 370F500C27CC1821001B35DC /* MPVViewController.swift in Sources */, 3782B9542755667600990149 /* String+Format.swift in Sources */, @@ -3587,6 +3674,7 @@ 3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */, 37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */, 3718B9A02921A9620003DB2E /* VideoDetailsOverlay.swift in Sources */, + 377E17162928265900894889 /* ListRowSeparator+Backport.swift in Sources */, 37FB28432721B22200A57617 /* ContentItem.swift in Sources */, 37D2E0D228B67DBC00F64D52 /* AnimationCompletionObserverModifier.swift in Sources */, 37AAF2A226741C97007FC770 /* FeedView.swift in Sources */, @@ -3931,7 +4019,7 @@ INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIStatusBarHidden = NO; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3981,7 +4069,7 @@ INFOPLIST_KEY_UIRequiresFullScreen = YES; INFOPLIST_KEY_UIStatusBarHidden = NO; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight UIInterfaceOrientationPortraitUpsideDown"; - IPHONEOS_DEPLOYMENT_TARGET = 15.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -4033,7 +4121,7 @@ "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.5; OTHER_LDFLAGS = "-Wl,-no_compact_unwind"; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; @@ -4073,7 +4161,7 @@ "@executable_path/../Frameworks", ); LIBRARY_SEARCH_PATHS = "$(inherited)"; - MACOSX_DEPLOYMENT_TARGET = 12.0; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.5; OTHER_LDFLAGS = "-Wl,-no_compact_unwind"; PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app; diff --git a/macOS/InstancesSettings.swift b/macOS/InstancesSettings.swift index a1ed7e55..9aecb948 100644 --- a/macOS/InstancesSettings.swift +++ b/macOS/InstancesSettings.swift @@ -38,7 +38,7 @@ struct InstancesSettings: View { if !selectedInstance.isNil, selectedInstance.app.supportsAccounts { SettingsHeader(text: "Accounts".localized()) - List(selection: $selectedAccount) { + let list = List(selection: $selectedAccount) { if selectedInstanceAccounts.isEmpty { Text("You have no accounts for this location") .foregroundColor(.secondary) @@ -69,7 +69,13 @@ struct InstancesSettings: View { .tag(account) } } - .listStyle(.inset(alternatesRowBackgrounds: true)) + + if #available(macOS 12.0, *) { + list + .listStyle(.inset(alternatesRowBackgrounds: true)) + } else { + list + } } if selectedInstance != nil, selectedInstance.app.hasFrontendURL { diff --git a/macOS/MPVOGLView.swift b/macOS/MPVOGLView.swift index 5dff6e28..76576761 100644 --- a/macOS/MPVOGLView.swift +++ b/macOS/MPVOGLView.swift @@ -4,6 +4,7 @@ final class MPVOGLView: NSView { override init(frame frameRect: CGRect) { super.init(frame: frameRect) autoresizingMask = [.width, .height] + wantsBestResolutionOpenGLSurface = true } @available(*, unavailable)