mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 05:23:41 +00:00
iOS 14/macOS Big Sur Support
This commit is contained in:
parent
696751e07c
commit
5ef89ac9f4
13
Backports/Backport.swift
Normal file
13
Backports/Backport.swift
Normal file
@ -0,0 +1,13 @@
|
||||
import SwiftUI
|
||||
|
||||
public struct Backport<Content> {
|
||||
public let content: Content
|
||||
|
||||
public init(_ content: Content) {
|
||||
self.content = content
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
var backport: Backport<Self> { Backport(self) }
|
||||
}
|
11
Backports/Badge+Backport.swift
Normal file
11
Backports/Badge+Backport.swift
Normal file
@ -0,0 +1,11 @@
|
||||
import SwiftUI
|
||||
|
||||
extension Backport where Content: View {
|
||||
@ViewBuilder func badge(_ count: Text) -> some View {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
content.badge(count)
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
11
Backports/Tint+Backport.swift
Normal file
11
Backports/Tint+Backport.swift
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
17
Extensions/Color+Background.swift
Normal file
17
Extensions/Color+Background.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import SwiftUI
|
||||
|
||||
extension Color {
|
||||
#if os(macOS)
|
||||
static let background = Color(NSColor.windowBackgroundColor)
|
||||
static let secondaryBackground = Color(NSColor.underPageBackgroundColor)
|
||||
static let tertiaryBackground = Color(NSColor.controlBackgroundColor)
|
||||
#elseif os(iOS)
|
||||
static let background = Color(UIColor.systemBackground)
|
||||
static let secondaryBackground = Color(UIColor.secondarySystemBackground)
|
||||
static let tertiaryBackground = Color(UIColor.tertiarySystemBackground)
|
||||
#else
|
||||
static let background = Color.black
|
||||
static let secondaryBackground = Color.black
|
||||
static let tertiaryBackground = Color.black
|
||||
#endif
|
||||
}
|
8
Extensions/NSTextField+FocusRingType.swift
Normal file
8
Extensions/NSTextField+FocusRingType.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import AppKit
|
||||
|
||||
extension NSTextField {
|
||||
override open var focusRingType: NSFocusRingType {
|
||||
get { .none }
|
||||
set {} // swiftlint:disable:this unused_setter_value
|
||||
}
|
||||
}
|
10
Extensions/String+Format.swift
Normal file
10
Extensions/String+Format.swift
Normal file
@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func replacingFirstOccurrence(of target: String, with replacement: String) -> String {
|
||||
guard let range = range(of: target) else {
|
||||
return self
|
||||
}
|
||||
return replacingCharacters(in: range, with: replacement)
|
||||
}
|
||||
}
|
@ -10,7 +10,21 @@ extension View {
|
||||
verticalEdgeBorder(.bottom, height: height, color: color)
|
||||
}
|
||||
|
||||
func borderLeading(width: Double, color: Color = Color(white: 0.7, opacity: 1)) -> some View {
|
||||
horizontalEdgeBorder(.leading, width: width, color: color)
|
||||
}
|
||||
|
||||
func borderTrailing(width: Double, color: Color = Color(white: 0.7, opacity: 1)) -> some View {
|
||||
horizontalEdgeBorder(.trailing, width: width, color: color)
|
||||
}
|
||||
|
||||
private func verticalEdgeBorder(_ edge: Alignment, height: Double, color: Color) -> some View {
|
||||
overlay(Rectangle().frame(width: nil, height: height, alignment: .top).foregroundColor(color), alignment: edge)
|
||||
overlay(Rectangle().frame(width: nil, height: height, alignment: .top)
|
||||
.foregroundColor(color), alignment: edge)
|
||||
}
|
||||
|
||||
private func horizontalEdgeBorder(_ edge: Alignment, width: Double, color: Color) -> some View {
|
||||
overlay(Rectangle().frame(width: width, height: nil, alignment: .leading)
|
||||
.foregroundColor(color), alignment: edge)
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ extension Video {
|
||||
thumbnails: Thumbnail.fixturesForAllQualities(videoId: id),
|
||||
live: false,
|
||||
upcoming: false,
|
||||
publishedAt: Date.now,
|
||||
publishedAt: Date(),
|
||||
likes: 37333,
|
||||
dislikes: 30,
|
||||
keywords: ["very", "cool", "video", "msfs 2020", "757", "747", "A380", "737-900", "MOD", "Zibo", "MD80", "MD11", "Rotate", "Laminar", "787", "A350", "MSFS", "MS2020", "Microsoft Flight Simulator", "Microsoft", "Flight", "Simulator", "SIM", "World", "Ortho", "Flying", "Boeing MAX"]
|
||||
|
@ -45,6 +45,7 @@ final class PlayerModel: ObservableObject {
|
||||
var accounts: AccountsModel
|
||||
|
||||
var composition = AVMutableComposition()
|
||||
var loadedCompositionAssets = [AVMediaType]()
|
||||
|
||||
private var currentArtwork: MPMediaItemArtwork?
|
||||
private var frequentTimeObserver: Any?
|
||||
@ -147,9 +148,7 @@ final class PlayerModel: ObservableObject {
|
||||
logger.info("composition audio asset: \(stream.audioAsset.url)")
|
||||
logger.info("composition video asset: \(stream.videoAsset.url)")
|
||||
|
||||
Task {
|
||||
await self.loadComposition(stream, of: video, preservingTime: preservingTime)
|
||||
}
|
||||
loadComposition(stream, of: video, preservingTime: preservingTime)
|
||||
}
|
||||
|
||||
updateCurrentArtwork()
|
||||
@ -228,46 +227,59 @@ final class PlayerModel: ObservableObject {
|
||||
_ stream: Stream,
|
||||
of video: Video,
|
||||
preservingTime: Bool = false
|
||||
) async {
|
||||
await loadCompositionAsset(stream.audioAsset, type: .audio, of: video)
|
||||
await loadCompositionAsset(stream.videoAsset, type: .video, of: video)
|
||||
|
||||
guard streamSelection == stream else {
|
||||
logger.critical("IGNORING LOADED")
|
||||
return
|
||||
}
|
||||
|
||||
insertPlayerItem(stream, for: video, preservingTime: preservingTime)
|
||||
) {
|
||||
loadedCompositionAssets = []
|
||||
loadCompositionAsset(stream.audioAsset, stream: stream, type: .audio, of: video, preservingTime: preservingTime)
|
||||
loadCompositionAsset(stream.videoAsset, stream: stream, type: .video, of: video, preservingTime: preservingTime)
|
||||
}
|
||||
|
||||
private func loadCompositionAsset(
|
||||
func loadCompositionAsset(
|
||||
_ asset: AVURLAsset,
|
||||
stream: Stream,
|
||||
type: AVMediaType,
|
||||
of video: Video
|
||||
) async {
|
||||
async let assetTracks = asset.loadTracks(withMediaType: type)
|
||||
of video: Video,
|
||||
preservingTime: Bool = false
|
||||
) {
|
||||
asset.loadValuesAsynchronously(forKeys: ["playable"]) { [weak self] in
|
||||
guard let self = self else {
|
||||
return
|
||||
}
|
||||
self.logger.info("loading \(type.rawValue) track")
|
||||
|
||||
logger.info("loading \(type.rawValue) track")
|
||||
guard let compositionTrack = composition.addMutableTrack(
|
||||
withMediaType: type,
|
||||
preferredTrackID: kCMPersistentTrackID_Invalid
|
||||
) else {
|
||||
logger.critical("composition \(type.rawValue) addMutableTrack FAILED")
|
||||
return
|
||||
let assetTracks = asset.tracks(withMediaType: type)
|
||||
|
||||
guard let compositionTrack = self.composition.addMutableTrack(
|
||||
withMediaType: type,
|
||||
preferredTrackID: kCMPersistentTrackID_Invalid
|
||||
) else {
|
||||
self.logger.critical("composition \(type.rawValue) addMutableTrack FAILED")
|
||||
return
|
||||
}
|
||||
|
||||
guard let assetTrack = assetTracks.first else {
|
||||
self.logger.critical("asset \(type.rawValue) track FAILED")
|
||||
return
|
||||
}
|
||||
|
||||
try! compositionTrack.insertTimeRange(
|
||||
CMTimeRange(start: .zero, duration: CMTime.secondsInDefaultTimescale(video.length)),
|
||||
of: assetTrack,
|
||||
at: .zero
|
||||
)
|
||||
|
||||
self.logger.critical("\(type.rawValue) LOADED")
|
||||
|
||||
guard self.streamSelection == stream else {
|
||||
self.logger.critical("IGNORING LOADED")
|
||||
return
|
||||
}
|
||||
|
||||
self.loadedCompositionAssets.append(type)
|
||||
|
||||
if self.loadedCompositionAssets.count == 2 {
|
||||
self.insertPlayerItem(stream, for: video, preservingTime: preservingTime)
|
||||
}
|
||||
}
|
||||
|
||||
guard let assetTrack = try? await assetTracks.first else {
|
||||
logger.critical("asset \(type.rawValue) track FAILED")
|
||||
return
|
||||
}
|
||||
|
||||
try! compositionTrack.insertTimeRange(
|
||||
CMTimeRange(start: .zero, duration: CMTime.secondsInDefaultTimescale(video.length)),
|
||||
of: assetTrack,
|
||||
at: .zero
|
||||
)
|
||||
|
||||
logger.critical("\(type.rawValue) LOADED")
|
||||
}
|
||||
|
||||
private func playerItem(_ stream: Stream) -> AVPlayerItem? {
|
||||
|
@ -10,6 +10,8 @@ final class SearchModel: ObservableObject {
|
||||
@Published var queryText = ""
|
||||
@Published var querySuggestions = Store<[String]>()
|
||||
|
||||
@Published var fieldIsFocused = false
|
||||
|
||||
private var previousResource: Resource?
|
||||
private var resource: Resource!
|
||||
|
||||
@ -80,6 +82,10 @@ final class SearchModel: ObservableObject {
|
||||
private var suggestionsDebounceTimer: Timer?
|
||||
|
||||
func loadSuggestions(_ query: String) {
|
||||
guard !query.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
suggestionsDebounceTimer?.invalidate()
|
||||
|
||||
suggestionsDebounceTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { _ in
|
||||
|
57
README.md
57
README.md
@ -1,11 +1,11 @@
|
||||
![Yattee Banner](https://r.yattee.stream/icons/yattee-banner.png)
|
||||
|
||||
Video player with support for [Invidious](https://github.com/iv-org/invidious) and [Piped](https://github.com/TeamPiped/Piped) instances built for iOS 15, tvOS 15 and macOS Monterey.
|
||||
Video player for [Invidious](https://github.com/iv-org/invidious) and [Piped](https://github.com/TeamPiped/Piped) instances built for iOS, tvOS and macOS.
|
||||
|
||||
|
||||
[![AGPL v3](https://shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html)
|
||||
![GitHub issues](https://img.shields.io/github/issues/yattee/app)
|
||||
![GitHub pull requests](https://img.shields.io/github/issues-pr/yattee/app)
|
||||
[![GitHub issues](https://img.shields.io/github/issues/yattee/yattee)](https://github.com/yattee/yattee/issues)
|
||||
[![GitHub pull requests](https://img.shields.io/github/issues-pr/yattee/yattee)](https://github.com/yattee/yattee/pulls)
|
||||
[![Matrix](https://img.shields.io/matrix/yattee:matrix.org)](https://matrix.to/#/#yattee:matrix.org)
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ Video player with support for [Invidious](https://github.com/iv-org/invidious) a
|
||||
* Fullscreen playback, Picture in Picture and AirPlay support
|
||||
* Stream quality selection
|
||||
* Favorites: customizable section of channels, playlists, trending, searches and other views
|
||||
* URL Scheme for integrations
|
||||
* `yattee://` URL Scheme for integrations
|
||||
|
||||
### Availability
|
||||
| Feature | Invidious | Piped |
|
||||
@ -38,17 +38,24 @@ Video player with support for [Invidious](https://github.com/iv-org/invidious) a
|
||||
|
||||
## Installation
|
||||
### Requirements
|
||||
Only iOS/tvOS 15 and macOS Monterey are supported.
|
||||
System requirements:
|
||||
* iOS 14 (or newer)
|
||||
* tvOS 15 (or newer)
|
||||
* macOS Big Sur (or newer)
|
||||
|
||||
### How to install?
|
||||
#### [AltStore](https://altstore.io/)
|
||||
You can sideload IPA files that you can download from Releases page.
|
||||
Alternatively, if you have to access to the beta AltStore version (v1.5), you can add the following repository in `Browse > Sources` screen:
|
||||
#### [AltStore](https://altstore.io/) (free)
|
||||
You can sideload IPA files downloaded from the [Releases](https://github.com/yattee/yattee/releases) page to your iOS or tvOS device - check [AltStore FAQ](https://altstore.io/faq/) for more information.
|
||||
|
||||
If you have to access to the beta AltStore version (v1.5, for Patreons only), you can add the following repository in `Browse > Sources` screen:
|
||||
|
||||
`https://alt.yattee.stream`
|
||||
|
||||
#### Signing IPA files online (paid)
|
||||
[UDID Registrations](https://www.udidregistrations.com/) provides services to sign IPA files for your devices. Refer to: ***Break free from the App Store*** section of the website for more information.
|
||||
|
||||
#### Manual installation
|
||||
Download sources and compile them on a Mac using Xcode, install to your devices. Please note that if you are not registered in Apple Developer Program then the applications will require reinstalling every 7 days.
|
||||
Download sources and compile them on a Mac using Xcode, install to your devices. Please note that if you are not registered in Apple Developer Program you will need to reinstall every 7 days.
|
||||
|
||||
## Integrations
|
||||
### macOS
|
||||
@ -63,14 +70,6 @@ With [Finicky](https://github.com/johnste/finicky) you can configure your system
|
||||
}
|
||||
```
|
||||
|
||||
### Experimental: Safari
|
||||
macOS and iOS apps include Safari extension which will redirect opened YouTube tabs to the app.
|
||||
|
||||
### Expermiental: Firefox
|
||||
You can use [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect) extension to make the videos open in the app. In extension settings put the following URL as Invidious instance:
|
||||
|
||||
`https://r.yatte.stream`
|
||||
|
||||
## Screenshots
|
||||
### iOS
|
||||
| Player | Search | Playlists |
|
||||
@ -111,6 +110,30 @@ You can use [Privacy Redirect](https://github.com/SimonBrazell/privacy-redirect)
|
||||
* `Command+S` - Play Next
|
||||
* `Command+O` - Toggle Player
|
||||
|
||||
|
||||
## Donations
|
||||
|
||||
You can support development of this app with
|
||||
[Patreon](https://www.patreon.com/arekf) or cryptocurrencies:
|
||||
|
||||
**Monero (XMR)**
|
||||
```
|
||||
48zfKjLmnXs21PinU2ucMiUPwhiKt5d7WJKiy3ACVS28BKqSn52c1TX8L337oESHJ5TZCyGkozjfWZG11h6C46mN9n4NPrD
|
||||
```
|
||||
**Bitcoin (BTC)**
|
||||
```
|
||||
bc1qe24zz5a5hm0trc7glwckz93py274eycxzju3mv
|
||||
```
|
||||
**Ethereum (ETH)**
|
||||
```
|
||||
0xa2f81A58Ec5E550132F03615c8d91954A4E37423
|
||||
```
|
||||
|
||||
Donations will be used to cover development program access and domain renewal costs.
|
||||
|
||||
## Contributing
|
||||
If you're interestred in contributing, you can browse the [issues](https://github.com/yattee/yattee/issues) list or create a new one to discuss your feature idea. Every contribution is very welcome.
|
||||
|
||||
## License and Liability
|
||||
|
||||
Yattee and its components is shared on [AGPL v3](https://www.gnu.org/licenses/agpl-3.0.en.html) license.
|
||||
|
@ -51,7 +51,7 @@ struct FavoritesView: View {
|
||||
.navigationTitle("Favorites")
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.background(Color.tertiaryBackground)
|
||||
.frame(minWidth: 360)
|
||||
#endif
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ struct MenuCommands: Commands {
|
||||
}
|
||||
|
||||
private var navigationMenu: some Commands {
|
||||
CommandMenu("Navigation") {
|
||||
CommandGroup(before: .windowSize) {
|
||||
Button("Favorites") {
|
||||
model.navigation?.tabSelection = .favorites
|
||||
}
|
||||
@ -19,7 +19,7 @@ struct MenuCommands: Commands {
|
||||
Button("Subscriptions") {
|
||||
model.navigation?.tabSelection = .subscriptions
|
||||
}
|
||||
.disabled(!(model.accounts?.app.supportsSubscriptions ?? true))
|
||||
.disabled(subscriptionsDisabled)
|
||||
.keyboardShortcut("2")
|
||||
|
||||
Button("Popular") {
|
||||
@ -37,9 +37,17 @@ struct MenuCommands: Commands {
|
||||
model.navigation?.tabSelection = .search
|
||||
}
|
||||
.keyboardShortcut("f")
|
||||
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
private var subscriptionsDisabled: Bool {
|
||||
!(
|
||||
(model.accounts?.app.supportsSubscriptions ?? false) && model.accounts?.signedIn ?? false
|
||||
)
|
||||
}
|
||||
|
||||
private var playbackMenu: some Commands {
|
||||
CommandMenu("Playback") {
|
||||
Button((model.player?.isPlaying ?? true) ? "Pause" : "Play") {
|
||||
|
@ -1,26 +0,0 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct UnsubscribeAlertModifier: ViewModifier {
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.alert(unsubscribeAlertTitle, isPresented: $navigation.presentingUnsubscribeAlert) {
|
||||
if let channel = navigation.channelToUnsubscribe {
|
||||
Button("Unsubscribe", role: .destructive) {
|
||||
subscriptions.unsubscribe(channel.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unsubscribeAlertTitle: String {
|
||||
if let channel = navigation.channelToUnsubscribe {
|
||||
return "Unsubscribe from \(channel.name)"
|
||||
}
|
||||
|
||||
return "Unknown channel"
|
||||
}
|
||||
}
|
@ -15,13 +15,25 @@ struct AccountsMenuView: View {
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(model.current?.description ?? "Select Account", systemImage: "person.crop.circle")
|
||||
.labelStyle(.titleAndIcon)
|
||||
if #available(iOS 15.0, macOS 12.0, *) {
|
||||
label
|
||||
.labelStyle(.titleAndIcon)
|
||||
} else {
|
||||
HStack {
|
||||
Image(systemName: "person.crop.circle")
|
||||
label
|
||||
.labelStyle(.titleOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(instances.isEmpty)
|
||||
.transaction { t in t.animation = .none }
|
||||
}
|
||||
|
||||
private var label: some View {
|
||||
Label(model.current?.description ?? "Select Account", systemImage: "person.crop.circle")
|
||||
}
|
||||
|
||||
private var allAccounts: [Account] {
|
||||
accounts + instances.map(\.anonymousAccount)
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ struct AppSidebarPlaylists: View {
|
||||
LazyView(PlaylistVideosView(playlist))
|
||||
} label: {
|
||||
Label(playlist.title, systemImage: AppSidebarNavigation.symbolSystemImage(playlist.title))
|
||||
.backport
|
||||
.badge(Text("\(playlist.videos.count)"))
|
||||
}
|
||||
.id(playlist.id)
|
||||
|
@ -18,7 +18,6 @@ struct AppSidebarSubscriptions: View {
|
||||
navigation.presentUnsubscribeAlert(channel)
|
||||
}
|
||||
}
|
||||
.modifier(UnsubscribeAlertModifier())
|
||||
.id("channel\(channel.id)")
|
||||
}
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ struct AppTabNavigation: View {
|
||||
} else {
|
||||
trendingNavigationView
|
||||
}
|
||||
} else {
|
||||
trendingNavigationView
|
||||
}
|
||||
} else {
|
||||
if accounts.app.supportsPopular {
|
||||
@ -62,26 +64,7 @@ struct AppTabNavigation: View {
|
||||
}
|
||||
|
||||
NavigationView {
|
||||
LazyView(
|
||||
SearchView()
|
||||
.toolbar { toolbarContent }
|
||||
.searchable(text: $search.queryText, placement: .navigationBarDrawer(displayMode: .always)) {
|
||||
ForEach(search.querySuggestions.collection, id: \.self) { suggestion in
|
||||
Text(suggestion)
|
||||
.searchCompletion(suggestion)
|
||||
}
|
||||
}
|
||||
.onChange(of: search.queryText) { query in
|
||||
search.loadSuggestions(query)
|
||||
}
|
||||
.onSubmit(of: .search) {
|
||||
search.changeQuery { query in
|
||||
query.query = search.queryText
|
||||
}
|
||||
|
||||
recents.addQuery(search.queryText)
|
||||
}
|
||||
)
|
||||
LazyView(SearchView())
|
||||
}
|
||||
.tabItem {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
@ -129,7 +112,7 @@ struct AppTabNavigation: View {
|
||||
.toolbar { toolbarContent }
|
||||
}
|
||||
.tabItem {
|
||||
Label("Popular", systemImage: "chart.bar")
|
||||
Label("Popular", systemImage: "arrow.up.right.circle")
|
||||
.accessibility(label: Text("Popular"))
|
||||
}
|
||||
.tag(TabSelection.popular)
|
||||
@ -141,7 +124,7 @@ struct AppTabNavigation: View {
|
||||
.toolbar { toolbarContent }
|
||||
}
|
||||
.tabItem {
|
||||
Label("Trending", systemImage: "chart.line.uptrend.xyaxis")
|
||||
Label("Trending", systemImage: "chart.bar")
|
||||
.accessibility(label: Text("Trending"))
|
||||
}
|
||||
.tag(TabSelection.trending)
|
||||
|
@ -46,7 +46,7 @@ struct Sidebar: View {
|
||||
}
|
||||
|
||||
var mainNavigationLinks: some View {
|
||||
Section("Videos") {
|
||||
Section(header: Text("Videos")) {
|
||||
NavigationLink(destination: LazyView(FavoritesView()), tag: TabSelection.favorites, selection: $navigation.tabSelection) {
|
||||
Label("Favorites", systemImage: "heart")
|
||||
.accessibility(label: Text("Favorites"))
|
||||
@ -60,13 +60,13 @@ struct Sidebar: View {
|
||||
|
||||
if accounts.app.supportsPopular {
|
||||
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
|
||||
Label("Popular", systemImage: "chart.bar")
|
||||
Label("Popular", systemImage: "arrow.up.right.circle")
|
||||
.accessibility(label: Text("Popular"))
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) {
|
||||
Label("Trending", systemImage: "chart.line.uptrend.xyaxis")
|
||||
Label("Trending", systemImage: "chart.bar")
|
||||
.accessibility(label: Text("Trending"))
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,8 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct PlaybackBar: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
@ -64,18 +65,20 @@ struct PlaybackBar: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.alert(player.playerError?.localizedDescription ?? "", isPresented: $player.presentingErrorDetails) {
|
||||
Button("OK") {}
|
||||
.alert(isPresented: $player.presentingErrorDetails) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: Text(player.playerError?.localizedDescription ?? "")
|
||||
)
|
||||
}
|
||||
.environment(\.colorScheme, .dark)
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.padding(4)
|
||||
.background(.black)
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
}
|
||||
|
||||
private var closeButton: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Label(
|
||||
"Close",
|
||||
@ -105,10 +108,18 @@ struct PlaybackBar: View {
|
||||
return "less than a minute"
|
||||
}
|
||||
|
||||
let timeFinishAt = Date.now.addingTimeInterval(remainingSeconds)
|
||||
let timeFinishAtString = timeFinishAt.formatted(date: .omitted, time: .shortened)
|
||||
let timeFinishAt = Date().addingTimeInterval(remainingSeconds)
|
||||
|
||||
return "ends at \(timeFinishAtString)"
|
||||
return "ends at \(formattedTimeFinishAt(timeFinishAt))"
|
||||
}
|
||||
|
||||
private func formattedTimeFinishAt(_ date: Date) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
dateFormatter.dateStyle = .none
|
||||
dateFormatter.timeStyle = .short
|
||||
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
private var rateMenu: some View {
|
||||
|
@ -44,16 +44,18 @@ struct PlayerQueueView: View {
|
||||
}
|
||||
|
||||
ForEach(player.queue) { item in
|
||||
PlayerQueueRow(item: item, fullScreen: $fullScreen)
|
||||
let row = PlayerQueueRow(item: item, fullScreen: $fullScreen)
|
||||
.contextMenu {
|
||||
removeButton(item, history: false)
|
||||
removeAllButton(history: false)
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
row.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: false)
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
row
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,14 +65,18 @@ struct PlayerQueueView: View {
|
||||
if !player.history.isEmpty {
|
||||
Section(header: Text("Played Previously")) {
|
||||
ForEach(player.history) { item in
|
||||
PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen)
|
||||
let row = PlayerQueueRow(item: item, history: true, fullScreen: $fullScreen)
|
||||
.contextMenu {
|
||||
removeButton(item, history: true)
|
||||
removeAllButton(history: true)
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: true)
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
row.swipeActions(edge: .trailing, allowsFullSwipe: true) {
|
||||
removeButton(item, history: true)
|
||||
}
|
||||
} else {
|
||||
row
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -100,28 +106,44 @@ struct PlayerQueueView: View {
|
||||
}
|
||||
|
||||
private func removeButton(_ item: PlayerQueueItem, history: Bool) -> some View {
|
||||
Button(role: .destructive) {
|
||||
if history {
|
||||
player.removeHistory(item)
|
||||
} else {
|
||||
player.remove(item)
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
return Button(role: .destructive) {
|
||||
removeButtonAction(item, history: history)
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
} else {
|
||||
return Button {
|
||||
removeButtonAction(item, history: history)
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
} label: {
|
||||
Label("Remove", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
private func removeButtonAction(_ item: PlayerQueueItem, history: Bool) {
|
||||
_ = history ? player.removeHistory(item) : player.remove(item)
|
||||
}
|
||||
|
||||
private func removeAllButton(history: Bool) -> some View {
|
||||
Button(role: .destructive) {
|
||||
if history {
|
||||
player.removeHistoryItems()
|
||||
} else {
|
||||
player.removeQueueItems()
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
return Button(role: .destructive) {
|
||||
removeAllButtonAction(history: history)
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
} else {
|
||||
return Button {
|
||||
removeAllButtonAction(history: history)
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
} label: {
|
||||
Label("Remove All", systemImage: "trash.fill")
|
||||
}
|
||||
}
|
||||
|
||||
private func removeAllButtonAction(history: Bool) {
|
||||
_ = history ? player.removeHistoryItems() : player.removeQueueItems()
|
||||
}
|
||||
}
|
||||
|
||||
struct PlayerQueueView_Previews: PreviewProvider {
|
||||
|
@ -11,14 +11,14 @@ struct VideoDetails: View {
|
||||
@Binding var fullScreen: Bool
|
||||
|
||||
@State private var subscribed = false
|
||||
@State private var confirmationShown = false
|
||||
@State private var presentingUnsubscribeAlert = false
|
||||
@State private var presentingAddToPlaylist = false
|
||||
@State private var presentingShareSheet = false
|
||||
@State private var shareURL: URL?
|
||||
|
||||
@State private var currentPage = Page.details
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@ -82,7 +82,7 @@ struct VideoDetails: View {
|
||||
if fullScreen {
|
||||
fullScreen = false
|
||||
} else {
|
||||
self.dismiss()
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -184,19 +184,26 @@ struct VideoDetails: View {
|
||||
Section {
|
||||
if subscribed {
|
||||
Button("Unsubscribe") {
|
||||
confirmationShown = true
|
||||
presentingUnsubscribeAlert = true
|
||||
}
|
||||
#if os(iOS)
|
||||
.backport
|
||||
.tint(.gray)
|
||||
#endif
|
||||
.confirmationDialog("Are you you want to unsubscribe from \(video!.channel.name)?", isPresented: $confirmationShown) {
|
||||
Button("Unsubscribe") {
|
||||
subscriptions.unsubscribe(video!.channel.id)
|
||||
.alert(isPresented: $presentingUnsubscribeAlert) {
|
||||
Alert(
|
||||
title: Text(
|
||||
"Are you you want to unsubscribe from \(video!.channel.name)?"
|
||||
),
|
||||
primaryButton: .destructive(Text("Unsubscribe")) {
|
||||
subscriptions.unsubscribe(video!.channel.id)
|
||||
|
||||
withAnimation {
|
||||
subscribed.toggle()
|
||||
}
|
||||
}
|
||||
withAnimation {
|
||||
subscribed.toggle()
|
||||
}
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Button("Subscribe") {
|
||||
@ -206,12 +213,12 @@ struct VideoDetails: View {
|
||||
subscribed.toggle()
|
||||
}
|
||||
}
|
||||
.backport
|
||||
.tint(.blue)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 13))
|
||||
.buttonStyle(.borderless)
|
||||
.buttonBorderShape(.roundedRectangle)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,13 +248,13 @@ struct VideoDetails: View {
|
||||
Text(published)
|
||||
}
|
||||
|
||||
if let publishedAt = video.publishedAt {
|
||||
if let date = video.publishedAt {
|
||||
if video.publishedDate != nil {
|
||||
Text("•")
|
||||
.foregroundColor(.secondary)
|
||||
.opacity(0.3)
|
||||
}
|
||||
Text(publishedAt.formatted(date: .abbreviated, time: .omitted))
|
||||
Text(formattedPublishedAt(date))
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12))
|
||||
@ -257,6 +264,15 @@ struct VideoDetails: View {
|
||||
}
|
||||
}
|
||||
|
||||
func formattedPublishedAt(_ date: Date) -> String {
|
||||
let dateFormatter = DateFormatter()
|
||||
|
||||
dateFormatter.dateStyle = .short
|
||||
dateFormatter.timeStyle = .none
|
||||
|
||||
return dateFormatter.string(from: date)
|
||||
}
|
||||
|
||||
var countsSection: some View {
|
||||
Group {
|
||||
if let video = player.currentVideo {
|
||||
@ -338,11 +354,17 @@ struct VideoDetails: View {
|
||||
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
if let description = video.description {
|
||||
Text(description)
|
||||
.textSelection(.enabled)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.caption)
|
||||
.padding(.bottom, 4)
|
||||
Group {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
Text(description)
|
||||
.textSelection(.enabled)
|
||||
} else {
|
||||
Text(description)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.font(.caption)
|
||||
.padding(.bottom, 4)
|
||||
} else {
|
||||
Text("No description")
|
||||
.foregroundColor(.secondary)
|
||||
|
@ -16,8 +16,10 @@ struct VideoPlayerView: View {
|
||||
@State private var playerSize: CGSize = .zero
|
||||
@State private var fullScreen = false
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
#endif
|
||||
@ -82,11 +84,11 @@ struct VideoPlayerView: View {
|
||||
fullScreen = true
|
||||
}
|
||||
},
|
||||
down: { dismiss() }
|
||||
down: { presentationMode.wrappedValue.dismiss() }
|
||||
)
|
||||
#endif
|
||||
|
||||
.background(.black)
|
||||
.background(Color.black)
|
||||
|
||||
Group {
|
||||
#if os(iOS)
|
||||
@ -98,7 +100,7 @@ struct VideoPlayerView: View {
|
||||
VideoDetails(sidebarQueue: sidebarQueueBinding, fullScreen: $fullScreen)
|
||||
#endif
|
||||
}
|
||||
.background()
|
||||
.background(colorScheme == .dark ? Color.black : Color.white)
|
||||
.modifier(VideoDetailsPaddingModifier(geometry: geometry, aspectRatio: player.controller?.aspectRatio, fullScreen: fullScreen))
|
||||
}
|
||||
#endif
|
||||
|
@ -7,7 +7,7 @@ struct AddToPlaylistView: View {
|
||||
|
||||
@State private var selectedPlaylistID: Playlist.ID = ""
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@EnvironmentObject<PlaylistsModel> private var model
|
||||
|
||||
var body: some View {
|
||||
@ -37,7 +37,7 @@ struct AddToPlaylistView: View {
|
||||
.padding(.vertical)
|
||||
#elseif os(tvOS)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(.thickMaterial)
|
||||
.background(Color.tertiaryBackground)
|
||||
#else
|
||||
.padding(.vertical)
|
||||
#endif
|
||||
@ -70,7 +70,7 @@ struct AddToPlaylistView: View {
|
||||
|
||||
#if !os(tvOS)
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
#endif
|
||||
@ -155,7 +155,7 @@ struct AddToPlaylistView: View {
|
||||
Defaults[.lastUsedPlaylistID] = id
|
||||
|
||||
model.addVideo(playlistID: id, videoID: video.videoID) {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,9 +10,7 @@ struct PlaylistFormView: View {
|
||||
@State private var valid = false
|
||||
@State private var showingDeleteConfirmation = false
|
||||
|
||||
@FocusState private var focused: Bool
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<PlaylistsModel> private var playlists
|
||||
@ -22,77 +20,68 @@ struct PlaylistFormView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS) || os(iOS)
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
Text(editing ? "Edit Playlist" : "Create Playlist")
|
||||
.font(.title2.bold())
|
||||
Group {
|
||||
#if os(macOS) || os(iOS)
|
||||
VStack(alignment: .leading) {
|
||||
HStack(alignment: .center) {
|
||||
Text(editing ? "Edit Playlist" : "Create Playlist")
|
||||
.font(.title2.bold())
|
||||
|
||||
Spacer()
|
||||
Spacer()
|
||||
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
}.keyboardShortcut(.cancelAction)
|
||||
Button("Cancel") {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}.keyboardShortcut(.cancelAction)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Form {
|
||||
TextField("Name", text: $name, onCommit: validate)
|
||||
.frame(maxWidth: 450)
|
||||
.padding(.leading, 10)
|
||||
|
||||
visibilityFormItem
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
#if os(macOS)
|
||||
.padding(.horizontal)
|
||||
#endif
|
||||
|
||||
HStack {
|
||||
if editing {
|
||||
deletePlaylistButton
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Save", action: submitForm)
|
||||
.disabled(!valid)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
.frame(minHeight: 35)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.horizontal)
|
||||
|
||||
Form {
|
||||
TextField("Name", text: $name, onCommit: validate)
|
||||
.frame(maxWidth: 450)
|
||||
.padding(.leading, 10)
|
||||
.focused($focused)
|
||||
|
||||
visibilityFormItem
|
||||
.pickerStyle(.segmented)
|
||||
}
|
||||
#if os(macOS)
|
||||
.padding(.horizontal)
|
||||
#if os(iOS)
|
||||
.padding(.vertical)
|
||||
#else
|
||||
.frame(width: 400, height: 150)
|
||||
#endif
|
||||
|
||||
HStack {
|
||||
if editing {
|
||||
deletePlaylistButton
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Save", action: submitForm)
|
||||
.disabled(!valid)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
.frame(minHeight: 35)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.onChange(of: name) { _ in validate() }
|
||||
.onAppear(perform: initializeForm)
|
||||
#if os(iOS)
|
||||
.padding(.vertical)
|
||||
#else
|
||||
.frame(width: 400, height: 150)
|
||||
VStack {
|
||||
Group {
|
||||
header
|
||||
form
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(Color.tertiaryBackground)
|
||||
#endif
|
||||
|
||||
#else
|
||||
VStack {
|
||||
Group {
|
||||
header
|
||||
form
|
||||
}
|
||||
.frame(maxWidth: 1000)
|
||||
}
|
||||
.onAppear {
|
||||
guard editing else {
|
||||
return
|
||||
}
|
||||
|
||||
self.name = self.playlist.title
|
||||
self.visibility = self.playlist.visibility
|
||||
|
||||
validate()
|
||||
}
|
||||
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(.thickMaterial)
|
||||
#endif
|
||||
}
|
||||
.onChange(of: name) { _ in validate() }
|
||||
.onAppear(perform: initializeForm)
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
@ -152,16 +141,16 @@ struct PlaylistFormView: View {
|
||||
#endif
|
||||
|
||||
func initializeForm() {
|
||||
focused = true
|
||||
|
||||
guard editing else {
|
||||
return
|
||||
}
|
||||
|
||||
name = playlist.title
|
||||
visibility = playlist.visibility
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
name = playlist.title
|
||||
visibility = playlist.visibility
|
||||
|
||||
validate()
|
||||
validate()
|
||||
}
|
||||
}
|
||||
|
||||
func validate() {
|
||||
@ -182,7 +171,7 @@ struct PlaylistFormView: View {
|
||||
|
||||
playlists.load(force: true)
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,7 +183,7 @@ struct PlaylistFormView: View {
|
||||
#if os(macOS)
|
||||
Picker("Visibility", selection: $visibility) {
|
||||
ForEach(Playlist.Visibility.allCases) { visibility in
|
||||
Text(visibility.name)
|
||||
Text(visibility.name).tag(visibility)
|
||||
}
|
||||
}
|
||||
#else
|
||||
@ -216,9 +205,10 @@ struct PlaylistFormView: View {
|
||||
}
|
||||
|
||||
var deletePlaylistButton: some View {
|
||||
Button("Delete", role: .destructive) {
|
||||
Button("Delete") {
|
||||
showingDeleteConfirmation = true
|
||||
}.alert(isPresented: $showingDeleteConfirmation) {
|
||||
}
|
||||
.alert(isPresented: $showingDeleteConfirmation) {
|
||||
Alert(
|
||||
title: Text("Are you sure you want to delete playlist?"),
|
||||
message: Text("Playlist \"\(playlist.title)\" will be deleted.\nIt cannot be undone."),
|
||||
@ -226,16 +216,14 @@ struct PlaylistFormView: View {
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
#if os(macOS)
|
||||
.foregroundColor(.red)
|
||||
#endif
|
||||
}
|
||||
|
||||
func deletePlaylistAndDismiss() {
|
||||
accounts.api.playlist(playlist.id)?.request(.delete).onSuccess { _ in
|
||||
playlist = nil
|
||||
playlists.load(force: true)
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -71,17 +71,20 @@ struct PlaylistsView: View {
|
||||
ToolbarItemGroup {
|
||||
#if !os(iOS)
|
||||
if !model.isEmpty {
|
||||
selectPlaylistButton
|
||||
.prefersDefaultFocus(in: focusNamespace)
|
||||
if #available(macOS 12.0, *) {
|
||||
selectPlaylistButton
|
||||
.prefersDefaultFocus(in: focusNamespace)
|
||||
} else {
|
||||
selectPlaylistButton
|
||||
}
|
||||
}
|
||||
|
||||
if currentPlaylist != nil {
|
||||
editPlaylistButton
|
||||
}
|
||||
#endif
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(selectedPlaylistID)))
|
||||
|
||||
newPlaylistButton
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(selectedPlaylistID)))
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
@ -99,6 +102,8 @@ struct PlaylistsView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
newPlaylistButton
|
||||
|
||||
if currentPlaylist != nil {
|
||||
editPlaylistButton
|
||||
}
|
||||
@ -168,7 +173,7 @@ struct PlaylistsView: View {
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.background(Color.tertiaryBackground)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
80
Shared/Search/SearchField.swift
Normal file
80
Shared/Search/SearchField.swift
Normal file
@ -0,0 +1,80 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SearchTextField: View {
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
|
||||
@EnvironmentObject<RecentsModel> private var recents
|
||||
@EnvironmentObject<SearchModel> private var state
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
#if os(macOS)
|
||||
fieldBorder
|
||||
#endif
|
||||
|
||||
HStack(spacing: 0) {
|
||||
#if os(macOS)
|
||||
Image(systemName: "magnifyingglass")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fill)
|
||||
.frame(width: 12, height: 12)
|
||||
.padding(.horizontal, 8)
|
||||
.opacity(0.8)
|
||||
#endif
|
||||
TextField("Search...", text: $state.queryText) {
|
||||
state.changeQuery { query in query.query = state.queryText }
|
||||
recents.addQuery(state.queryText)
|
||||
}
|
||||
.onChange(of: state.queryText) { _ in
|
||||
if state.query.query.compare(state.queryText, options: .caseInsensitive) == .orderedSame {
|
||||
state.fieldIsFocused = true
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.textFieldStyle(.plain)
|
||||
#else
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.padding(.leading)
|
||||
.padding(.trailing, 15)
|
||||
#endif
|
||||
if !self.state.queryText.isEmpty {
|
||||
clearButton
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, navigationStyle == .tab ? 10 : 0)
|
||||
}
|
||||
|
||||
private var fieldBorder: some View {
|
||||
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
||||
.fill(Color.background)
|
||||
.frame(width: 250, height: 32)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 5, style: .continuous)
|
||||
.stroke(
|
||||
state.fieldIsFocused ? Color.blue.opacity(0.7) : Color.gray.opacity(0.4),
|
||||
lineWidth: state.fieldIsFocused ? 3 : 1
|
||||
)
|
||||
.frame(width: 250, height: 31)
|
||||
)
|
||||
}
|
||||
|
||||
private var clearButton: some View {
|
||||
Button(action: {
|
||||
self.state.queryText = ""
|
||||
}) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
#if os(macOS)
|
||||
.frame(width: 14, height: 14)
|
||||
#else
|
||||
.frame(width: 18, height: 18)
|
||||
#endif
|
||||
.padding(.trailing, 3)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.padding(.trailing, 10)
|
||||
.opacity(0.7)
|
||||
}
|
||||
}
|
77
Shared/Search/SearchSuggestions.swift
Normal file
77
Shared/Search/SearchSuggestions.swift
Normal file
@ -0,0 +1,77 @@
|
||||
import SwiftUI
|
||||
|
||||
struct SearchSuggestions: View {
|
||||
@EnvironmentObject<RecentsModel> private var recents
|
||||
@EnvironmentObject<SearchModel> private var state
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
Button {
|
||||
state.changeQuery { query in
|
||||
query.query = state.queryText
|
||||
state.fieldIsFocused = false
|
||||
}
|
||||
|
||||
recents.addQuery(state.queryText)
|
||||
} label: {
|
||||
HStack(spacing: 5) {
|
||||
Label(state.queryText, systemImage: "magnifyingglass")
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.onHover(perform: onHover(_:))
|
||||
#endif
|
||||
|
||||
ForEach(visibleSuggestions, id: \.self) { suggestion in
|
||||
Button {
|
||||
state.queryText = suggestion
|
||||
} label: {
|
||||
HStack(spacing: 0) {
|
||||
Label(state.queryText, systemImage: "arrow.up.left.circle")
|
||||
.lineLimit(1)
|
||||
.layoutPriority(2)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text(querySuffix(suggestion))
|
||||
.lineLimit(1)
|
||||
.layoutPriority(1)
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.onHover(perform: onHover(_:))
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.buttonStyle(.link)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var visibleSuggestions: [String] {
|
||||
state.querySuggestions.collection.filter {
|
||||
$0.compare(state.queryText, options: .caseInsensitive) != .orderedSame
|
||||
}
|
||||
}
|
||||
|
||||
private func querySuffix(_ suggestion: String) -> String {
|
||||
suggestion.replacingFirstOccurrence(of: state.queryText.lowercased(), with: "")
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private func onHover(_ inside: Bool) {
|
||||
if inside {
|
||||
NSCursor.pointingHand.push()
|
||||
} else {
|
||||
NSCursor.pop()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
struct SearchSuggestions_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SearchSuggestions()
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
@ -9,7 +9,6 @@ struct SearchView: View {
|
||||
@State private var searchDate = SearchQuery.Date.any
|
||||
@State private var searchDuration = SearchQuery.Duration.any
|
||||
|
||||
@State private var presentingClearConfirmation = false
|
||||
@State private var recentsChanged = false
|
||||
|
||||
#if os(tvOS)
|
||||
@ -31,50 +30,37 @@ struct SearchView: View {
|
||||
state.store.collection.sorted { $0 < $1 }
|
||||
}
|
||||
|
||||
init(_ query: SearchQuery? = nil, videos: [Video] = [Video]()) {
|
||||
init(_ query: SearchQuery? = nil, videos: [Video] = []) {
|
||||
self.query = query
|
||||
self.videos = videos
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
PlayerControlsView {
|
||||
VStack {
|
||||
if showRecentQueries {
|
||||
recentQueries
|
||||
} else {
|
||||
#if os(tvOS)
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
if accounts.app.supportsSearchFilters {
|
||||
filtersHorizontalStack
|
||||
}
|
||||
#if os(iOS)
|
||||
VStack {
|
||||
SearchTextField()
|
||||
|
||||
if let favoriteItem = favoriteItem {
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem.id)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: 25))
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalCells(items: items)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#else
|
||||
VerticalCells(items: items)
|
||||
#endif
|
||||
|
||||
if noResults {
|
||||
Text("No results")
|
||||
|
||||
if searchFiltersActive {
|
||||
Button("Reset search filters", action: resetFilters)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
if state.query.query != state.queryText, !state.queryText.isEmpty, !state.querySuggestions.collection.isEmpty {
|
||||
SearchSuggestions()
|
||||
} else {
|
||||
results
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
ZStack {
|
||||
results
|
||||
|
||||
if state.query.query != state.queryText, !state.queryText.isEmpty, !state.querySuggestions.collection.isEmpty {
|
||||
HStack {
|
||||
Spacer()
|
||||
SearchSuggestions()
|
||||
.borderLeading(width: 1, color: Color("ControlsBorderColor"))
|
||||
.frame(maxWidth: 280)
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.toolbar {
|
||||
#if !os(tvOS)
|
||||
@ -118,6 +104,10 @@ struct SearchView: View {
|
||||
if accounts.app.supportsSearchFilters {
|
||||
filtersMenu
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
SearchTextField()
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -132,10 +122,11 @@ struct SearchView: View {
|
||||
state.store.replace(ContentItem.array(of: videos))
|
||||
}
|
||||
}
|
||||
.searchable(text: $state.queryText, placement: searchFieldPlacement) {
|
||||
ForEach(state.querySuggestions.collection, id: \.self) { suggestion in
|
||||
Text(suggestion)
|
||||
.searchCompletion(suggestion)
|
||||
.onChange(of: state.query.query) { newQuery in
|
||||
if newQuery.isEmpty {
|
||||
favoriteItem = nil
|
||||
} else {
|
||||
updateFavoriteItem()
|
||||
}
|
||||
}
|
||||
.onChange(of: state.queryText) { newQuery in
|
||||
@ -161,11 +152,7 @@ struct SearchView: View {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.onSubmit(of: .search) {
|
||||
state.changeQuery { query in query.query = state.queryText }
|
||||
recents.addQuery(state.queryText)
|
||||
updateFavoriteItem()
|
||||
}
|
||||
|
||||
.onChange(of: searchSortOrder) { order in
|
||||
state.changeQuery { query in
|
||||
query.sortBy = order
|
||||
@ -185,19 +172,55 @@ struct SearchView: View {
|
||||
}
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.ignoresSafeArea(.keyboard, edges: .bottom)
|
||||
.navigationTitle("Search")
|
||||
#endif
|
||||
}
|
||||
|
||||
var searchFieldPlacement: SearchFieldPlacement {
|
||||
#if os(iOS)
|
||||
.navigationBarDrawer(displayMode: .always)
|
||||
#else
|
||||
.automatic
|
||||
.navigationBarHidden(true)
|
||||
#endif
|
||||
}
|
||||
|
||||
var toolbarPlacement: ToolbarItemPlacement {
|
||||
private var results: some View {
|
||||
VStack {
|
||||
if showRecentQueries {
|
||||
recentQueries
|
||||
} else {
|
||||
#if os(tvOS)
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
HStack(spacing: 0) {
|
||||
if accounts.app.supportsSearchFilters {
|
||||
filtersHorizontalStack
|
||||
}
|
||||
|
||||
if let favoriteItem = favoriteItem {
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem.id)
|
||||
.labelStyle(.iconOnly)
|
||||
.font(.system(size: 25))
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalCells(items: items)
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#else
|
||||
VerticalCells(items: items)
|
||||
#endif
|
||||
|
||||
if noResults {
|
||||
Text("No results")
|
||||
|
||||
if searchFiltersActive {
|
||||
Button("Reset search filters", action: resetFilters)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var toolbarPlacement: ToolbarItemPlacement {
|
||||
#if os(iOS)
|
||||
.bottomBar
|
||||
#else
|
||||
@ -205,25 +228,25 @@ struct SearchView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
fileprivate var showRecentQueries: Bool {
|
||||
private var showRecentQueries: Bool {
|
||||
navigationStyle == .tab && state.queryText.isEmpty
|
||||
}
|
||||
|
||||
fileprivate var filtersActive: Bool {
|
||||
private var filtersActive: Bool {
|
||||
searchDuration != .any || searchDate != .any
|
||||
}
|
||||
|
||||
fileprivate func resetFilters() {
|
||||
private func resetFilters() {
|
||||
searchSortOrder = .relevance
|
||||
searchDate = .any
|
||||
searchDuration = .any
|
||||
}
|
||||
|
||||
fileprivate var noResults: Bool {
|
||||
private var noResults: Bool {
|
||||
items.isEmpty && !state.isLoading && !state.query.isEmpty
|
||||
}
|
||||
|
||||
var recentQueries: some View {
|
||||
private var recentQueries: some View {
|
||||
VStack {
|
||||
List {
|
||||
Section(header: Text("Recents")) {
|
||||
@ -237,22 +260,13 @@ struct SearchView: View {
|
||||
state.changeQuery { query in query.query = item.title }
|
||||
updateFavoriteItem()
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing) {
|
||||
deleteButton(item)
|
||||
}
|
||||
#elseif os(tvOS)
|
||||
.contextMenu {
|
||||
deleteButton(item)
|
||||
deleteAllButton
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.redrawOn(change: recentsChanged)
|
||||
|
||||
if !recentItems.isEmpty {
|
||||
clearAllButton
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
@ -260,37 +274,33 @@ struct SearchView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
func deleteButton(_ item: RecentItem) -> some View {
|
||||
Button(role: .destructive) {
|
||||
recents.close(item)
|
||||
recentsChanged.toggle()
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
var clearAllButton: some View {
|
||||
Button("Clear All", role: .destructive) {
|
||||
presentingClearConfirmation = true
|
||||
}
|
||||
.confirmationDialog("Clear All", isPresented: $presentingClearConfirmation) {
|
||||
Button("Clear All", role: .destructive) {
|
||||
recents.clearQueries()
|
||||
}
|
||||
private func deleteButton(_ item: RecentItem) -> some View {
|
||||
Button {
|
||||
recents.close(item)
|
||||
recentsChanged.toggle()
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
|
||||
var searchFiltersActive: Bool {
|
||||
private var deleteAllButton: some View {
|
||||
Button {
|
||||
recents.clearQueries()
|
||||
recentsChanged.toggle()
|
||||
} label: {
|
||||
Label("Delete All", systemImage: "trash.fill")
|
||||
}
|
||||
}
|
||||
|
||||
private var searchFiltersActive: Bool {
|
||||
searchDate != .any || searchDuration != .any
|
||||
}
|
||||
|
||||
var recentItems: [RecentItem] {
|
||||
private var recentItems: [RecentItem] {
|
||||
Defaults[.recentlyOpened].filter { $0.type == .query }.reversed()
|
||||
}
|
||||
|
||||
var searchSortOrderPicker: some View {
|
||||
private var searchSortOrderPicker: some View {
|
||||
Picker("Sort", selection: $searchSortOrder) {
|
||||
ForEach(SearchQuery.SortOrder.allCases) { sortOrder in
|
||||
Text(sortOrder.name).tag(sortOrder)
|
||||
@ -299,7 +309,7 @@ struct SearchView: View {
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
var searchSortOrderButton: some View {
|
||||
private var searchSortOrderButton: some View {
|
||||
Button(action: { self.searchSortOrder = self.searchSortOrder.next() }) { Text(self.searchSortOrder.name)
|
||||
.font(.system(size: 30))
|
||||
.padding(.horizontal)
|
||||
@ -315,7 +325,7 @@ struct SearchView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var searchDateButton: some View {
|
||||
private var searchDateButton: some View {
|
||||
Button(action: { self.searchDate = self.searchDate.next() }) {
|
||||
Text(self.searchDate.name)
|
||||
.font(.system(size: 30))
|
||||
@ -332,7 +342,7 @@ struct SearchView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var searchDurationButton: some View {
|
||||
private var searchDurationButton: some View {
|
||||
Button(action: { self.searchDuration = self.searchDuration.next() }) {
|
||||
Text(self.searchDuration.name)
|
||||
.font(.system(size: 30))
|
||||
@ -349,7 +359,7 @@ struct SearchView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var filtersHorizontalStack: some View {
|
||||
private var filtersHorizontalStack: some View {
|
||||
HStack {
|
||||
HStack(spacing: 30) {
|
||||
Text("Sort")
|
||||
@ -375,7 +385,7 @@ struct SearchView: View {
|
||||
.font(.system(size: 30))
|
||||
}
|
||||
#else
|
||||
var filtersMenu: some View {
|
||||
private var filtersMenu: some View {
|
||||
Menu(filtersActive ? "Filter: active" : "Filter") {
|
||||
Picker(selection: $searchDuration, label: Text("Duration")) {
|
||||
ForEach(SearchQuery.Duration.allCases) { duration in
|
@ -15,9 +15,7 @@ struct AccountForm: View {
|
||||
@State private var validationError: String?
|
||||
@State private var validationDebounce = Debounce()
|
||||
|
||||
@FocusState private var focused: Bool
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
@ -32,7 +30,7 @@ struct AccountForm: View {
|
||||
.padding(.vertical)
|
||||
#elseif os(tvOS)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(.thickMaterial)
|
||||
.background(Color.tertiaryBackground)
|
||||
#else
|
||||
.frame(width: 400, height: 145)
|
||||
#endif
|
||||
@ -46,7 +44,7 @@ struct AccountForm: View {
|
||||
Spacer()
|
||||
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
@ -68,7 +66,6 @@ struct AccountForm: View {
|
||||
formFields
|
||||
#endif
|
||||
}
|
||||
.onAppear(perform: initializeForm)
|
||||
.onChange(of: username) { _ in validate() }
|
||||
.onChange(of: password) { _ in validate() }
|
||||
}
|
||||
@ -76,24 +73,23 @@ struct AccountForm: View {
|
||||
var formFields: some View {
|
||||
Group {
|
||||
if !instance.app.accountsUsePassword {
|
||||
TextField("Name", text: $name, prompt: Text("Account Name (optional)"))
|
||||
.focused($focused)
|
||||
TextField("Name", text: $name)
|
||||
}
|
||||
|
||||
TextField("Username", text: $username, prompt: usernamePrompt)
|
||||
TextField(usernamePrompt, text: $username)
|
||||
|
||||
if instance.app.accountsUsePassword {
|
||||
SecureField("Password", text: $password, prompt: Text("Password"))
|
||||
SecureField("Password", text: $password)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var usernamePrompt: Text {
|
||||
var usernamePrompt: String {
|
||||
switch instance.app {
|
||||
case .invidious:
|
||||
return Text("SID Cookie")
|
||||
return "SID Cookie"
|
||||
default:
|
||||
return Text("Username")
|
||||
return "Username"
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,10 +117,6 @@ struct AccountForm: View {
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private func initializeForm() {
|
||||
focused = true
|
||||
}
|
||||
|
||||
private func validate() {
|
||||
isValid = false
|
||||
validationDebounce.invalidate()
|
||||
@ -151,7 +143,7 @@ struct AccountForm: View {
|
||||
let account = AccountsModel.add(instance: instance, name: name, username: username, password: password)
|
||||
selectedAccount?.wrappedValue = account
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
|
||||
private var validator: AccountValidator {
|
||||
|
31
Shared/Settings/AccountsNavigationLink.swift
Normal file
31
Shared/Settings/AccountsNavigationLink.swift
Normal file
@ -0,0 +1,31 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AccountsNavigationLink: View {
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
var instance: Instance
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(instance.longDescription) {
|
||||
InstanceSettings(instanceID: instance.id)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contextMenu {
|
||||
removeInstanceButton(instance)
|
||||
}
|
||||
}
|
||||
|
||||
private func removeInstanceButton(_ instance: Instance) -> some View {
|
||||
if #available(iOS 15.0, *) {
|
||||
return Button("Remove", role: .destructive) { removeAction(instance) }
|
||||
} else {
|
||||
return Button("Remove") { removeAction(instance) }
|
||||
}
|
||||
}
|
||||
|
||||
private func removeAction(_ instance: Instance) {
|
||||
if accounts.current?.instance == instance {
|
||||
accounts.setCurrent(nil)
|
||||
}
|
||||
InstancesModel.remove(instance)
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AccountsSettings: View {
|
||||
let instanceID: Instance.ID?
|
||||
|
||||
@State private var accountsChanged = false
|
||||
@State private var presentingAccountForm = false
|
||||
|
||||
@State private var frontendURL = ""
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var model
|
||||
@EnvironmentObject<InstancesModel> private var instances
|
||||
|
||||
var instance: Instance! {
|
||||
InstancesModel.find(instanceID)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if instance.app.hasFrontendURL {
|
||||
Section(header: Text("Frontend URL")) {
|
||||
TextField(
|
||||
"Frontend URL",
|
||||
text: $frontendURL,
|
||||
prompt: Text("To enable videos, channels and playlists sharing")
|
||||
)
|
||||
.onAppear {
|
||||
frontendURL = instance.frontendURL ?? ""
|
||||
}
|
||||
.onChange(of: frontendURL) { newValue in
|
||||
InstancesModel.setFrontendURL(instance, newValue)
|
||||
}
|
||||
.labelsHidden()
|
||||
.autocapitalization(.none)
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Accounts"), footer: sectionFooter) {
|
||||
if instance.app.supportsAccounts {
|
||||
accounts
|
||||
} else {
|
||||
Text("Accounts are not supported for the application of this instance")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.frame(maxWidth: 1000)
|
||||
#endif
|
||||
|
||||
.navigationTitle(instance.description)
|
||||
}
|
||||
|
||||
var accounts: some View {
|
||||
Group {
|
||||
ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in
|
||||
#if os(tvOS)
|
||||
Button(account.description) {}
|
||||
.contextMenu {
|
||||
Button("Remove", role: .destructive) { removeAccount(account) }
|
||||
Button("Cancel", role: .cancel) {}
|
||||
}
|
||||
#else
|
||||
Text(account.description)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button("Remove", role: .destructive) { removeAccount(account) }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.redrawOn(change: accountsChanged)
|
||||
|
||||
Button("Add account...") {
|
||||
presentingAccountForm = true
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) {
|
||||
AccountForm(instance: instance)
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
}
|
||||
|
||||
private var sectionFooter: some View {
|
||||
if !instance.app.supportsAccounts {
|
||||
return Text("")
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
return Text("Swipe to remove account")
|
||||
#else
|
||||
return Text("Tap and hold to remove account")
|
||||
.foregroundColor(.secondary)
|
||||
#endif
|
||||
}
|
||||
|
||||
private func removeAccount(_ account: Account) {
|
||||
AccountsModel.remove(account)
|
||||
accountsChanged.toggle()
|
||||
}
|
||||
}
|
@ -13,9 +13,7 @@ struct InstanceForm: View {
|
||||
@State private var validationError: String?
|
||||
@State private var validationDebounce = Debounce()
|
||||
|
||||
@FocusState private var nameFieldFocused: Bool
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
@ -30,12 +28,11 @@ struct InstanceForm: View {
|
||||
}
|
||||
.onChange(of: app) { _ in validate() }
|
||||
.onChange(of: url) { _ in validate() }
|
||||
.onAppear(perform: initializeForm)
|
||||
#if os(iOS)
|
||||
.padding(.vertical)
|
||||
#elseif os(tvOS)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(.thickMaterial)
|
||||
.background(Color.tertiaryBackground)
|
||||
#else
|
||||
.frame(width: 400, height: 190)
|
||||
#endif
|
||||
@ -49,7 +46,7 @@ struct InstanceForm: View {
|
||||
Spacer()
|
||||
|
||||
Button("Cancel") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
@ -80,10 +77,9 @@ struct InstanceForm: View {
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
TextField("Name", text: $name, prompt: Text("Instance Name (optional)"))
|
||||
.focused($nameFieldFocused)
|
||||
TextField("Name", text: $name)
|
||||
|
||||
TextField("API URL", text: $url, prompt: Text("https://invidious.home.net"))
|
||||
TextField("API URL", text: $url)
|
||||
|
||||
#if !os(macOS)
|
||||
.autocapitalization(.none)
|
||||
@ -138,10 +134,6 @@ struct InstanceForm: View {
|
||||
}
|
||||
}
|
||||
|
||||
func initializeForm() {
|
||||
nameFieldFocused = true
|
||||
}
|
||||
|
||||
func submitForm() {
|
||||
guard isValid else {
|
||||
return
|
||||
@ -149,7 +141,7 @@ struct InstanceForm: View {
|
||||
|
||||
savedInstanceID = InstancesModel.add(app: app, name: name, url: url).id
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
104
Shared/Settings/InstanceSettings.swift
Normal file
104
Shared/Settings/InstanceSettings.swift
Normal file
@ -0,0 +1,104 @@
|
||||
import SwiftUI
|
||||
|
||||
struct InstanceSettings: View {
|
||||
let instanceID: Instance.ID?
|
||||
|
||||
@State private var accountsChanged = false
|
||||
@State private var presentingAccountForm = false
|
||||
|
||||
@State private var frontendURL = ""
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var model
|
||||
@EnvironmentObject<InstancesModel> private var instances
|
||||
|
||||
var instance: Instance! {
|
||||
InstancesModel.find(instanceID)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
if instance.app.hasFrontendURL {
|
||||
Section(header: Text("Frontend URL")) {
|
||||
TextField(
|
||||
"Frontend URL",
|
||||
text: $frontendURL
|
||||
)
|
||||
.onAppear {
|
||||
frontendURL = instance.frontendURL ?? ""
|
||||
}
|
||||
.onChange(of: frontendURL) { newValue in
|
||||
InstancesModel.setFrontendURL(instance, newValue)
|
||||
}
|
||||
.labelsHidden()
|
||||
.autocapitalization(.none)
|
||||
.keyboardType(.URL)
|
||||
}
|
||||
}
|
||||
|
||||
Section(header: Text("Accounts"), footer: sectionFooter) {
|
||||
if instance.app.supportsAccounts {
|
||||
ForEach(InstancesModel.accounts(instanceID), id: \.self) { account in
|
||||
#if os(tvOS)
|
||||
Button(account.description) {}
|
||||
.contextMenu {
|
||||
Button("Remove") { removeAccount(account) }
|
||||
Button("Cancel", role: .cancel) {}
|
||||
}
|
||||
#else
|
||||
ZStack {
|
||||
NavigationLink(destination: EmptyView()) {
|
||||
EmptyView()
|
||||
}
|
||||
.disabled(true)
|
||||
.hidden()
|
||||
|
||||
HStack {
|
||||
Text(account.description)
|
||||
Spacer()
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Remove") { removeAccount(account) }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.redrawOn(change: accountsChanged)
|
||||
|
||||
Button("Add account...") {
|
||||
presentingAccountForm = true
|
||||
}
|
||||
.sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) {
|
||||
AccountForm(instance: instance)
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
} else {
|
||||
Text("Accounts are not supported for the application of this instance")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.frame(maxWidth: 1000)
|
||||
#elseif os(iOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
|
||||
.navigationTitle(instance.description)
|
||||
}
|
||||
|
||||
private var sectionFooter: some View {
|
||||
if !instance.app.supportsAccounts {
|
||||
return Text("")
|
||||
}
|
||||
|
||||
return Text("Tap and hold to remove account")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
private func removeAccount(_ account: Account) {
|
||||
AccountsModel.remove(account)
|
||||
accountsChanged.toggle()
|
||||
}
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct InstancesSettings: View {
|
||||
@Default(.instances) private var instances
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
@State private var selectedInstanceID: Instance.ID?
|
||||
@State private var selectedAccount: Account?
|
||||
|
||||
@State private var presentingInstanceForm = false
|
||||
@State private var savedFormInstanceID: Instance.ID?
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
Section(header: SettingsHeader(text: "Instances")) {
|
||||
ForEach(instances) { instance in
|
||||
Group {
|
||||
NavigationLink(instance.longDescription) {
|
||||
AccountsSettings(instanceID: instance.id)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
removeInstanceButton(instance)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
#else
|
||||
.contextMenu {
|
||||
removeInstanceButton(instance)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
addInstanceButton
|
||||
}
|
||||
#if os(iOS)
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
}
|
||||
.sheet(isPresented: $presentingInstanceForm) {
|
||||
InstanceForm(savedInstanceID: $savedFormInstanceID)
|
||||
}
|
||||
}
|
||||
|
||||
private var addInstanceButton: some View {
|
||||
Button("Add Instance...") {
|
||||
presentingInstanceForm = true
|
||||
}
|
||||
}
|
||||
|
||||
private func removeInstanceButton(_ instance: Instance) -> some View {
|
||||
Button("Remove", role: .destructive) {
|
||||
if accounts.current?.instance == instance {
|
||||
accounts.setCurrent(nil)
|
||||
}
|
||||
InstancesModel.remove(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct InstancesSettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
InstancesSettings()
|
||||
}
|
||||
.frame(width: 400, height: 270)
|
||||
}
|
||||
}
|
@ -9,8 +9,7 @@ struct ServicesSettings: View {
|
||||
Section(header: SettingsHeader(text: "SponsorBlock API")) {
|
||||
TextField(
|
||||
"SponsorBlock API Instance",
|
||||
text: $sponsorBlockInstance,
|
||||
prompt: Text("SponsorBlock API URL, leave blank to disable")
|
||||
text: $sponsorBlockInstance
|
||||
)
|
||||
.labelsHidden()
|
||||
#if !os(macOS)
|
||||
@ -21,7 +20,7 @@ struct ServicesSettings: View {
|
||||
|
||||
Section(header: SettingsHeader(text: "Categories to Skip")) {
|
||||
#if os(macOS)
|
||||
List(SponsorBlockAPI.categories, id: \.self) { category in
|
||||
let list = List(SponsorBlockAPI.categories, id: \.self) { category in
|
||||
SponsorBlockCategorySelectionRow(
|
||||
title: SponsorBlockAPI.categoryDescription(category) ?? "Unknown",
|
||||
selected: sponsorBlockCategories.contains(category)
|
||||
@ -29,7 +28,16 @@ struct ServicesSettings: View {
|
||||
toggleCategory(category, value: value)
|
||||
}
|
||||
}
|
||||
.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
|
||||
|
@ -10,11 +10,16 @@ struct SettingsView: View {
|
||||
#endif
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
#endif
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
@State private var presentingInstanceForm = false
|
||||
@State private var savedFormInstanceID: Instance.ID?
|
||||
|
||||
@Default(.instances) private var instances
|
||||
|
||||
var body: some View {
|
||||
#if os(macOS)
|
||||
TabView {
|
||||
@ -65,8 +70,14 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
InstancesSettings()
|
||||
.environmentObject(accounts)
|
||||
|
||||
Section(header: Text("Instances")) {
|
||||
ForEach(instances) { instance in
|
||||
AccountsNavigationLink(instance: instance)
|
||||
}
|
||||
addInstanceButton
|
||||
}
|
||||
|
||||
BrowsingSettings()
|
||||
PlaybackSettings()
|
||||
ServicesSettings()
|
||||
@ -76,7 +87,7 @@ struct SettingsView: View {
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
#if !os(tvOS)
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
#endif
|
||||
@ -87,11 +98,20 @@ struct SettingsView: View {
|
||||
.listStyle(.insetGrouped)
|
||||
#endif
|
||||
}
|
||||
.sheet(isPresented: $presentingInstanceForm) {
|
||||
InstanceForm(savedInstanceID: $savedFormInstanceID)
|
||||
}
|
||||
#if os(tvOS)
|
||||
.background(.black)
|
||||
.background(Color.black)
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
private var addInstanceButton: some View {
|
||||
Button("Add Instance...") {
|
||||
presentingInstanceForm = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
|
@ -9,51 +9,34 @@ struct TrendingCountry: View {
|
||||
@State private var query: String = ""
|
||||
@State private var selection: Country?
|
||||
|
||||
@FocusState var countryIsFocused
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
#if os(macOS)
|
||||
#if !os(tvOS)
|
||||
HStack {
|
||||
TextField("Country", text: $query, prompt: Text(TrendingCountry.prompt))
|
||||
.focused($countryIsFocused)
|
||||
if #available(iOS 15.0, macOS 12.0, *) {
|
||||
TextField("Country", text: $query, prompt: Text(TrendingCountry.prompt))
|
||||
} else {
|
||||
TextField(TrendingCountry.prompt, text: $query)
|
||||
}
|
||||
|
||||
Button("Done") { selectCountryAndDismiss() }
|
||||
.keyboardShortcut(.defaultAction)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
}
|
||||
.padding([.horizontal, .top])
|
||||
|
||||
countriesList
|
||||
#else
|
||||
NavigationView {
|
||||
countriesList
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .navigationBarLeading) {
|
||||
Button("Done") { selectCountryAndDismiss() }
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.navigationBarTitle("Trending Country", displayMode: .automatic)
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
countriesList
|
||||
}
|
||||
.onAppear {
|
||||
countryIsFocused = true
|
||||
}
|
||||
.onSubmit { selectCountryAndDismiss() }
|
||||
#if !os(macOS)
|
||||
.searchable(text: $query, placement: searchPlacement, prompt: Text(TrendingCountry.prompt))
|
||||
#endif
|
||||
#if os(tvOS)
|
||||
.background(.thinMaterial)
|
||||
.searchable(text: $query, placement: .automatic, prompt: Text(TrendingCountry.prompt))
|
||||
.background(Color.black)
|
||||
#endif
|
||||
}
|
||||
|
||||
var countriesList: some View {
|
||||
ScrollViewReader { _ in
|
||||
let list = ScrollViewReader { _ in
|
||||
List(store.collection, selection: $selection) { country in
|
||||
#if os(macOS)
|
||||
Text(country.name)
|
||||
@ -71,29 +54,29 @@ struct TrendingCountry: View {
|
||||
}
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||
.padding(.bottom, 5)
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
#if !os(macOS)
|
||||
var searchPlacement: SearchFieldPlacement {
|
||||
#if os(iOS)
|
||||
.navigationBarDrawer(displayMode: .always)
|
||||
return Group {
|
||||
#if os(macOS)
|
||||
if #available(macOS 12.0, *) {
|
||||
list
|
||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||
} else {
|
||||
list
|
||||
}
|
||||
#else
|
||||
.automatic
|
||||
list
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.padding(.bottom, 5)
|
||||
#endif
|
||||
}
|
||||
|
||||
func selectCountryAndDismiss(_ country: Country? = nil) {
|
||||
if let selected = country ?? selection {
|
||||
selectedCountry = selected
|
||||
}
|
||||
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,11 +172,12 @@ struct TrendingView: View {
|
||||
}
|
||||
|
||||
#else
|
||||
Picker("Category", selection: $category) {
|
||||
Picker(category.controlLabel, selection: $category) {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Text(category.controlLabel).tag(category)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.menu)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ struct VerticalCells: View {
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.background(Color.tertiaryBackground)
|
||||
.frame(minWidth: 360)
|
||||
#endif
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ struct VideoCell: View {
|
||||
@Environment(\.horizontalCells) private var horizontalCells
|
||||
#endif
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
@EnvironmentObject<ThumbnailsModel> private var thumbnails
|
||||
|
||||
@ -38,7 +39,13 @@ struct VideoCell: View {
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contentShape(RoundedRectangle(cornerRadius: 12))
|
||||
.contextMenu { VideoContextMenuView(video: video, playerNavigationLinkActive: $player.playerNavigationLinkActive) }
|
||||
.contextMenu {
|
||||
VideoContextMenuView(
|
||||
video: video,
|
||||
playerNavigationLinkActive: $player.playerNavigationLinkActive
|
||||
)
|
||||
.environmentObject(accounts)
|
||||
}
|
||||
}
|
||||
|
||||
var content: some View {
|
||||
@ -55,7 +62,7 @@ struct VideoCell: View {
|
||||
#endif
|
||||
}
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.background(Color.tertiaryBackground)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ struct ChannelPlaylistView: View {
|
||||
.navigationTitle(playlist.title)
|
||||
|
||||
#else
|
||||
.background(.thickMaterial)
|
||||
.background(Color.tertiaryBackground)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ struct ChannelVideosView: View {
|
||||
|
||||
@StateObject private var store = Store<Channel>()
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
#if os(iOS)
|
||||
@ -43,7 +43,7 @@ struct ChannelVideosView: View {
|
||||
}
|
||||
|
||||
var content: some View {
|
||||
VStack {
|
||||
let content = VStack {
|
||||
#if os(tvOS)
|
||||
HStack {
|
||||
Text(navigationTitle)
|
||||
@ -65,40 +65,43 @@ struct ChannelVideosView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
#endif
|
||||
|
||||
VerticalCells(items: videos)
|
||||
|
||||
#if !os(iOS)
|
||||
.prefersDefaultFocus(in: focusNamespace)
|
||||
#if os(iOS)
|
||||
VerticalCells(items: videos)
|
||||
#else
|
||||
if #available(macOS 12.0, *) {
|
||||
VerticalCells(items: videos)
|
||||
.prefersDefaultFocus(in: focusNamespace)
|
||||
} else {
|
||||
VerticalCells(items: videos)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
.environment(\.inChannelView, true)
|
||||
#if !os(iOS)
|
||||
.focusScope(focusNamespace)
|
||||
#endif
|
||||
|
||||
#if !os(tvOS)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
ShareButton(
|
||||
contentItem: contentItem,
|
||||
presentingShareSheet: $presentingShareSheet,
|
||||
shareURL: $shareURL
|
||||
)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .navigation) {
|
||||
ShareButton(
|
||||
contentItem: contentItem,
|
||||
presentingShareSheet: $presentingShareSheet,
|
||||
shareURL: $shareURL
|
||||
)
|
||||
}
|
||||
|
||||
ToolbarItem {
|
||||
HStack {
|
||||
Text("**\(store.item?.subscriptionsString ?? "loading")** subscribers")
|
||||
.foregroundColor(.secondary)
|
||||
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
|
||||
ToolbarItem {
|
||||
HStack {
|
||||
Text("**\(store.item?.subscriptionsString ?? "loading")** subscribers")
|
||||
.foregroundColor(.secondary)
|
||||
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
|
||||
|
||||
subscriptionToggleButton
|
||||
subscriptionToggleButton
|
||||
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
.background(.thickMaterial)
|
||||
.background(Color.tertiaryBackground)
|
||||
#endif
|
||||
#if os(iOS)
|
||||
.sheet(isPresented: $presentingShareSheet) {
|
||||
@ -107,7 +110,6 @@ struct ChannelVideosView: View {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
.modifier(UnsubscribeAlertModifier())
|
||||
.onAppear {
|
||||
if store.item.isNil {
|
||||
resource.addObserver(store)
|
||||
@ -115,6 +117,17 @@ struct ChannelVideosView: View {
|
||||
}
|
||||
}
|
||||
.navigationTitle(navigationTitle)
|
||||
|
||||
return Group {
|
||||
if #available(macOS 12.0, *) {
|
||||
content
|
||||
#if !os(iOS)
|
||||
.focusScope(focusNamespace)
|
||||
#endif
|
||||
} else {
|
||||
content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var resource: Resource {
|
||||
|
@ -26,8 +26,13 @@ struct DetailBadge: View {
|
||||
|
||||
struct DefaultStyleModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.background(.thinMaterial)
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
content
|
||||
.background(.thinMaterial)
|
||||
} else {
|
||||
content
|
||||
.background(Color.background)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,15 +1,15 @@
|
||||
import SwiftUI
|
||||
|
||||
struct OpenSettingsButton: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
#if !os(macOS)
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
#endif
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
let button = Button {
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
|
||||
#if os(macOS)
|
||||
NSApp.sendAction(Selector(("showPreferencesWindow:")), to: nil, from: nil)
|
||||
@ -19,7 +19,13 @@ struct OpenSettingsButton: View {
|
||||
} label: {
|
||||
Label("Open Settings", systemImage: "gearshape.2")
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
button
|
||||
.buttonStyle(.borderedProminent)
|
||||
} else {
|
||||
button
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ struct PlayerControlsView<Content: View>: View {
|
||||
}
|
||||
|
||||
private var controls: some View {
|
||||
HStack {
|
||||
let controls = HStack {
|
||||
Button(action: {
|
||||
model.presentingPlayer.toggle()
|
||||
}) {
|
||||
@ -92,14 +92,23 @@ struct PlayerControlsView<Content: View>: View {
|
||||
.padding(.horizontal)
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 55)
|
||||
.padding(.vertical, 0)
|
||||
.background(.ultraThinMaterial)
|
||||
.borderTop(height: 0.4, color: Color("PlayerControlsBorderColor"))
|
||||
.borderBottom(height: navigationStyle == .sidebar ? 0 : 0.4, color: Color("PlayerControlsBorderColor"))
|
||||
.borderTop(height: 0.4, color: Color("ControlsBorderColor"))
|
||||
.borderBottom(height: navigationStyle == .sidebar ? 0 : 0.4, color: Color("ControlsBorderColor"))
|
||||
#if !os(tvOS)
|
||||
.onSwipeGesture(up: {
|
||||
model.presentingPlayer = true
|
||||
})
|
||||
#endif
|
||||
|
||||
return Group {
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
controls
|
||||
.background(Material.ultraThinMaterial)
|
||||
} else {
|
||||
controls
|
||||
.background(Color.tertiaryBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var appVersion: String {
|
||||
|
@ -31,9 +31,6 @@ struct SubscriptionsView: View {
|
||||
FavoriteButton(item: FavoriteItem(section: .subscriptions))
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
loadResources(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func loadResources(force: Bool = false) {
|
||||
|
@ -113,7 +113,7 @@ struct VideoContextMenuView: View {
|
||||
private var subscriptionButton: some View {
|
||||
Group {
|
||||
if subscriptions.isSubscribing(video.channel.id) {
|
||||
Button(role: .destructive) {
|
||||
Button {
|
||||
#if os(tvOS)
|
||||
subscriptions.unsubscribe(video.channel.id)
|
||||
#else
|
||||
@ -143,7 +143,7 @@ struct VideoContextMenuView: View {
|
||||
}
|
||||
|
||||
func removeFromPlaylistButton(playlistID: String) -> some View {
|
||||
Button(role: .destructive) {
|
||||
Button {
|
||||
playlists.removeVideo(videoIndexID: video.indexID!, playlistID: playlistID)
|
||||
} label: {
|
||||
Label("Remove from playlist", systemImage: "text.badge.minus")
|
||||
|
@ -2,14 +2,14 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct WelcomeScreen: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.presentationMode) private var presentationMode
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
@Default(.accounts) private var allAccounts
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
let welcomeScreen = VStack {
|
||||
Spacer()
|
||||
|
||||
Text("Welcome")
|
||||
@ -26,7 +26,7 @@ struct WelcomeScreen: View {
|
||||
AccountSelectionView(showHeader: false)
|
||||
|
||||
Button {
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Text("Start")
|
||||
}
|
||||
@ -36,7 +36,7 @@ struct WelcomeScreen: View {
|
||||
#else
|
||||
AccountsMenuView()
|
||||
.onChange(of: accounts.current) { _ in
|
||||
dismiss()
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 280)
|
||||
@ -50,10 +50,16 @@ struct WelcomeScreen: View {
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.interactiveDismissDisabled()
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
.frame(minWidth: 400, minHeight: 400)
|
||||
#endif
|
||||
|
||||
if #available(iOS 15.0, macOS 12.0, tvOS 15.0, *) {
|
||||
welcomeScreen
|
||||
.interactiveDismissDisabled()
|
||||
} else {
|
||||
welcomeScreen
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,6 +77,8 @@
|
||||
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 */; };
|
||||
3729037E2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; };
|
||||
3729037F2739E47400EA99F6 /* MenuCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3729037D2739E47400EA99F6 /* MenuCommands.swift */; };
|
||||
372915E42687E33E00F5A35B /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 372915E32687E33E00F5A35B /* Defaults */; };
|
||||
@ -110,6 +112,8 @@
|
||||
3743CA52270F284F00E4D32B /* View+Borders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743CA51270F284F00E4D32B /* View+Borders.swift */; };
|
||||
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743CA51270F284F00E4D32B /* View+Borders.swift */; };
|
||||
3743CA54270F284F00E4D32B /* View+Borders.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743CA51270F284F00E4D32B /* View+Borders.swift */; };
|
||||
374710052755291C00CE0F87 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374710042755291C00CE0F87 /* SearchField.swift */; };
|
||||
374710062755291C00CE0F87 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 374710042755291C00CE0F87 /* SearchField.swift */; };
|
||||
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186526A7627F0084E870 /* Video+Fixtures.swift */; };
|
||||
3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186526A7627F0084E870 /* Video+Fixtures.swift */; };
|
||||
3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3748186526A7627F0084E870 /* Video+Fixtures.swift */; };
|
||||
@ -122,16 +126,14 @@
|
||||
37484C1926FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; };
|
||||
37484C1A26FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; };
|
||||
37484C1B26FC837400287258 /* PlaybackSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1826FC837400287258 /* PlaybackSettings.swift */; };
|
||||
37484C1D26FC83A400287258 /* InstancesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettings.swift */; };
|
||||
37484C1F26FC83A400287258 /* InstancesSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C1C26FC83A400287258 /* InstancesSettings.swift */; };
|
||||
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
|
||||
37484C2626FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
|
||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2426FC83E000287258 /* InstanceForm.swift */; };
|
||||
37484C2926FC83FF00287258 /* AccountForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountForm.swift */; };
|
||||
37484C2A26FC83FF00287258 /* AccountForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountForm.swift */; };
|
||||
37484C2B26FC83FF00287258 /* AccountForm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2826FC83FF00287258 /* AccountForm.swift */; };
|
||||
37484C2D26FC844700287258 /* AccountsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* AccountsSettings.swift */; };
|
||||
37484C2F26FC844700287258 /* AccountsSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* AccountsSettings.swift */; };
|
||||
37484C2D26FC844700287258 /* InstanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* InstanceSettings.swift */; };
|
||||
37484C2F26FC844700287258 /* InstanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C2C26FC844700287258 /* InstanceSettings.swift */; };
|
||||
37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
|
||||
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
|
||||
37484C3326FCB8F900287258 /* AccountValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37484C3026FCB8F900287258 /* AccountValidator.swift */; };
|
||||
@ -164,9 +166,6 @@
|
||||
3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
|
||||
3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
|
||||
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */; };
|
||||
3761AC0F26F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; };
|
||||
3761AC1026F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; };
|
||||
3761AC1126F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */; };
|
||||
3763495126DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; };
|
||||
3763495226DFF59D00B9A393 /* AppSidebarRecents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */; };
|
||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
||||
@ -269,12 +268,29 @@
|
||||
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||
377FC7ED267A0A0800A6BBAF /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7EC267A0A0800A6BBAF /* SwiftyJSON */; };
|
||||
377FC7F3267A0A0800A6BBAF /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 377FC7F2267A0A0800A6BBAF /* Logging */; };
|
||||
3782B94F27553A6700990149 /* SearchSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B94E27553A6700990149 /* SearchSuggestions.swift */; };
|
||||
3782B95027553A6700990149 /* SearchSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B94E27553A6700990149 /* SearchSuggestions.swift */; };
|
||||
3782B9522755667600990149 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B9512755667600990149 /* String+Format.swift */; };
|
||||
3782B9532755667600990149 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B9512755667600990149 /* String+Format.swift */; };
|
||||
3782B9542755667600990149 /* String+Format.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B9512755667600990149 /* String+Format.swift */; };
|
||||
3782B95627557E4E00990149 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||
3782B95727557E6E00990149 /* SearchSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B94E27553A6700990149 /* SearchSuggestions.swift */; };
|
||||
3782B95E2755858100990149 /* NSTextField+FocusRingType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3782B95C2755858100990149 /* NSTextField+FocusRingType.swift */; };
|
||||
3784B23B272894DA00B09468 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23A272894DA00B09468 /* ShareSheet.swift */; };
|
||||
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23C2728B85300B09468 /* ShareButton.swift */; };
|
||||
3784B23E2728B85300B09468 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23C2728B85300B09468 /* ShareButton.swift */; };
|
||||
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 */; };
|
||||
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378E50FA26FE8B9F00F49626 /* Instance.swift */; };
|
||||
378E50FC26FE8B9F00F49626 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378E50FA26FE8B9F00F49626 /* Instance.swift */; };
|
||||
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378E50FA26FE8B9F00F49626 /* Instance.swift */; };
|
||||
@ -294,8 +310,6 @@
|
||||
37A3B15F27255E7F000FB5EE /* images in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B15E27255E7F000FB5EE /* images */; };
|
||||
37A3B16127255E7F000FB5EE /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B16027255E7F000FB5EE /* manifest.json */; };
|
||||
37A3B16527255E7F000FB5EE /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B16427255E7F000FB5EE /* content.js */; };
|
||||
37A3B17027255E7F000FB5EE /* Open in Yattee (macOS).appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 37A3B15727255E7F000FB5EE /* Open in Yattee (macOS).appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
37A3B18F2725735F000FB5EE /* Open in Yattee (iOS).appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 37A3B1792725735F000FB5EE /* Open in Yattee (iOS).appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
37A3B194272574FB000FB5EE /* SafariWebExtensionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A3B15927255E7F000FB5EE /* SafariWebExtensionHandler.swift */; };
|
||||
37A3B19627257503000FB5EE /* content.js in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B16427255E7F000FB5EE /* content.js */; };
|
||||
37A3B1982725750B000FB5EE /* manifest.json in Resources */ = {isa = PBXBuildFile; fileRef = 37A3B16027255E7F000FB5EE /* manifest.json */; };
|
||||
@ -307,7 +321,6 @@
|
||||
37A9965F26D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||
37AAF27E26737323007FC770 /* PopularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27D26737323007FC770 /* PopularView.swift */; };
|
||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||
37AAF29026740715007FC770 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF28F26740715007FC770 /* Channel.swift */; };
|
||||
37AAF29126740715007FC770 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF28F26740715007FC770 /* Channel.swift */; };
|
||||
37AAF29226740715007FC770 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF28F26740715007FC770 /* Channel.swift */; };
|
||||
@ -452,6 +465,8 @@
|
||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||
37DD87C8271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */; };
|
||||
37E084AC2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
||||
37E084AD2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */; };
|
||||
37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E2EEAA270656EC00170416 /* PlayerControlsView.swift */; };
|
||||
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E2EEAA270656EC00170416 /* PlayerControlsView.swift */; };
|
||||
37E2EEAD270656EC00170416 /* PlayerControlsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E2EEAA270656EC00170416 /* PlayerControlsView.swift */; };
|
||||
@ -510,20 +525,6 @@
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
37A3B16E27255E7F000FB5EE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 37D4B0BD2671614700C925CA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 37A3B15627255E7F000FB5EE;
|
||||
remoteInfo = "Open in Yattee";
|
||||
};
|
||||
37A3B18D2725735F000FB5EE /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 37D4B0BD2671614700C925CA /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 37A3B1782725735F000FB5EE;
|
||||
remoteInfo = "Open in Yattee";
|
||||
};
|
||||
37D4B0D52671614900C925CA /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 37D4B0BD2671614700C925CA /* Project object */;
|
||||
@ -547,31 +548,6 @@
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
37A3B17127255E7F000FB5EE /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
37A3B17027255E7F000FB5EE /* Open in Yattee (macOS).appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
37A3B1932725735F000FB5EE /* Embed App Extensions */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 13;
|
||||
files = (
|
||||
37A3B18F2725735F000FB5EE /* Open in Yattee (iOS).appex in Embed App Extensions */,
|
||||
);
|
||||
name = "Embed App Extensions";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
3700155A271B0D4D0049C794 /* PipedAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PipedAPI.swift; sourceTree = "<group>"; };
|
||||
3700155E271B12DD0049C794 /* SiestaConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SiestaConfiguration.swift; sourceTree = "<group>"; };
|
||||
@ -585,6 +561,9 @@
|
||||
37169AA12729D98A0011DE61 /* InstancesBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesBridge.swift; sourceTree = "<group>"; };
|
||||
37169AA52729E2CC0011DE61 /* AccountsBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsBridge.swift; sourceTree = "<group>"; };
|
||||
371F2F19269B43D300E4A7AB /* NavigationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModel.swift; sourceTree = "<group>"; };
|
||||
3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Badge+Backport.swift"; sourceTree = "<group>"; };
|
||||
3722AEBD274DA401005EA4D6 /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = "<group>"; };
|
||||
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Tint+Backport.swift"; sourceTree = "<group>"; };
|
||||
3729037D2739E47400EA99F6 /* MenuCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuCommands.swift; sourceTree = "<group>"; };
|
||||
372915E52687E3B900F5A35B /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = "<group>"; };
|
||||
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingView.swift; sourceTree = "<group>"; };
|
||||
@ -598,14 +577,14 @@
|
||||
3743B86727216D3600261544 /* ChannelCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelCell.swift; sourceTree = "<group>"; };
|
||||
3743CA4D270EFE3400E4D32B /* PlayerQueueRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueRow.swift; sourceTree = "<group>"; };
|
||||
3743CA51270F284F00E4D32B /* View+Borders.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Borders.swift"; sourceTree = "<group>"; };
|
||||
374710042755291C00CE0F87 /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = "<group>"; };
|
||||
3748186526A7627F0084E870 /* Video+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Video+Fixtures.swift"; sourceTree = "<group>"; };
|
||||
3748186926A764FB0084E870 /* Thumbnail+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Thumbnail+Fixtures.swift"; sourceTree = "<group>"; };
|
||||
3748186D26A769D60084E870 /* DetailBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailBadge.swift; sourceTree = "<group>"; };
|
||||
37484C1826FC837400287258 /* PlaybackSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackSettings.swift; sourceTree = "<group>"; };
|
||||
37484C1C26FC83A400287258 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = "<group>"; };
|
||||
37484C2426FC83E000287258 /* InstanceForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceForm.swift; sourceTree = "<group>"; };
|
||||
37484C2826FC83FF00287258 /* AccountForm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountForm.swift; sourceTree = "<group>"; };
|
||||
37484C2C26FC844700287258 /* AccountsSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettings.swift; sourceTree = "<group>"; };
|
||||
37484C2C26FC844700287258 /* InstanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstanceSettings.swift; sourceTree = "<group>"; };
|
||||
37484C3026FCB8F900287258 /* AccountValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountValidator.swift; sourceTree = "<group>"; };
|
||||
374C053427242D9F009BDDBE /* ServicesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServicesSettings.swift; sourceTree = "<group>"; };
|
||||
374C053A2724614F009BDDBE /* PlayerTVMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerTVMenu.swift; sourceTree = "<group>"; };
|
||||
@ -618,7 +597,6 @@
|
||||
37599F37272B4D740087F250 /* FavoriteButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteButton.swift; sourceTree = "<group>"; };
|
||||
375DFB5726F9DA010013F468 /* InstancesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModel.swift; sourceTree = "<group>"; };
|
||||
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = "<group>"; };
|
||||
3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UnsubscribeAlertModifier.swift; sourceTree = "<group>"; };
|
||||
3763495026DFF59D00B9A393 /* AppSidebarRecents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarRecents.swift; sourceTree = "<group>"; };
|
||||
376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; };
|
||||
376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
|
||||
@ -634,9 +612,13 @@
|
||||
37732FF32703D32400F04329 /* Sidebar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar.swift; sourceTree = "<group>"; };
|
||||
3774122927387B6C00423605 /* InstancesModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesModelTests.swift; sourceTree = "<group>"; };
|
||||
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = "<group>"; };
|
||||
3782B94E27553A6700990149 /* SearchSuggestions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchSuggestions.swift; sourceTree = "<group>"; };
|
||||
3782B9512755667600990149 /* String+Format.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Format.swift"; sourceTree = "<group>"; };
|
||||
3782B95C2755858100990149 /* NSTextField+FocusRingType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTextField+FocusRingType.swift"; sourceTree = "<group>"; };
|
||||
3784B23A272894DA00B09468 /* ShareSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheet.swift; sourceTree = "<group>"; };
|
||||
3784B23C2728B85300B09468 /* ShareButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareButton.swift; sourceTree = "<group>"; };
|
||||
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.swift; sourceTree = "<group>"; };
|
||||
378AE942274EF00A006A4EE1 /* Color+Background.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Background.swift"; sourceTree = "<group>"; };
|
||||
378E50FA26FE8B9F00F49626 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.swift; sourceTree = "<group>"; };
|
||||
378E50FE26FE8EEE00F49626 /* AccountsMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsMenuView.swift; sourceTree = "<group>"; };
|
||||
37977582268922F600DD52A8 /* InvidiousAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InvidiousAPI.swift; sourceTree = "<group>"; };
|
||||
@ -710,7 +692,7 @@
|
||||
37D4B0D82671614900C925CA /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = "<group>"; };
|
||||
37D4B0DE2671614900C925CA /* Tests (macOS).xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests (macOS).xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
37D4B0E22671614900C925CA /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = "<group>"; };
|
||||
37D4B158267164AE00C925CA /* Yattee (tvOS).app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Yattee (tvOS).app"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
37D4B158267164AE00C925CA /* Yattee.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Yattee.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
37D4B15E267164AF00C925CA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
37D4B171267164B000C925CA /* Tests (tvOS).xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests (tvOS).xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
37D4B175267164B000C925CA /* YatteeUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YatteeUITests.swift; sourceTree = "<group>"; };
|
||||
@ -721,6 +703,7 @@
|
||||
37D526E22720B4BE00ED2F5E /* View+SwipeGesture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+SwipeGesture.swift"; sourceTree = "<group>"; };
|
||||
37D9169A27388A81002B1BAA /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
|
||||
37DD87C6271C9CFE0027CBF9 /* PlayerStreams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerStreams.swift; sourceTree = "<group>"; };
|
||||
37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsNavigationLink.swift; sourceTree = "<group>"; };
|
||||
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerControlsView.swift; sourceTree = "<group>"; };
|
||||
37E64DD026D597EB00C71877 /* SubscriptionsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionsModel.swift; sourceTree = "<group>"; };
|
||||
37E70922271CD43000D34DDE /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
|
||||
@ -909,7 +892,6 @@
|
||||
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */,
|
||||
37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */,
|
||||
37AAF27D26737323007FC770 /* PopularView.swift */,
|
||||
37AAF27F26737550007FC770 /* SearchView.swift */,
|
||||
3784B23C2728B85300B09468 /* ShareButton.swift */,
|
||||
376B2E0626F920D600B1D64D /* SignInRequiredView.swift */,
|
||||
37AAF29F26741C97007FC770 /* SubscriptionsView.swift */,
|
||||
@ -919,6 +901,16 @@
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3722AEBA274DA312005EA4D6 /* Backports */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3722AEBD274DA401005EA4D6 /* Backport.swift */,
|
||||
3722AEBB274DA396005EA4D6 /* Badge+Backport.swift */,
|
||||
3722AEBF274DAEB8005EA4D6 /* Tint+Backport.swift */,
|
||||
);
|
||||
path = Backports;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3743B864272169E200261544 /* Applications */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -976,11 +968,11 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37484C2826FC83FF00287258 /* AccountForm.swift */,
|
||||
37484C2C26FC844700287258 /* AccountsSettings.swift */,
|
||||
37E084AB2753D95F00039B7D /* AccountsNavigationLink.swift */,
|
||||
37732FEF2703A26300F04329 /* AccountValidationStatus.swift */,
|
||||
376BE50A27349108009AD608 /* BrowsingSettings.swift */,
|
||||
37484C2426FC83E000287258 /* InstanceForm.swift */,
|
||||
37484C1C26FC83A400287258 /* InstancesSettings.swift */,
|
||||
37484C2C26FC844700287258 /* InstanceSettings.swift */,
|
||||
37484C1826FC837400287258 /* PlaybackSettings.swift */,
|
||||
374C053427242D9F009BDDBE /* ServicesSettings.swift */,
|
||||
376BE50627347B57009AD608 /* SettingsHeader.swift */,
|
||||
@ -1002,7 +994,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */,
|
||||
3761AC0E26F0F9A600AA496F /* UnsubscribeAlertModifier.swift */,
|
||||
);
|
||||
path = Modifiers;
|
||||
sourceTree = "<group>";
|
||||
@ -1014,6 +1005,16 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3782B95527557A2400990149 /* Search */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
374710042755291C00CE0F87 /* SearchField.swift */,
|
||||
3782B94E27553A6700990149 /* SearchSuggestions.swift */,
|
||||
37AAF27F26737550007FC770 /* SearchView.swift */,
|
||||
);
|
||||
path = Search;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3788AC2126F683AB00F6BAA9 /* Favorites */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1067,8 +1068,8 @@
|
||||
37BE0BD826A214500092E2DB /* macOS */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37FD43E1270472060073EE42 /* Settings */,
|
||||
374C0542272496E4009BDDBE /* AppDelegate.swift */,
|
||||
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
|
||||
374108D0272B11B2006C5CC8 /* PictureInPictureDelegate.swift */,
|
||||
37BE0BDB26A2367F0092E2DB /* Player.swift */,
|
||||
37BE0BD926A214630092E2DB /* PlayerViewController.swift */,
|
||||
@ -1083,8 +1084,11 @@
|
||||
379775922689365600DD52A8 /* Array+Next.swift */,
|
||||
376578842685429C00D4EA09 /* CaseIterable+Next.swift */,
|
||||
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */,
|
||||
378AE942274EF00A006A4EE1 /* Color+Background.swift */,
|
||||
37C3A240272359900087A57A /* Double+Format.swift */,
|
||||
37BA794E26DC3E0E002A0235 /* Int+Format.swift */,
|
||||
3782B95C2755858100990149 /* NSTextField+FocusRingType.swift */,
|
||||
3782B9512755667600990149 /* String+Format.swift */,
|
||||
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */,
|
||||
3743CA51270F284F00E4D32B /* View+Borders.swift */,
|
||||
);
|
||||
@ -1098,6 +1102,7 @@
|
||||
37BE0BD826A214500092E2DB /* macOS */,
|
||||
37D4B159267164AE00C925CA /* tvOS */,
|
||||
37D4B0C12671614700C925CA /* Shared */,
|
||||
3722AEBA274DA312005EA4D6 /* Backports */,
|
||||
37D4B1B72672CFE300C925CA /* Model */,
|
||||
37C7A9022679058300E721B4 /* Extensions */,
|
||||
3748186426A762300084E870 /* Fixtures */,
|
||||
@ -1120,6 +1125,7 @@
|
||||
371AAE2326CEB9E800901972 /* Navigation */,
|
||||
371AAE2426CEBA4100901972 /* Player */,
|
||||
371AAE2626CEBF1600901972 /* Playlists */,
|
||||
3782B95527557A2400990149 /* Search */,
|
||||
37484C1726FC836500287258 /* Settings */,
|
||||
371AAE2526CEBF0B00901972 /* Trending */,
|
||||
371AAE2726CEBF4700901972 /* Videos */,
|
||||
@ -1145,7 +1151,7 @@
|
||||
37D4B0CF2671614900C925CA /* Yattee.app */,
|
||||
37D4B0D42671614900C925CA /* Tests (iOS).xctest */,
|
||||
37D4B0DE2671614900C925CA /* Tests (macOS).xctest */,
|
||||
37D4B158267164AE00C925CA /* Yattee (tvOS).app */,
|
||||
37D4B158267164AE00C925CA /* Yattee.app */,
|
||||
37D4B171267164B000C925CA /* Tests (tvOS).xctest */,
|
||||
37A3B15727255E7F000FB5EE /* Open in Yattee (macOS).appex */,
|
||||
37A3B1792725735F000FB5EE /* Open in Yattee (iOS).appex */,
|
||||
@ -1242,14 +1248,6 @@
|
||||
path = Search;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
37FD43E1270472060073EE42 /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37FD43DB270470B70073EE42 /* InstancesSettings.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -1295,12 +1293,10 @@
|
||||
37D4B0C52671614900C925CA /* Sources */,
|
||||
37D4B0C62671614900C925CA /* Frameworks */,
|
||||
37D4B0C72671614900C925CA /* Resources */,
|
||||
37A3B1932725735F000FB5EE /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
37A3B18E2725735F000FB5EE /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Yattee (iOS)";
|
||||
packageProductDependencies = (
|
||||
@ -1327,12 +1323,10 @@
|
||||
37D4B0CB2671614900C925CA /* Sources */,
|
||||
37D4B0CC2671614900C925CA /* Frameworks */,
|
||||
37D4B0CD2671614900C925CA /* Resources */,
|
||||
37A3B17127255E7F000FB5EE /* Embed App Extensions */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
37A3B16F27255E7F000FB5EE /* PBXTargetDependency */,
|
||||
);
|
||||
name = "Yattee (macOS)";
|
||||
packageProductDependencies = (
|
||||
@ -1419,7 +1413,7 @@
|
||||
3765917D27237D2A009F956E /* PINCache */,
|
||||
);
|
||||
productName = Yattee;
|
||||
productReference = 37D4B158267164AE00C925CA /* Yattee (tvOS).app */;
|
||||
productReference = 37D4B158267164AE00C925CA /* Yattee.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
};
|
||||
37D4B170267164B000C925CA /* Tests (tvOS) */ = {
|
||||
@ -1723,6 +1717,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
374710052755291C00CE0F87 /* SearchField.swift in Sources */,
|
||||
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||
37CC3F45270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
|
||||
@ -1737,6 +1732,7 @@
|
||||
37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */,
|
||||
37152EEA26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||
3761ABFD26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
||||
3782B94F27553A6700990149 /* SearchSuggestions.swift in Sources */,
|
||||
378E50FF26FE8EEE00F49626 /* AccountsMenuView.swift in Sources */,
|
||||
37169AA62729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||
37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */,
|
||||
@ -1756,8 +1752,10 @@
|
||||
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */,
|
||||
37A9965E26D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
||||
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 */,
|
||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||
@ -1765,7 +1763,9 @@
|
||||
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
||||
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
378AE940274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */,
|
||||
376BE50927347B5F009AD608 /* SettingsHeader.swift in Sources */,
|
||||
3722AEBE274DA401005EA4D6 /* Backport.swift in Sources */,
|
||||
3700155F271B12DD0049C794 /* SiestaConfiguration.swift in Sources */,
|
||||
37EAD86F267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
375168D62700FAFF008F96A6 /* Debounce.swift in Sources */,
|
||||
@ -1773,6 +1773,7 @@
|
||||
376578892685471400D4EA09 /* Playlist.swift in Sources */,
|
||||
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
3782B9522755667600990149 /* String+Format.swift in Sources */,
|
||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
37BF661F27308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||
3700155B271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
||||
@ -1786,7 +1787,6 @@
|
||||
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||
3748186A26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */,
|
||||
3761AC0F26F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */,
|
||||
37B81AFF26D2CA3700675966 /* VideoDetails.swift in Sources */,
|
||||
377FC7E5267A084E00A6BBAF /* SearchView.swift in Sources */,
|
||||
376578912685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
||||
@ -1795,7 +1795,7 @@
|
||||
374C053F272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||
37FB28412721B22200A57617 /* ContentItem.swift in Sources */,
|
||||
37C3A24D272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||
37484C2D26FC844700287258 /* AccountsSettings.swift in Sources */,
|
||||
37484C2D26FC844700287258 /* InstanceSettings.swift in Sources */,
|
||||
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||
37484C3126FCB8F900287258 /* AccountValidator.swift in Sources */,
|
||||
37319F0527103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||
@ -1830,6 +1830,7 @@
|
||||
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||
37E084AC2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */,
|
||||
37D526E32720B4BE00ED2F5E /* View+SwipeGesture.swift in Sources */,
|
||||
37732FF42703D32400F04329 /* Sidebar.swift in Sources */,
|
||||
37D4B19726717E1500C925CA /* Video.swift in Sources */,
|
||||
@ -1842,7 +1843,6 @@
|
||||
37CC3F50270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||
37E70923271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||
37484C1D26FC83A400287258 /* InstancesSettings.swift in Sources */,
|
||||
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
|
||||
37D4B0E42671614900C925CA /* YatteeApp.swift in Sources */,
|
||||
@ -1857,6 +1857,8 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
374710062755291C00CE0F87 /* SearchField.swift in Sources */,
|
||||
378AE93F274EDFB5006A4EE1 /* Tint+Backport.swift in Sources */,
|
||||
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
||||
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
|
||||
@ -1870,6 +1872,7 @@
|
||||
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
37C3A24E272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
||||
3782B95027553A6700990149 /* SearchSuggestions.swift in Sources */,
|
||||
371F2F1B269B43D300E4A7AB /* NavigationModel.swift in Sources */,
|
||||
37001564271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||
3761ABFE26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
||||
@ -1889,9 +1892,11 @@
|
||||
37484C1A26FC837400287258 /* PlaybackSettings.swift in Sources */,
|
||||
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
|
||||
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
|
||||
378AE944274EF00A006A4EE1 /* Color+Background.swift in Sources */,
|
||||
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||
378AE93A274EDFAF006A4EE1 /* Badge+Backport.swift in Sources */,
|
||||
37599F35272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||
@ -1900,6 +1905,7 @@
|
||||
378E510026FE8EEE00F49626 /* AccountsMenuView.swift in Sources */,
|
||||
37141670267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
376BE50727347B57009AD608 /* SettingsHeader.swift in Sources */,
|
||||
378AE93C274EDFB2006A4EE1 /* Backport.swift in Sources */,
|
||||
37152EEB26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||
377FC7E2267A084A00A6BBAF /* VideoCell.swift in Sources */,
|
||||
37CC3F51270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||
@ -1907,6 +1913,7 @@
|
||||
37169AA32729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
||||
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
||||
3782B9532755667600990149 /* String+Format.swift in Sources */,
|
||||
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||
@ -1935,7 +1942,6 @@
|
||||
37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
||||
37C0697F2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift in Sources */,
|
||||
37A9965B26D6F8CA006E3224 /* HorizontalCells.swift in Sources */,
|
||||
3761AC1026F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */,
|
||||
37732FF52703D32400F04329 /* Sidebar.swift in Sources */,
|
||||
379775942689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||
3748186726A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||
@ -1976,6 +1982,7 @@
|
||||
37B17DA1268A1F89006AEE9B /* VideoContextMenuView.swift in Sources */,
|
||||
3743B86927216D3600261544 /* ChannelCell.swift in Sources */,
|
||||
3748186B26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */,
|
||||
3782B95E2755858100990149 /* NSTextField+FocusRingType.swift in Sources */,
|
||||
37C3A252272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
||||
373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
|
||||
@ -2055,7 +2062,6 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */,
|
||||
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37CC3F52270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||
37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
@ -2071,6 +2077,7 @@
|
||||
37D4B1802671650A00C925CA /* YatteeApp.swift in Sources */,
|
||||
3748187026A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||
37A9965C26D6F8CA006E3224 /* HorizontalCells.swift in Sources */,
|
||||
3782B95727557E6E00990149 /* SearchSuggestions.swift in Sources */,
|
||||
37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */,
|
||||
376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */,
|
||||
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
@ -2080,10 +2087,12 @@
|
||||
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||
376BE50D27349108009AD608 /* BrowsingSettings.swift in Sources */,
|
||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||
378AE93E274EDFB4006A4EE1 /* Tint+Backport.swift in Sources */,
|
||||
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */,
|
||||
37BA794126DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
||||
37C0697C2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */,
|
||||
378AE93D274EDFB3006A4EE1 /* Backport.swift in Sources */,
|
||||
37C3A243272359900087A57A /* Double+Format.swift in Sources */,
|
||||
37AAF29226740715007FC770 /* Channel.swift in Sources */,
|
||||
37EAD86D267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
@ -2098,7 +2107,6 @@
|
||||
3743B86A27216D3600261544 /* ChannelCell.swift in Sources */,
|
||||
37B767DD2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
3761AC1126F0F9A600AA496F /* UnsubscribeAlertModifier.swift in Sources */,
|
||||
3730D8A02712E2B70020ED53 /* NowPlayingView.swift in Sources */,
|
||||
37169AA42729D98A0011DE61 /* InstancesBridge.swift in Sources */,
|
||||
37D4B18E26717B3800C925CA /* VideoCell.swift in Sources */,
|
||||
@ -2112,7 +2120,6 @@
|
||||
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
||||
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||
37484C1F26FC83A400287258 /* InstancesSettings.swift in Sources */,
|
||||
37EF5C242739D37B00B03725 /* MenuModel.swift in Sources */,
|
||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
||||
@ -2123,6 +2130,7 @@
|
||||
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||
3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||
37C3A253272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
||||
378AE945274EF00A006A4EE1 /* Color+Background.swift in Sources */,
|
||||
3743CA54270F284F00E4D32B /* View+Borders.swift in Sources */,
|
||||
371F2F1C269B43D300E4A7AB /* NavigationModel.swift in Sources */,
|
||||
37E2EEAD270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||
@ -2148,6 +2156,7 @@
|
||||
37FAE000272ED58000330459 /* EditFavorites.swift in Sources */,
|
||||
37599F32272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||
37141675267A8E10006CA35D /* Country.swift in Sources */,
|
||||
3782B9542755667600990149 /* String+Format.swift in Sources */,
|
||||
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||
37F49BA826CB0FCE00304AC0 /* PlaylistFormView.swift in Sources */,
|
||||
@ -2158,6 +2167,8 @@
|
||||
37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||
37599F36272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||
3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
37E084AD2753D95F00039B7D /* AccountsNavigationLink.swift in Sources */,
|
||||
3782B95627557E4E00990149 /* SearchView.swift in Sources */,
|
||||
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
||||
37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||
37FB28432721B22200A57617 /* ContentItem.swift in Sources */,
|
||||
@ -2166,7 +2177,7 @@
|
||||
372915E82687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */,
|
||||
3797758D2689345500DD52A8 /* Store.swift in Sources */,
|
||||
37484C2F26FC844700287258 /* AccountsSettings.swift in Sources */,
|
||||
37484C2F26FC844700287258 /* InstanceSettings.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -2181,16 +2192,6 @@
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
37A3B16F27255E7F000FB5EE /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 37A3B15627255E7F000FB5EE /* Open in Yattee (macOS) */;
|
||||
targetProxy = 37A3B16E27255E7F000FB5EE /* PBXContainerItemProxy */;
|
||||
};
|
||||
37A3B18E2725735F000FB5EE /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 37A3B1782725735F000FB5EE /* Open in Yattee (iOS) */;
|
||||
targetProxy = 37A3B18D2725735F000FB5EE /* PBXContainerItemProxy */;
|
||||
};
|
||||
37D4B0D62671614900C925CA /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
target = 37D4B0C82671614900C925CA /* Yattee (iOS) */;
|
||||
@ -2216,7 +2217,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -2229,7 +2230,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@ -2250,7 +2251,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Open in Yattee/Open in Yattee.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -2263,7 +2264,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@ -2282,7 +2283,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
@ -2294,7 +2295,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@ -2314,7 +2315,7 @@
|
||||
buildSettings = {
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++17";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "Open in Yattee/Info.plist";
|
||||
@ -2326,7 +2327,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
OTHER_LDFLAGS = (
|
||||
"-framework",
|
||||
SafariServices,
|
||||
@ -2474,11 +2475,10 @@
|
||||
37D4B0ED2671614900C925CA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -2488,12 +2488,12 @@
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = iphoneos;
|
||||
@ -2506,11 +2506,10 @@
|
||||
37D4B0EE2671614900C925CA /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -2520,12 +2519,12 @@
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = iphoneos;
|
||||
@ -2539,14 +2538,13 @@
|
||||
37D4B0F02671614900C925CA /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Shared/Yattee.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -2556,13 +2554,12 @@
|
||||
INFOPLIST_FILE = macOS/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.1;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = macosx;
|
||||
@ -2574,14 +2571,13 @@
|
||||
37D4B0F12671614900C925CA /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_ENTITLEMENTS = Shared/Yattee.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_APP_SANDBOX = YES;
|
||||
ENABLE_HARDENED_RUNTIME = YES;
|
||||
@ -2591,13 +2587,12 @@
|
||||
INFOPLIST_FILE = macOS/Info.plist;
|
||||
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.video";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||
INFOPLIST_KEY_NSMainStoryboardFile = Main;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 12.0;
|
||||
MARKETING_VERSION = 1.1;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.0;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = macosx;
|
||||
@ -2713,14 +2708,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = tvOS/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Yattee;
|
||||
INFOPLIST_KEY_CFBundleExecutable = "Yattee (Apple TV)";
|
||||
INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)";
|
||||
INFOPLIST_KEY_CFBundleVersion = 1;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@ -2729,9 +2723,9 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -2746,14 +2740,13 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = "App Icon & Top Shelf Image";
|
||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2;
|
||||
CURRENT_PROJECT_VERSION = 3;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
ENABLE_PREVIEWS = YES;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = tvOS/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = Yattee;
|
||||
INFOPLIST_KEY_CFBundleExecutable = "Yattee (Apple TV)";
|
||||
INFOPLIST_KEY_CFBundleName = "Yattee (Apple TV)";
|
||||
INFOPLIST_KEY_CFBundleVersion = 1;
|
||||
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
|
||||
@ -2762,9 +2755,9 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.1;
|
||||
MARKETING_VERSION = 1.2;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = stream.yattee.app;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PRODUCT_NAME = Yattee;
|
||||
SDKROOT = appletvos;
|
||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||
SWIFT_VERSION = 5.0;
|
||||
@ -2980,7 +2973,7 @@
|
||||
repositoryURL = "https://github.com/sindresorhus/Defaults";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 5.0.0;
|
||||
minimumVersion = 6.0.0;
|
||||
};
|
||||
};
|
||||
3765917827237D07009F956E /* XCRemoteSwiftPackageReference "PINCache" */ = {
|
||||
|
@ -15,8 +15,8 @@
|
||||
"repositoryURL": "https://github.com/sindresorhus/Defaults",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "63d93f97ad545c8bceb125a8a36175ea705f7cf5",
|
||||
"version": "5.0.0"
|
||||
"revision": "55f3302c3ab30a8760f10042d0ebc0a6907f865a",
|
||||
"version": "6.1.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -33,7 +33,7 @@
|
||||
"repositoryURL": "https://github.com/pinterest/PINCache",
|
||||
"state": {
|
||||
"branch": "master",
|
||||
"revision": "a16dae6cec4897a7a9710239e0cb50b6de935e7b",
|
||||
"revision": "046f67609085a7d73d27105d2be91729d139208f",
|
||||
"version": null
|
||||
}
|
||||
},
|
||||
|
@ -15,7 +15,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "37D4B157267164AE00C925CA"
|
||||
BuildableName = "Yattee (tvOS).app"
|
||||
BuildableName = "Yattee.app"
|
||||
BlueprintName = "Yattee (tvOS)"
|
||||
ReferencedContainer = "container:Yattee.xcodeproj">
|
||||
</BuildableReference>
|
||||
@ -65,7 +65,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "37D4B157267164AE00C925CA"
|
||||
BuildableName = "Yattee (tvOS).app"
|
||||
BuildableName = "Yattee.app"
|
||||
BlueprintName = "Yattee (tvOS)"
|
||||
ReferencedContainer = "container:Yattee.xcodeproj">
|
||||
</BuildableReference>
|
||||
@ -82,7 +82,7 @@
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "37D4B157267164AE00C925CA"
|
||||
BuildableName = "Yattee (tvOS).app"
|
||||
BuildableName = "Yattee.app"
|
||||
BlueprintName = "Yattee (tvOS)"
|
||||
ReferencedContainer = "container:Yattee.xcodeproj">
|
||||
</BuildableReference>
|
||||
|
@ -40,7 +40,7 @@ struct InstancesSettings: View {
|
||||
if !selectedInstance.isNil, selectedInstance.app.supportsAccounts {
|
||||
SettingsHeader(text: "Accounts")
|
||||
|
||||
List(selection: $selectedAccount) {
|
||||
let list = List(selection: $selectedAccount) {
|
||||
if selectedInstanceAccounts.isEmpty {
|
||||
Text("You have no accounts for this instance")
|
||||
.foregroundColor(.secondary)
|
||||
@ -51,7 +51,7 @@ struct InstancesSettings: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Remove", role: .destructive) {
|
||||
Button("Remove") {
|
||||
presentingAccountRemovalConfirmation = true
|
||||
}
|
||||
.foregroundColor(.red)
|
||||
@ -60,30 +60,40 @@ struct InstancesSettings: View {
|
||||
.tag(account)
|
||||
}
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to remove \(selectedAccount?.description ?? "") account?",
|
||||
isPresented: $presentingAccountRemovalConfirmation
|
||||
) {
|
||||
Button("Remove", role: .destructive) {
|
||||
AccountsModel.remove(selectedAccount!)
|
||||
}
|
||||
.alert(isPresented: $presentingAccountRemovalConfirmation) {
|
||||
Alert(
|
||||
title: Text(
|
||||
"Are you sure you want to remove \(selectedAccount?.description ?? "") account?"
|
||||
),
|
||||
message: Text("This cannot be undone"),
|
||||
primaryButton: .destructive(Text("Delete")) {
|
||||
AccountsModel.remove(selectedAccount!)
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
if #available(macOS 12.0, *) {
|
||||
list
|
||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||
} else {
|
||||
list
|
||||
}
|
||||
.listStyle(.inset(alternatesRowBackgrounds: true))
|
||||
}
|
||||
|
||||
if selectedInstance != nil, selectedInstance.app.hasFrontendURL {
|
||||
SettingsHeader(text: "Frontend URL")
|
||||
|
||||
TextField("Frontend URL", text: $frontendURL, prompt: Text("Frontend URL"))
|
||||
.onAppear {
|
||||
frontendURL = selectedInstance.frontendURL ?? ""
|
||||
TextField("Frontend URL", text: $frontendURL)
|
||||
.onChange(of: selectedInstance) { _ in
|
||||
frontendURL = selectedInstanceFrontendURL
|
||||
}
|
||||
.onChange(of: frontendURL) { newValue in
|
||||
InstancesModel.setFrontendURL(selectedInstance, newValue)
|
||||
}
|
||||
.labelsHidden()
|
||||
|
||||
Text("If provided, you can copy links from videos, channels and playlist")
|
||||
Text("Used to create links from videos, channels and playlist")
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
@ -105,23 +115,26 @@ struct InstancesSettings: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Remove Instance", role: .destructive) {
|
||||
Button("Remove Instance") {
|
||||
presentingInstanceRemovalConfirmation = true
|
||||
}
|
||||
.confirmationDialog(
|
||||
"Are you sure you want to remove \(selectedInstance!.longDescription) instance?",
|
||||
isPresented: $presentingInstanceRemovalConfirmation
|
||||
) {
|
||||
Button("Remove Instance", role: .destructive) {
|
||||
if accounts.current?.instance == selectedInstance {
|
||||
accounts.setCurrent(nil)
|
||||
}
|
||||
.alert(isPresented: $presentingInstanceRemovalConfirmation) {
|
||||
Alert(
|
||||
title: Text(
|
||||
"Are you sure you want to remove \(selectedInstance!.longDescription) instance?"
|
||||
),
|
||||
message: Text("This cannot be undone"),
|
||||
primaryButton: .destructive(Text("Remove")) {
|
||||
if accounts.current?.instance == selectedInstance {
|
||||
accounts.setCurrent(nil)
|
||||
}
|
||||
|
||||
InstancesModel.remove(selectedInstance!)
|
||||
selectedInstanceID = instances.last?.id
|
||||
}
|
||||
InstancesModel.remove(selectedInstance!)
|
||||
selectedInstanceID = instances.last?.id
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
}
|
||||
|
||||
.foregroundColor(.red)
|
||||
}
|
||||
}
|
||||
@ -134,6 +147,7 @@ struct InstancesSettings: View {
|
||||
|
||||
.onAppear {
|
||||
selectedInstanceID = instances.first?.id
|
||||
frontendURL = selectedInstanceFrontendURL
|
||||
}
|
||||
.sheet(isPresented: $presentingAccountForm) {
|
||||
AccountForm(instance: selectedInstance, selectedAccount: $selectedAccount)
|
||||
@ -154,6 +168,10 @@ struct InstancesSettings: View {
|
||||
InstancesModel.find(selectedInstanceID)
|
||||
}
|
||||
|
||||
var selectedInstanceFrontendURL: String {
|
||||
selectedInstance?.frontendURL ?? ""
|
||||
}
|
||||
|
||||
private var selectedInstanceAccounts: [Account] {
|
||||
guard selectedInstance != nil else {
|
||||
return []
|
@ -1,10 +1,12 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "TopShelf-Wide.png",
|
||||
"idiom" : "tv",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "TopShelf-Wide@2x.png",
|
||||
"idiom" : "tv",
|
||||
"scale" : "2x"
|
||||
},
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 61 KiB |
Loading…
Reference in New Issue
Block a user