mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Managing Favorites sections
This commit is contained in:
parent
f11125a399
commit
8df452752a
@ -14,6 +14,7 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
|
||||
.environmentObject(RecentsModel())
|
||||
.environmentObject(SearchModel())
|
||||
.environmentObject(subscriptions)
|
||||
.environmentObject(ThumbnailsModel())
|
||||
}
|
||||
|
||||
private var invidious: InvidiousAPI {
|
||||
|
@ -60,6 +60,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "channel/\(id)")
|
||||
}
|
||||
|
||||
func channelVideos(_ id: String) -> Resource {
|
||||
channel(id)
|
||||
}
|
||||
|
||||
func channelPlaylist(_ id: String) -> Resource? {
|
||||
resource(baseURL: account.url, path: "playlists/\(id)")
|
||||
}
|
||||
@ -94,6 +98,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
|
||||
func channelSubscription(_: String) -> Resource? { nil }
|
||||
|
||||
func playlist(_: String) -> Resource? { nil }
|
||||
func playlistVideo(_: String, _: String) -> Resource? { nil }
|
||||
func playlistVideos(_: String) -> Resource? { nil }
|
||||
|
||||
|
@ -6,6 +6,7 @@ protocol VideosAPI {
|
||||
var signedIn: Bool { get }
|
||||
|
||||
func channel(_ id: String) -> Resource
|
||||
func channelVideos(_ id: String) -> Resource
|
||||
func trending(country: Country, category: TrendingCategory?) -> Resource
|
||||
func search(_ query: SearchQuery) -> Resource
|
||||
func searchSuggestions(query: String) -> Resource
|
||||
@ -20,6 +21,7 @@ protocol VideosAPI {
|
||||
|
||||
func channelSubscription(_ id: String) -> Resource?
|
||||
|
||||
func playlist(_ id: String) -> Resource?
|
||||
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?
|
||||
func playlistVideos(_ id: String) -> Resource?
|
||||
|
||||
|
39
Model/FavoriteItem.swift
Normal file
39
Model/FavoriteItem.swift
Normal file
@ -0,0 +1,39 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
|
||||
struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
||||
enum Section: Codable, Equatable, Defaults.Serializable {
|
||||
case subscriptions
|
||||
case popular
|
||||
case trending(String, String?)
|
||||
case channel(String, String)
|
||||
case playlist(String)
|
||||
case channelPlaylist(String, String)
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .subscriptions:
|
||||
return "Subscriptions"
|
||||
case .popular:
|
||||
return "Popular"
|
||||
case let .trending(country, category):
|
||||
let trendingCountry = Country(rawValue: country)!
|
||||
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)!
|
||||
return "\(trendingCountry.flag) \(trendingCategory?.name ?? "")"
|
||||
case let .channel(_, name):
|
||||
return name
|
||||
case let .channelPlaylist(_, name):
|
||||
return name
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: FavoriteItem, rhs: FavoriteItem) -> Bool {
|
||||
lhs.section == rhs.section
|
||||
}
|
||||
|
||||
var id = UUID().uuidString
|
||||
var section: Section
|
||||
}
|
77
Model/FavoritesModel.swift
Normal file
77
Model/FavoritesModel.swift
Normal file
@ -0,0 +1,77 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
|
||||
struct FavoritesModel {
|
||||
static let shared = FavoritesModel()
|
||||
|
||||
@Default(.favorites) var all
|
||||
|
||||
func contains(_ item: FavoriteItem) -> Bool {
|
||||
all.contains { $0 == item }
|
||||
}
|
||||
|
||||
func toggle(_ item: FavoriteItem) {
|
||||
contains(item) ? remove(item) : add(item)
|
||||
}
|
||||
|
||||
func add(_ item: FavoriteItem) {
|
||||
all.append(item)
|
||||
}
|
||||
|
||||
func remove(_ item: FavoriteItem) {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
all.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
func canMoveUp(_ item: FavoriteItem) -> Bool {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
return index > all.startIndex
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func canMoveDown(_ item: FavoriteItem) -> Bool {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
return index < all.endIndex - 1
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func moveUp(_ item: FavoriteItem) {
|
||||
guard canMoveUp(item) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let from = all.firstIndex(where: { $0 == item }) {
|
||||
all.move(
|
||||
fromOffsets: IndexSet(integer: from),
|
||||
toOffset: from - 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func moveDown(_ item: FavoriteItem) {
|
||||
guard canMoveDown(item) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let from = all.firstIndex(where: { $0 == item }) {
|
||||
all.move(
|
||||
fromOffsets: IndexSet(integer: from),
|
||||
toOffset: from + 2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func addableItems() -> [FavoriteItem] {
|
||||
let allItems = [
|
||||
FavoriteItem(section: .subscriptions),
|
||||
FavoriteItem(section: .popular)
|
||||
]
|
||||
|
||||
return allItems.filter { item in !all.contains { $0.section == item.section } }
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ import SwiftUI
|
||||
|
||||
final class NavigationModel: ObservableObject {
|
||||
enum TabSelection: Hashable {
|
||||
case watchNow
|
||||
case favorites
|
||||
case subscriptions
|
||||
case popular
|
||||
case trending
|
||||
@ -23,7 +23,7 @@ final class NavigationModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var tabSelection: TabSelection! = .watchNow
|
||||
@Published var tabSelection: TabSelection! = .favorites
|
||||
|
||||
@Published var presentingAddToPlaylist = false
|
||||
@Published var videoToAddToPlaylist: Video!
|
||||
@ -44,7 +44,7 @@ final class NavigationModel: ObservableObject {
|
||||
var tabSelectionBinding: Binding<TabSelection> {
|
||||
Binding<TabSelection>(
|
||||
get: {
|
||||
self.tabSelection ?? .watchNow
|
||||
self.tabSelection ?? .favorites
|
||||
},
|
||||
set: { newValue in
|
||||
self.tabSelection = newValue
|
||||
|
@ -168,10 +168,12 @@ final class PlayerModel: ObservableObject {
|
||||
try? AVAudioSession.sharedInstance().setActive(true)
|
||||
#endif
|
||||
|
||||
if self.isAutoplaying(playerItem!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
self?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let replaceItemAndSeek = {
|
||||
self.player.replaceCurrentItem(with: playerItem)
|
||||
@ -440,7 +442,8 @@ final class PlayerModel: ObservableObject {
|
||||
MPMediaItemPropertyTitle: currentItem.video.title as AnyObject,
|
||||
MPMediaItemPropertyArtist: currentItem.video.author as AnyObject,
|
||||
MPMediaItemPropertyArtwork: currentArtwork as AnyObject,
|
||||
MPMediaItemPropertyPlaybackDuration: Int(currentItem.videoDuration ?? 0) as AnyObject,
|
||||
MPMediaItemPropertyPlaybackDuration: (currentItem.video.live ? nil : Int(currentItem.videoDuration ?? 0)) as AnyObject,
|
||||
MPNowPlayingInfoPropertyIsLiveStream: currentItem.video.live as AnyObject,
|
||||
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds as AnyObject,
|
||||
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
|
||||
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
|
||||
|
@ -106,7 +106,7 @@ extension PlayerModel {
|
||||
}
|
||||
|
||||
func isAutoplaying(_ item: AVPlayerItem) -> Bool {
|
||||
player.currentItem == item
|
||||
player.currentItem == item && presentingPlayer
|
||||
}
|
||||
|
||||
@discardableResult func enqueueVideo(
|
||||
|
@ -11,8 +11,6 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
||||
return nil
|
||||
}
|
||||
|
||||
let videoID = value.videoID.isEmpty ? value.video!.videoID : value.videoID
|
||||
|
||||
var playbackTime = ""
|
||||
if let time = value.playbackTime {
|
||||
if time.seconds.isFinite {
|
||||
@ -28,7 +26,7 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
||||
}
|
||||
|
||||
return [
|
||||
"videoID": videoID,
|
||||
"videoID": value.videoID,
|
||||
"playbackTime": playbackTime,
|
||||
"videoDuration": videoDuration
|
||||
]
|
||||
|
@ -2,7 +2,7 @@ import Defaults
|
||||
import Foundation
|
||||
|
||||
final class SearchQuery: ObservableObject {
|
||||
enum Date: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum Date: String, CaseIterable, Identifiable {
|
||||
case any, hour, today, week, month, year
|
||||
|
||||
var id: SearchQuery.Date.RawValue {
|
||||
@ -14,7 +14,7 @@ final class SearchQuery: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
enum Duration: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum Duration: String, CaseIterable, Identifiable {
|
||||
case any, short, long
|
||||
|
||||
var id: SearchQuery.Duration.RawValue {
|
||||
@ -26,7 +26,7 @@ final class SearchQuery: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
enum SortOrder: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum SortOrder: String, CaseIterable, Identifiable {
|
||||
case relevance, rating, uploadDate, viewCount
|
||||
|
||||
var id: SearchQuery.SortOrder.RawValue {
|
||||
|
@ -3,11 +3,19 @@ import Defaults
|
||||
enum TrendingCategory: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||
case `default`, music, gaming, movies
|
||||
|
||||
var id: TrendingCategory.RawValue {
|
||||
var id: RawValue {
|
||||
rawValue
|
||||
}
|
||||
|
||||
var name: String {
|
||||
var title: RawValue {
|
||||
rawValue.capitalized
|
||||
}
|
||||
|
||||
var name: String {
|
||||
id == "default" ? "Trending" : title
|
||||
}
|
||||
|
||||
var controlLabel: String {
|
||||
id == "default" ? "All" : title
|
||||
}
|
||||
}
|
||||
|
@ -144,6 +144,15 @@
|
||||
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||
375168D82700FDB9008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||
3758638A2721B0A9000CB14E /* ChannelCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3743B86727216D3600261544 /* ChannelCell.swift */; };
|
||||
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
||||
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
||||
37599F32272B42810087F250 /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F2F272B42810087F250 /* FavoriteItem.swift */; };
|
||||
37599F34272B44000087F250 /* FavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F33272B44000087F250 /* FavoritesModel.swift */; };
|
||||
37599F35272B44000087F250 /* FavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F33272B44000087F250 /* FavoritesModel.swift */; };
|
||||
37599F36272B44000087F250 /* FavoritesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F33272B44000087F250 /* FavoritesModel.swift */; };
|
||||
37599F38272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37599F37272B4D740087F250 /* FavoriteButton.swift */; };
|
||||
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
|
||||
375DFB5926F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
|
||||
375DFB5A26F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
|
||||
@ -201,12 +210,9 @@
|
||||
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 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; };
|
||||
3788AC2826F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; };
|
||||
3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; };
|
||||
3788AC2B26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */; };
|
||||
3788AC2C26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */; };
|
||||
3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.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 */; };
|
||||
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 */; };
|
||||
@ -241,9 +247,9 @@
|
||||
37A9965A26D6F8CA006E3224 /* HorizontalCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* HorizontalCells.swift */; };
|
||||
37A9965B26D6F8CA006E3224 /* HorizontalCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* HorizontalCells.swift */; };
|
||||
37A9965C26D6F8CA006E3224 /* HorizontalCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* HorizontalCells.swift */; };
|
||||
37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
||||
37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
||||
37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
||||
37A9965E26D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||
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 */; };
|
||||
@ -311,6 +317,10 @@
|
||||
37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */; };
|
||||
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD926A214630092E2DB /* PlayerViewController.swift */; };
|
||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* Player.swift */; };
|
||||
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
|
||||
37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661B27308859008CCFB0 /* DropFavorite.swift */; };
|
||||
37BF661F27308884008CCFB0 /* DropFavoriteOutside.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */; };
|
||||
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */; };
|
||||
37C069782725962F00F7F6CB /* ScreenSaverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069772725962F00F7F6CB /* ScreenSaverManager.swift */; };
|
||||
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
|
||||
37C0697B2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
|
||||
@ -411,6 +421,7 @@
|
||||
37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */; };
|
||||
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */; };
|
||||
37F64FE626FE70A60081B69E /* RedrawOnModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */; };
|
||||
37FAE000272ED58000330459 /* EditFavorites.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FADFFF272ED58000330459 /* EditFavorites.swift */; };
|
||||
37FB28412721B22200A57617 /* ContentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FB28402721B22200A57617 /* ContentItem.swift */; };
|
||||
37FB28422721B22200A57617 /* ContentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FB28402721B22200A57617 /* ContentItem.swift */; };
|
||||
37FB28432721B22200A57617 /* ContentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FB28402721B22200A57617 /* ContentItem.swift */; };
|
||||
@ -538,6 +549,9 @@
|
||||
374C0542272496E4009BDDBE /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = macOS/AppDelegate.swift; sourceTree = SOURCE_ROOT; };
|
||||
374C0544272496FD009BDDBE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
375168D52700FAFF008F96A6 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = "<group>"; };
|
||||
37599F2F272B42810087F250 /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = "<group>"; };
|
||||
37599F33272B44000087F250 /* FavoritesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesModel.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@ -555,8 +569,7 @@
|
||||
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.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 /* WatchNowSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSection.swift; sourceTree = "<group>"; };
|
||||
3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSectionBody.swift; sourceTree = "<group>"; };
|
||||
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.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>"; };
|
||||
@ -575,7 +588,7 @@
|
||||
37A3B16D27255E7F000FB5EE /* Open in Yattee.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Open in Yattee.entitlements"; sourceTree = "<group>"; };
|
||||
37A3B1792725735F000FB5EE /* Open in Yattee (iOS).appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "Open in Yattee (iOS).appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
37A9965926D6F8CA006E3224 /* HorizontalCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HorizontalCells.swift; sourceTree = "<group>"; };
|
||||
37A9965D26D6F9B9006E3224 /* WatchNowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowView.swift; sourceTree = "<group>"; };
|
||||
37A9965D26D6F9B9006E3224 /* FavoritesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesView.swift; sourceTree = "<group>"; };
|
||||
37AAF27D26737323007FC770 /* PopularView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopularView.swift; sourceTree = "<group>"; };
|
||||
37AAF27F26737550007FC770 /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||
37AAF28F26740715007FC770 /* Channel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = "<group>"; };
|
||||
@ -603,6 +616,8 @@
|
||||
37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
||||
37BE0BD926A214630092E2DB /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
||||
37BE0BDB26A2367F0092E2DB /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
|
||||
37BF661B27308859008CCFB0 /* DropFavorite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropFavorite.swift; sourceTree = "<group>"; };
|
||||
37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropFavoriteOutside.swift; sourceTree = "<group>"; };
|
||||
37C069772725962F00F7F6CB /* ScreenSaverManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenSaverManager.swift; sourceTree = "<group>"; };
|
||||
37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerQueueItemBridge.swift; sourceTree = "<group>"; };
|
||||
37C0697D2725C8D400F7F6CB /* CMTime+DefaultTimescale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CMTime+DefaultTimescale.swift"; sourceTree = "<group>"; };
|
||||
@ -649,6 +664,7 @@
|
||||
37F49BA226CAA59B00304AC0 /* Playlist+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Playlist+Fixtures.swift"; sourceTree = "<group>"; };
|
||||
37F4AE7126828F0900BD60EA /* VerticalCells.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalCells.swift; sourceTree = "<group>"; };
|
||||
37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RedrawOnModifier.swift; sourceTree = "<group>"; };
|
||||
37FADFFF272ED58000330459 /* EditFavorites.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFavorites.swift; sourceTree = "<group>"; };
|
||||
37FB28402721B22200A57617 /* ContentItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentItem.swift; sourceTree = "<group>"; };
|
||||
37FB285D272225E800A57617 /* ContentItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentItemView.swift; sourceTree = "<group>"; };
|
||||
37FD43DB270470B70073EE42 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = "<group>"; };
|
||||
@ -814,6 +830,7 @@
|
||||
37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */,
|
||||
37FB285D272225E800A57617 /* ContentItemView.swift */,
|
||||
3748186D26A769D60084E870 /* DetailBadge.swift */,
|
||||
37599F37272B4D740087F250 /* FavoriteButton.swift */,
|
||||
37152EE926EFEB95004FB96D /* LazyView.swift */,
|
||||
37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */,
|
||||
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */,
|
||||
@ -922,14 +939,15 @@
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
3788AC2126F683AB00F6BAA9 /* Watch Now */ = {
|
||||
3788AC2126F683AB00F6BAA9 /* Favorites */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */,
|
||||
3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */,
|
||||
37A9965D26D6F9B9006E3224 /* WatchNowView.swift */,
|
||||
37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */,
|
||||
37BF661B27308859008CCFB0 /* DropFavorite.swift */,
|
||||
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */,
|
||||
37A9965D26D6F9B9006E3224 /* FavoritesView.swift */,
|
||||
);
|
||||
path = "Watch Now";
|
||||
path = Favorites;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
37992DC826CC50CD003D4C27 /* iOS */ = {
|
||||
@ -1022,6 +1040,7 @@
|
||||
37D4B0C12671614700C925CA /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
3788AC2126F683AB00F6BAA9 /* Favorites */,
|
||||
37D526E12720B49200ED2F5E /* Gestures */,
|
||||
3761AC0526F0F96100AA496F /* Modifiers */,
|
||||
371AAE2326CEB9E800901972 /* Navigation */,
|
||||
@ -1031,7 +1050,6 @@
|
||||
371AAE2526CEBF0B00901972 /* Trending */,
|
||||
371AAE2726CEBF4700901972 /* Videos */,
|
||||
371AAE2826CEC7D900901972 /* Views */,
|
||||
3788AC2126F683AB00F6BAA9 /* Watch Now */,
|
||||
375168D52700FAFF008F96A6 /* Debounce.swift */,
|
||||
372915E52687E3B900F5A35B /* Defaults.swift */,
|
||||
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
|
||||
@ -1082,6 +1100,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37666BA927023AF000F869E5 /* AccountSelectionView.swift */,
|
||||
37FADFFF272ED58000330459 /* EditFavorites.swift */,
|
||||
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */,
|
||||
37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */,
|
||||
37D4B15E267164AF00C925CA /* Assets.xcassets */,
|
||||
@ -1123,6 +1142,8 @@
|
||||
37C0698127260B2100F7F6CB /* ThumbnailsModel.swift */,
|
||||
3705B181267B4E4900704544 /* TrendingCategory.swift */,
|
||||
37D4B19626717E1500C925CA /* Video.swift */,
|
||||
37599F2F272B42810087F250 /* FavoriteItem.swift */,
|
||||
37599F33272B44000087F250 /* FavoritesModel.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@ -1627,6 +1648,7 @@
|
||||
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||
37CC3F45270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
|
||||
37599F38272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||
37CB12792724C76D00213B45 /* VideoURLParser.swift in Sources */,
|
||||
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */,
|
||||
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
@ -1653,11 +1675,12 @@
|
||||
37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */,
|
||||
37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
||||
37A9965E26D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
||||
37F4AE7226828F0900BD60EA /* VerticalCells.swift in Sources */,
|
||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||
37599F34272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
||||
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
||||
@ -1669,15 +1692,16 @@
|
||||
37E64DD126D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
|
||||
376578892685471400D4EA09 /* Playlist.swift in Sources */,
|
||||
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
3788AC2B26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
37BF661F27308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||
3700155B271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
||||
37B044B726F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||
377FC7E3267A084A00A6BBAF /* VideoCell.swift in Sources */,
|
||||
37C3A251272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
||||
37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||
374C053527242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
||||
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */,
|
||||
376A33E42720CB35000C1D6B /* Account.swift in Sources */,
|
||||
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||
@ -1711,13 +1735,14 @@
|
||||
37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||
37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
||||
3788AC2726F6840700F6BAA9 /* WatchNowSection.swift in Sources */,
|
||||
3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */,
|
||||
37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
||||
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||
@ -1751,8 +1776,8 @@
|
||||
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
||||
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
|
||||
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||
37C3A24627235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
||||
3788AC2C26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
||||
37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||
374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
||||
@ -1781,7 +1806,8 @@
|
||||
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
|
||||
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
3788AC2826F6840700F6BAA9 /* WatchNowSection.swift in Sources */,
|
||||
3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||
37599F35272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||
@ -1796,12 +1822,14 @@
|
||||
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
||||
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||
374C0543272496E4009BDDBE /* AppDelegate.swift in Sources */,
|
||||
373CFACC26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||
37AAF29126740715007FC770 /* Channel.swift in Sources */,
|
||||
376A33E12720CAD6000C1D6B /* VideosApp.swift in Sources */,
|
||||
37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||
37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */,
|
||||
3748186F26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||
372915E72687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||
37C3A242272359900087A57A /* Double+Format.swift in Sources */,
|
||||
@ -1815,7 +1843,7 @@
|
||||
37B81B0326D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||
376A33E52720CB35000C1D6B /* Account.swift in Sources */,
|
||||
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||
37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
||||
37A9965F26D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||
37F4AE7326828F0900BD60EA /* VerticalCells.swift in Sources */,
|
||||
37001560271B12DD0049C794 /* SiestaConfiguration.swift in Sources */,
|
||||
37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
||||
@ -1842,6 +1870,7 @@
|
||||
37CC3F46270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
||||
3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */,
|
||||
37D526DF2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||
@ -1891,7 +1920,6 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */,
|
||||
3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
||||
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37CC3F52270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||
37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
@ -1911,7 +1939,7 @@
|
||||
376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */,
|
||||
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
37C3A24727235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
||||
3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */,
|
||||
3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||
@ -1940,11 +1968,12 @@
|
||||
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||
37AAF27E26737323007FC770 /* PopularView.swift in Sources */,
|
||||
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
||||
37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
||||
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
||||
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||
37484C1F26FC83A400287258 /* InstancesSettings.swift in Sources */,
|
||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
||||
@ -1976,6 +2005,8 @@
|
||||
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||
37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||
37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */,
|
||||
37FAE000272ED58000330459 /* EditFavorites.swift in Sources */,
|
||||
37599F32272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||
37141675267A8E10006CA35D /* Country.swift in Sources */,
|
||||
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||
@ -1984,6 +2015,7 @@
|
||||
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||
37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||
37599F36272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||
3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
||||
37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.swift in Sources */,
|
||||
|
@ -35,6 +35,10 @@ extension Defaults.Keys {
|
||||
static let sponsorBlockInstance = Key<String>("sponsorBlockInstance", default: "https://sponsor.ajay.app")
|
||||
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
||||
|
||||
static let favorites = Key<[FavoriteItem]>("favorites", default: [
|
||||
.init(section: .trending("US", nil))
|
||||
])
|
||||
|
||||
static let quality = Key<Stream.ResolutionSetting>("quality", default: .hd720pFirstThenBest)
|
||||
|
||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
||||
|
35
Shared/Favorites/DropFavorite.swift
Normal file
35
Shared/Favorites/DropFavorite.swift
Normal file
@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct DropFavorite: DropDelegate {
|
||||
let item: FavoriteItem
|
||||
@Binding var favorites: [FavoriteItem]
|
||||
@Binding var current: FavoriteItem?
|
||||
|
||||
func dropEntered(info _: DropInfo) {
|
||||
guard item != current else {
|
||||
return
|
||||
}
|
||||
|
||||
let from = favorites.firstIndex(of: current!)!
|
||||
let to = favorites.firstIndex(of: item)!
|
||||
|
||||
guard favorites[to].id != current!.id else {
|
||||
return
|
||||
}
|
||||
|
||||
favorites.move(
|
||||
fromOffsets: IndexSet(integer: from),
|
||||
toOffset: to > from ? to + 1 : to
|
||||
)
|
||||
}
|
||||
|
||||
func dropUpdated(info _: DropInfo) -> DropProposal? {
|
||||
DropProposal(operation: .move)
|
||||
}
|
||||
|
||||
func performDrop(info _: DropInfo) -> Bool {
|
||||
current = nil
|
||||
return true
|
||||
}
|
||||
}
|
11
Shared/Favorites/DropFavoriteOutside.swift
Normal file
11
Shared/Favorites/DropFavoriteOutside.swift
Normal file
@ -0,0 +1,11 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct DropFavoriteOutside: DropDelegate {
|
||||
@Binding var current: FavoriteItem?
|
||||
|
||||
func performDrop(info _: DropInfo) -> Bool {
|
||||
current = nil
|
||||
return true
|
||||
}
|
||||
}
|
93
Shared/Favorites/FavoriteItemView.swift
Normal file
93
Shared/Favorites/FavoriteItemView.swift
Normal file
@ -0,0 +1,93 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
final class FavoriteResourceObserver: ObservableObject, ResourceObserver {
|
||||
@Published var videos = [Video]()
|
||||
|
||||
func resourceChanged(_ resource: Resource, event _: ResourceEvent) {
|
||||
if let videos: [Video] = resource.typedContent() {
|
||||
self.videos = videos
|
||||
} else if let channel: Channel = resource.typedContent() {
|
||||
videos = channel.videos
|
||||
} else if let playlist: ChannelPlaylist = resource.typedContent() {
|
||||
videos = playlist.videos
|
||||
} else if let playlist: Playlist = resource.typedContent() {
|
||||
videos = playlist.videos
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FavoriteItemView: View {
|
||||
let item: FavoriteItem
|
||||
let resource: Resource?
|
||||
|
||||
@StateObject private var store = FavoriteResourceObserver()
|
||||
|
||||
@Binding private var favorites: [FavoriteItem]
|
||||
@Binding private var dragging: FavoriteItem?
|
||||
|
||||
@EnvironmentObject<PlaylistsModel> private var playlistsModel
|
||||
|
||||
init(
|
||||
item: FavoriteItem,
|
||||
resource: Resource?,
|
||||
favorites: Binding<[FavoriteItem]>,
|
||||
dragging: Binding<FavoriteItem?>
|
||||
) {
|
||||
self.item = item
|
||||
self.resource = resource
|
||||
_favorites = favorites
|
||||
_dragging = dragging
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(label)
|
||||
.font(.title3.bold())
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
.contextMenu {
|
||||
Button {
|
||||
FavoritesModel.shared.remove(item)
|
||||
} label: {
|
||||
Label("Remove from Favorites", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
#if os(tvOS)
|
||||
.padding(.leading, 40)
|
||||
#else
|
||||
.padding(.leading, 15)
|
||||
#endif
|
||||
|
||||
HorizontalCells(items: store.videos.map { ContentItem(video: $0) })
|
||||
}
|
||||
|
||||
.contentShape(Rectangle())
|
||||
.opacity(dragging?.id == item.id ? 0.5 : 1)
|
||||
.onAppear {
|
||||
resource?.addObserver(store)
|
||||
resource?.loadIfNeeded()
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.onDrag {
|
||||
dragging = item
|
||||
return NSItemProvider(object: item.id as NSString)
|
||||
}
|
||||
.onDrop(
|
||||
of: [UTType.text],
|
||||
delegate: DropFavorite(item: item, favorites: $favorites, current: $dragging)
|
||||
)
|
||||
#endif
|
||||
}
|
||||
|
||||
var label: String {
|
||||
if case let .playlist(id) = item.section {
|
||||
return playlistsModel.find(id: id)?.title ?? "Unknown Playlist"
|
||||
}
|
||||
|
||||
return item.section.label
|
||||
}
|
||||
}
|
91
Shared/Favorites/FavoritesView.swift
Normal file
91
Shared/Favorites/FavoritesView.swift
Normal file
@ -0,0 +1,91 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FavoritesView: View {
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<PlaylistsModel> private var playlists
|
||||
|
||||
@State private var dragging: FavoriteItem?
|
||||
@State private var presentingEditFavorites = false
|
||||
|
||||
@Default(.favorites) private var favorites
|
||||
|
||||
var body: some View {
|
||||
PlayerControlsView {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
if !accounts.current.isNil {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
ForEach(favorites) { item in
|
||||
VStack {
|
||||
if let resource = resource(item) {
|
||||
FavoriteItemView(item: item, resource: resource, favorites: $favorites, dragging: $dragging)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
Button {
|
||||
presentingEditFavorites = true
|
||||
} label: {
|
||||
Text("Edit Favorites...")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if os(tvOS)
|
||||
.sheet(isPresented: $presentingEditFavorites) {
|
||||
EditFavorites()
|
||||
}
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#else
|
||||
.onDrop(of: [UTType.text], delegate: DropFavoriteOutside(current: $dragging))
|
||||
.navigationTitle("Favorites")
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.frame(minWidth: 360)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
func resource(_ item: FavoriteItem) -> Resource? {
|
||||
switch item.section {
|
||||
case .subscriptions:
|
||||
if accounts.app.supportsSubscriptions {
|
||||
return accounts.api.feed
|
||||
}
|
||||
|
||||
case .popular:
|
||||
if accounts.app.supportsPopular {
|
||||
return accounts.api.popular
|
||||
}
|
||||
|
||||
case let .trending(country, category):
|
||||
let trendingCountry = Country(rawValue: country)!
|
||||
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)!
|
||||
|
||||
return accounts.api.trending(country: trendingCountry, category: trendingCategory)
|
||||
|
||||
case let .channel(id, _):
|
||||
return accounts.api.channelVideos(id)
|
||||
|
||||
case let .channelPlaylist(id, _):
|
||||
return accounts.api.channelPlaylist(id)
|
||||
|
||||
case let .playlist(id):
|
||||
return accounts.api.playlist(id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
struct Favorites_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
FavoritesView()
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
@ -11,14 +11,14 @@ struct AppTabNavigation: View {
|
||||
var body: some View {
|
||||
TabView(selection: navigation.tabSelectionBinding) {
|
||||
NavigationView {
|
||||
LazyView(WatchNowView())
|
||||
LazyView(FavoritesView())
|
||||
.toolbar { toolbarContent }
|
||||
}
|
||||
.tabItem {
|
||||
Label("Watch Now", systemImage: "play.circle")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
Label("Favorites", systemImage: "heart")
|
||||
.accessibility(label: Text("Favorites"))
|
||||
}
|
||||
.tag(TabSelection.watchNow)
|
||||
.tag(TabSelection.favorites)
|
||||
|
||||
if accounts.app.supportsSubscriptions {
|
||||
NavigationView {
|
||||
|
@ -28,9 +28,9 @@ struct Sidebar: View {
|
||||
|
||||
var mainNavigationLinks: some View {
|
||||
Section("Videos") {
|
||||
NavigationLink(destination: LazyView(WatchNowView()), tag: TabSelection.watchNow, selection: $navigation.tabSelection) {
|
||||
Label("Watch Now", systemImage: "play.circle")
|
||||
.accessibility(label: Text("Watch Now"))
|
||||
NavigationLink(destination: LazyView(FavoritesView()), tag: TabSelection.favorites, selection: $navigation.tabSelection) {
|
||||
Label("Favorites", systemImage: "heart")
|
||||
.accessibility(label: Text("Favorites"))
|
||||
}
|
||||
if accounts.app.supportsSubscriptions && accounts.signedIn {
|
||||
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
||||
|
@ -13,13 +13,16 @@ struct VideoPlayerView: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
@State private var playerSize: CGSize = .zero
|
||||
@State private var fullScreen = false
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||
|
||||
private var idiom: UIUserInterfaceIdiom {
|
||||
UIDevice.current.userInterfaceIdiom
|
||||
}
|
||||
#endif
|
||||
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
@ -75,12 +78,6 @@ struct VideoPlayerView: View {
|
||||
#endif
|
||||
|
||||
.background(.black)
|
||||
.onAppear {
|
||||
self.playerSize = geometry.size
|
||||
}
|
||||
.onChange(of: geometry.size) { size in
|
||||
self.playerSize = size
|
||||
}
|
||||
|
||||
Group {
|
||||
#if os(iOS)
|
||||
@ -134,7 +131,7 @@ struct VideoPlayerView: View {
|
||||
|
||||
#if os(iOS)
|
||||
var sidebarQueue: Bool {
|
||||
horizontalSizeClass == .regular && playerSize.width > 750
|
||||
horizontalSizeClass == .regular && idiom == .pad
|
||||
}
|
||||
|
||||
var sidebarQueueBinding: Binding<Bool> {
|
||||
|
@ -75,6 +75,8 @@ struct PlaylistsView: View {
|
||||
editPlaylistButton
|
||||
}
|
||||
#endif
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(selectedPlaylistID)))
|
||||
|
||||
newPlaylistButton
|
||||
}
|
||||
|
||||
@ -139,6 +141,11 @@ struct PlaylistsView: View {
|
||||
editPlaylistButton
|
||||
}
|
||||
|
||||
if let playlist = currentPlaylist {
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(playlist.id)))
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
newPlaylistButton
|
||||
|
@ -95,6 +95,8 @@ struct ServicesSettings: View {
|
||||
|
||||
struct ServicesSettings_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
ServicesSettings()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,9 +11,11 @@ struct TrendingView: View {
|
||||
|
||||
@State private var presentingCountrySelection = false
|
||||
|
||||
@State private var favoriteItem: FavoriteItem?
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
var popular: [ContentItem] {
|
||||
var trending: [ContentItem] {
|
||||
ContentItem.array(of: store.collection)
|
||||
}
|
||||
|
||||
@ -36,12 +38,12 @@ struct TrendingView: View {
|
||||
VStack(alignment: .center, spacing: 0) {
|
||||
#if os(tvOS)
|
||||
toolbar
|
||||
HorizontalCells(items: popular)
|
||||
HorizontalCells(items: trending)
|
||||
.padding(.top, 40)
|
||||
|
||||
Spacer()
|
||||
#else
|
||||
VerticalCells(items: popular)
|
||||
VerticalCells(items: trending)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -62,6 +64,11 @@ struct TrendingView: View {
|
||||
.toolbar {
|
||||
#if os(macOS)
|
||||
ToolbarItemGroup {
|
||||
if let favoriteItem = favoriteItem {
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem.id)
|
||||
}
|
||||
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
categoryButton
|
||||
}
|
||||
@ -70,8 +77,8 @@ struct TrendingView: View {
|
||||
#elseif os(iOS)
|
||||
ToolbarItemGroup(placement: .bottomBar) {
|
||||
Group {
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
HStack {
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
Text("Category")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
@ -80,7 +87,14 @@ struct TrendingView: View {
|
||||
// force redraw of the view when it changes
|
||||
.id(UUID())
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let favoriteItem = favoriteItem {
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem.id)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@ -96,6 +110,7 @@ struct TrendingView: View {
|
||||
}
|
||||
.onChange(of: resource) { _ in
|
||||
resource.load()
|
||||
updateFavoriteItem()
|
||||
}
|
||||
.onAppear {
|
||||
if videos.isEmpty {
|
||||
@ -104,10 +119,12 @@ struct TrendingView: View {
|
||||
} else {
|
||||
store.replace(videos)
|
||||
}
|
||||
|
||||
updateFavoriteItem()
|
||||
}
|
||||
}
|
||||
|
||||
var toolbar: some View {
|
||||
private var toolbar: some View {
|
||||
HStack {
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
HStack {
|
||||
@ -128,17 +145,25 @@ struct TrendingView: View {
|
||||
|
||||
countryButton
|
||||
}
|
||||
|
||||
#if os(tvOS)
|
||||
if let favoriteItem = favoriteItem {
|
||||
FavoriteButton(item: favoriteItem)
|
||||
.id(favoriteItem.id)
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
var categoryButton: some View {
|
||||
private var categoryButton: some View {
|
||||
#if os(tvOS)
|
||||
Button(category.name) {
|
||||
self.category = category.next()
|
||||
}
|
||||
.contextMenu {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Button(category.name) { self.category = category }
|
||||
Button(category.controlLabel) { self.category = category }
|
||||
}
|
||||
|
||||
Button("Cancel", role: .cancel) {}
|
||||
@ -147,13 +172,13 @@ struct TrendingView: View {
|
||||
#else
|
||||
Picker("Category", selection: $category) {
|
||||
ForEach(TrendingCategory.allCases) { category in
|
||||
Text(category.name).tag(category)
|
||||
Text(category.controlLabel).tag(category)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
var countryButton: some View {
|
||||
private var countryButton: some View {
|
||||
Button(action: {
|
||||
presentingCountrySelection.toggle()
|
||||
resource.removeObservers(ownedBy: store)
|
||||
@ -161,6 +186,10 @@ struct TrendingView: View {
|
||||
Text("\(country.flag) \(country.id)")
|
||||
}
|
||||
}
|
||||
|
||||
private func updateFavoriteItem() {
|
||||
favoriteItem = FavoriteItem(section: .trending(country.rawValue, category.rawValue))
|
||||
}
|
||||
}
|
||||
|
||||
struct TrendingView_Previews: PreviewProvider {
|
||||
|
@ -143,8 +143,6 @@ struct VideoCell: View {
|
||||
#endif
|
||||
.padding(.bottom, 4)
|
||||
|
||||
Group {
|
||||
if additionalDetailsAvailable {
|
||||
HStack(spacing: 8) {
|
||||
if let date = video.publishedDate {
|
||||
Image(systemName: "calendar")
|
||||
@ -157,10 +155,6 @@ struct VideoCell: View {
|
||||
}
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 30, alignment: .top)
|
||||
#if os(tvOS)
|
||||
.padding(.bottom, 10)
|
||||
|
@ -40,9 +40,16 @@ struct ChannelPlaylistView: View {
|
||||
var content: some View {
|
||||
VStack(alignment: .leading) {
|
||||
#if os(tvOS)
|
||||
HStack {
|
||||
Text(playlist.title)
|
||||
.font(.title2)
|
||||
.frame(alignment: .leading)
|
||||
|
||||
Spacer()
|
||||
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
#endif
|
||||
VerticalCells(items: items)
|
||||
}
|
||||
@ -66,12 +73,8 @@ struct ChannelPlaylistView: View {
|
||||
)
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
if inNavigationView {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
ToolbarItem {
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||
}
|
||||
}
|
||||
.navigationTitle(playlist.title)
|
||||
|
@ -51,6 +51,9 @@ struct ChannelVideosView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||
.labelStyle(.iconOnly)
|
||||
|
||||
if let subscribers = store.item?.subscriptionsString {
|
||||
Text("**\(subscribers)** subscribers")
|
||||
.foregroundColor(.secondary)
|
||||
@ -87,14 +90,8 @@ struct ChannelVideosView: View {
|
||||
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
|
||||
|
||||
subscriptionToggleButton
|
||||
}
|
||||
}
|
||||
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
if inNavigationView {
|
||||
Button("Done") {
|
||||
dismiss()
|
||||
}
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
25
Shared/Views/FavoriteButton.swift
Normal file
25
Shared/Views/FavoriteButton.swift
Normal file
@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct FavoriteButton: View {
|
||||
let item: FavoriteItem
|
||||
let favorites = FavoritesModel.shared
|
||||
|
||||
@State private var isFavorite = false
|
||||
|
||||
var body: some View {
|
||||
Button {
|
||||
favorites.toggle(item)
|
||||
isFavorite.toggle()
|
||||
} label: {
|
||||
if isFavorite {
|
||||
Label("Remove from Favorites", systemImage: "heart.fill")
|
||||
} else {
|
||||
Label("Add to Favorites", systemImage: "heart")
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
isFavorite = favorites.contains(item)
|
||||
}
|
||||
}
|
||||
}
|
@ -19,5 +19,10 @@ struct PlaylistVideosView: View {
|
||||
.navigationTitle("\(playlist.title) Playlist")
|
||||
#endif
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
FavoriteButton(item: FavoriteItem(section: .playlist(playlist.id)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -25,5 +25,10 @@ struct PopularView: View {
|
||||
.navigationTitle("Popular")
|
||||
#endif
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .automatic) {
|
||||
FavoriteButton(item: FavoriteItem(section: .popular))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ struct SubscriptionsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .automatic) {
|
||||
FavoriteButton(item: FavoriteItem(section: .subscriptions))
|
||||
}
|
||||
}
|
||||
.refreshable {
|
||||
loadResources(force: true)
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct WatchNowSection: View {
|
||||
let resource: Resource?
|
||||
let label: String
|
||||
|
||||
@StateObject private var store = Store<[Video]>()
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
init(resource: Resource?, label: String) {
|
||||
self.resource = resource
|
||||
self.label = label
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
WatchNowSectionBody(label: label, videos: store.collection)
|
||||
.onAppear {
|
||||
resource?.addObserver(store)
|
||||
resource?.loadIfNeeded()
|
||||
}
|
||||
.onChange(of: accounts.current) { _ in
|
||||
resource?.load()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct WatchNowSectionBody: View {
|
||||
let label: String
|
||||
let videos: [Video]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(label)
|
||||
.font(.title3.bold())
|
||||
.foregroundColor(.secondary)
|
||||
#if os(tvOS)
|
||||
.padding(.leading, 40)
|
||||
#else
|
||||
.padding(.leading, 15)
|
||||
#endif
|
||||
|
||||
HorizontalCells(items: ContentItem.array(of: videos))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct WatchNowView: View {
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
var body: some View {
|
||||
PlayerControlsView {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
if !accounts.current.isNil {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if accounts.api.signedIn {
|
||||
WatchNowSection(resource: accounts.api.feed, label: "Subscriptions")
|
||||
}
|
||||
if accounts.app.supportsPopular {
|
||||
WatchNowSection(resource: accounts.api.popular, label: "Popular")
|
||||
}
|
||||
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .default), label: "Trending")
|
||||
if accounts.app.supportsTrendingCategories {
|
||||
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .movies), label: "Movies")
|
||||
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .music), label: "Music")
|
||||
}
|
||||
|
||||
// TODO: adding sections to view
|
||||
// ===================
|
||||
// WatchNowPlaylistSection(id: "IVPLmRFYLGYZpq61SpujNw3EKbzzGNvoDmH")
|
||||
// WatchNowSection(resource: api.channelVideos("UCBJycsmduvYEL83R_U4JriQ"), label: "MKBHD")
|
||||
}
|
||||
}
|
||||
}
|
||||
.id(UUID())
|
||||
#if os(tvOS)
|
||||
.edgesIgnoringSafeArea(.horizontal)
|
||||
#else
|
||||
.navigationTitle("Watch Now")
|
||||
#endif
|
||||
#if os(macOS)
|
||||
.background()
|
||||
.frame(minWidth: 360)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct WatchNowView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
WatchNowView()
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
94
tvOS/EditFavorites.swift
Normal file
94
tvOS/EditFavorites.swift
Normal file
@ -0,0 +1,94 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct EditFavorites: View {
|
||||
@EnvironmentObject<PlaylistsModel> private var playlistsModel
|
||||
|
||||
private var model = FavoritesModel.shared
|
||||
|
||||
@Default(.favorites) private var favorites
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
ScrollView {
|
||||
Text("Edit Favorites")
|
||||
.font(.system(size: 40))
|
||||
.fontWeight(.bold)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
ForEach(favorites) { item in
|
||||
HStack {
|
||||
Text(label(item))
|
||||
|
||||
Spacer()
|
||||
HStack(spacing: 30) {
|
||||
Button {
|
||||
model.moveUp(item)
|
||||
} label: {
|
||||
Image(systemName: "arrow.up")
|
||||
}
|
||||
|
||||
Button {
|
||||
model.moveDown(item)
|
||||
} label: {
|
||||
Image(systemName: "arrow.down")
|
||||
}
|
||||
|
||||
Button {
|
||||
model.remove(item)
|
||||
} label: {
|
||||
Image(systemName: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 40)
|
||||
|
||||
Divider()
|
||||
.padding(20)
|
||||
|
||||
ForEach(model.addableItems()) { item in
|
||||
HStack {
|
||||
Text(label(item))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
model.add(item)
|
||||
} label: {
|
||||
Label("Add to Favorites", systemImage: "heart")
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.trailing, 40)
|
||||
|
||||
HStack {
|
||||
Text("Add more Channels and Playlists to your Favorites using button")
|
||||
Button {} label: {
|
||||
Label("Add to Favorites", systemImage: "heart")
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
.disabled(true)
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.padding(.top, 80)
|
||||
}
|
||||
.frame(width: 1000, alignment: .leading)
|
||||
}
|
||||
}
|
||||
|
||||
func label(_ item: FavoriteItem) -> String {
|
||||
if case let .playlist(id) = item.section {
|
||||
return playlistsModel.find(id: id)?.title ?? "Unknown Playlist"
|
||||
}
|
||||
|
||||
return item.section.label
|
||||
}
|
||||
}
|
||||
|
||||
struct EditFavorites_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
EditFavorites()
|
||||
.injectFixtureEnvironmentObjects()
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ struct TVNavigationView: View {
|
||||
|
||||
var body: some View {
|
||||
TabView(selection: navigation.tabSelectionBinding) {
|
||||
WatchNowView()
|
||||
.tabItem { Text("Watch Now") }
|
||||
.tag(TabSelection.watchNow)
|
||||
FavoritesView()
|
||||
.tabItem { Text("Favorites") }
|
||||
.tag(TabSelection.favorites)
|
||||
|
||||
if accounts.app.supportsSubscriptions {
|
||||
SubscriptionsView()
|
||||
|
Loading…
Reference in New Issue
Block a user