mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Managing Favorites sections
This commit is contained in:
parent
f11125a399
commit
8df452752a
@ -14,6 +14,7 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
|
|||||||
.environmentObject(RecentsModel())
|
.environmentObject(RecentsModel())
|
||||||
.environmentObject(SearchModel())
|
.environmentObject(SearchModel())
|
||||||
.environmentObject(subscriptions)
|
.environmentObject(subscriptions)
|
||||||
|
.environmentObject(ThumbnailsModel())
|
||||||
}
|
}
|
||||||
|
|
||||||
private var invidious: InvidiousAPI {
|
private var invidious: InvidiousAPI {
|
||||||
|
@ -60,6 +60,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
resource(baseURL: account.url, path: "channel/\(id)")
|
resource(baseURL: account.url, path: "channel/\(id)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func channelVideos(_ id: String) -> Resource {
|
||||||
|
channel(id)
|
||||||
|
}
|
||||||
|
|
||||||
func channelPlaylist(_ id: String) -> Resource? {
|
func channelPlaylist(_ id: String) -> Resource? {
|
||||||
resource(baseURL: account.url, path: "playlists/\(id)")
|
resource(baseURL: account.url, path: "playlists/\(id)")
|
||||||
}
|
}
|
||||||
@ -94,6 +98,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
|||||||
|
|
||||||
func channelSubscription(_: String) -> Resource? { nil }
|
func channelSubscription(_: String) -> Resource? { nil }
|
||||||
|
|
||||||
|
func playlist(_: String) -> Resource? { nil }
|
||||||
func playlistVideo(_: String, _: String) -> Resource? { nil }
|
func playlistVideo(_: String, _: String) -> Resource? { nil }
|
||||||
func playlistVideos(_: String) -> Resource? { nil }
|
func playlistVideos(_: String) -> Resource? { nil }
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ protocol VideosAPI {
|
|||||||
var signedIn: Bool { get }
|
var signedIn: Bool { get }
|
||||||
|
|
||||||
func channel(_ id: String) -> Resource
|
func channel(_ id: String) -> Resource
|
||||||
|
func channelVideos(_ id: String) -> Resource
|
||||||
func trending(country: Country, category: TrendingCategory?) -> Resource
|
func trending(country: Country, category: TrendingCategory?) -> Resource
|
||||||
func search(_ query: SearchQuery) -> Resource
|
func search(_ query: SearchQuery) -> Resource
|
||||||
func searchSuggestions(query: String) -> Resource
|
func searchSuggestions(query: String) -> Resource
|
||||||
@ -20,6 +21,7 @@ protocol VideosAPI {
|
|||||||
|
|
||||||
func channelSubscription(_ id: String) -> Resource?
|
func channelSubscription(_ id: String) -> Resource?
|
||||||
|
|
||||||
|
func playlist(_ id: String) -> Resource?
|
||||||
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?
|
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?
|
||||||
func playlistVideos(_ id: 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 {
|
final class NavigationModel: ObservableObject {
|
||||||
enum TabSelection: Hashable {
|
enum TabSelection: Hashable {
|
||||||
case watchNow
|
case favorites
|
||||||
case subscriptions
|
case subscriptions
|
||||||
case popular
|
case popular
|
||||||
case trending
|
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 presentingAddToPlaylist = false
|
||||||
@Published var videoToAddToPlaylist: Video!
|
@Published var videoToAddToPlaylist: Video!
|
||||||
@ -44,7 +44,7 @@ final class NavigationModel: ObservableObject {
|
|||||||
var tabSelectionBinding: Binding<TabSelection> {
|
var tabSelectionBinding: Binding<TabSelection> {
|
||||||
Binding<TabSelection>(
|
Binding<TabSelection>(
|
||||||
get: {
|
get: {
|
||||||
self.tabSelection ?? .watchNow
|
self.tabSelection ?? .favorites
|
||||||
},
|
},
|
||||||
set: { newValue in
|
set: { newValue in
|
||||||
self.tabSelection = newValue
|
self.tabSelection = newValue
|
||||||
|
@ -168,10 +168,12 @@ final class PlayerModel: ObservableObject {
|
|||||||
try? AVAudioSession.sharedInstance().setActive(true)
|
try? AVAudioSession.sharedInstance().setActive(true)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if self.isAutoplaying(playerItem!) {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||||
self?.play()
|
self?.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let replaceItemAndSeek = {
|
let replaceItemAndSeek = {
|
||||||
self.player.replaceCurrentItem(with: playerItem)
|
self.player.replaceCurrentItem(with: playerItem)
|
||||||
@ -440,7 +442,8 @@ final class PlayerModel: ObservableObject {
|
|||||||
MPMediaItemPropertyTitle: currentItem.video.title as AnyObject,
|
MPMediaItemPropertyTitle: currentItem.video.title as AnyObject,
|
||||||
MPMediaItemPropertyArtist: currentItem.video.author as AnyObject,
|
MPMediaItemPropertyArtist: currentItem.video.author as AnyObject,
|
||||||
MPMediaItemPropertyArtwork: currentArtwork 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,
|
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds as AnyObject,
|
||||||
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
|
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
|
||||||
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
|
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
|
||||||
|
@ -106,7 +106,7 @@ extension PlayerModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isAutoplaying(_ item: AVPlayerItem) -> Bool {
|
func isAutoplaying(_ item: AVPlayerItem) -> Bool {
|
||||||
player.currentItem == item
|
player.currentItem == item && presentingPlayer
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult func enqueueVideo(
|
@discardableResult func enqueueVideo(
|
||||||
|
@ -11,8 +11,6 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
let videoID = value.videoID.isEmpty ? value.video!.videoID : value.videoID
|
|
||||||
|
|
||||||
var playbackTime = ""
|
var playbackTime = ""
|
||||||
if let time = value.playbackTime {
|
if let time = value.playbackTime {
|
||||||
if time.seconds.isFinite {
|
if time.seconds.isFinite {
|
||||||
@ -28,7 +26,7 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
"videoID": videoID,
|
"videoID": value.videoID,
|
||||||
"playbackTime": playbackTime,
|
"playbackTime": playbackTime,
|
||||||
"videoDuration": videoDuration
|
"videoDuration": videoDuration
|
||||||
]
|
]
|
||||||
|
@ -2,7 +2,7 @@ import Defaults
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
final class SearchQuery: ObservableObject {
|
final class SearchQuery: ObservableObject {
|
||||||
enum Date: String, CaseIterable, Identifiable, DefaultsSerializable {
|
enum Date: String, CaseIterable, Identifiable {
|
||||||
case any, hour, today, week, month, year
|
case any, hour, today, week, month, year
|
||||||
|
|
||||||
var id: SearchQuery.Date.RawValue {
|
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
|
case any, short, long
|
||||||
|
|
||||||
var id: SearchQuery.Duration.RawValue {
|
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
|
case relevance, rating, uploadDate, viewCount
|
||||||
|
|
||||||
var id: SearchQuery.SortOrder.RawValue {
|
var id: SearchQuery.SortOrder.RawValue {
|
||||||
|
@ -3,11 +3,19 @@ import Defaults
|
|||||||
enum TrendingCategory: String, CaseIterable, Identifiable, Defaults.Serializable {
|
enum TrendingCategory: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||||
case `default`, music, gaming, movies
|
case `default`, music, gaming, movies
|
||||||
|
|
||||||
var id: TrendingCategory.RawValue {
|
var id: RawValue {
|
||||||
rawValue
|
rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
var name: String {
|
var title: RawValue {
|
||||||
rawValue.capitalized
|
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 */; };
|
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375168D52700FAFF008F96A6 /* Debounce.swift */; };
|
||||||
375168D82700FDB9008F96A6 /* 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 */; };
|
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 */; };
|
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375DFB5726F9DA010013F468 /* InstancesModel.swift */; };
|
||||||
375DFB5926F9DA010013F468 /* 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 */; };
|
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 */; };
|
3784B23B272894DA00B09468 /* ShareSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23A272894DA00B09468 /* ShareSheet.swift */; };
|
||||||
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23C2728B85300B09468 /* ShareButton.swift */; };
|
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3784B23C2728B85300B09468 /* ShareButton.swift */; };
|
||||||
3784B23E2728B85300B09468 /* 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 */; };
|
3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; };
|
||||||
3788AC2826F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; };
|
3788AC2826F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */; };
|
||||||
3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */; };
|
3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3788AC2626F6840700F6BAA9 /* FavoriteItemView.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 */; };
|
|
||||||
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378E50FA26FE8B9F00F49626 /* Instance.swift */; };
|
378E50FB26FE8B9F00F49626 /* Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 378E50FA26FE8B9F00F49626 /* Instance.swift */; };
|
||||||
378E50FC26FE8B9F00F49626 /* 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 */; };
|
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 */; };
|
37A9965A26D6F8CA006E3224 /* HorizontalCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* HorizontalCells.swift */; };
|
||||||
37A9965B26D6F8CA006E3224 /* 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 */; };
|
37A9965C26D6F8CA006E3224 /* HorizontalCells.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965926D6F8CA006E3224 /* HorizontalCells.swift */; };
|
||||||
37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
37A9965E26D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||||
37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
37A9965F26D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||||
37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* WatchNowView.swift */; };
|
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37A9965D26D6F9B9006E3224 /* FavoritesView.swift */; };
|
||||||
37AAF27E26737323007FC770 /* PopularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27D26737323007FC770 /* PopularView.swift */; };
|
37AAF27E26737323007FC770 /* PopularView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27D26737323007FC770 /* PopularView.swift */; };
|
||||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
37AAF28026737550007FC770 /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF27F26737550007FC770 /* SearchView.swift */; };
|
||||||
37AAF29026740715007FC770 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37AAF28F26740715007FC770 /* Channel.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 */; };
|
37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD526A1D4A90092E2DB /* PlayerViewController.swift */; };
|
||||||
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD926A214630092E2DB /* PlayerViewController.swift */; };
|
37BE0BDA26A214630092E2DB /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BD926A214630092E2DB /* PlayerViewController.swift */; };
|
||||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BDB26A2367F0092E2DB /* Player.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 */; };
|
37C069782725962F00F7F6CB /* ScreenSaverManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069772725962F00F7F6CB /* ScreenSaverManager.swift */; };
|
||||||
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
|
37C0697A2725C09E00F7F6CB /* PlayerQueueItemBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C069792725C09E00F7F6CB /* PlayerQueueItemBridge.swift */; };
|
||||||
37C0697B2725C09E00F7F6CB /* 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 */; };
|
37F64FE426FE70A60081B69E /* RedrawOnModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F64FE326FE70A60081B69E /* RedrawOnModifier.swift */; };
|
||||||
37F64FE526FE70A60081B69E /* 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 */; };
|
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 */; };
|
37FB28412721B22200A57617 /* ContentItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FB28402721B22200A57617 /* ContentItem.swift */; };
|
||||||
37FB28422721B22200A57617 /* 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 */; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItemView.swift; sourceTree = "<group>"; };
|
||||||
3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSectionBody.swift; sourceTree = "<group>"; };
|
|
||||||
378E50FA26FE8B9F00F49626 /* Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Instance.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>"; };
|
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>"; };
|
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>"; };
|
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; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
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>"; };
|
37FD43DB270470B70073EE42 /* InstancesSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InstancesSettings.swift; sourceTree = "<group>"; };
|
||||||
@ -814,6 +830,7 @@
|
|||||||
37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */,
|
37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */,
|
||||||
37FB285D272225E800A57617 /* ContentItemView.swift */,
|
37FB285D272225E800A57617 /* ContentItemView.swift */,
|
||||||
3748186D26A769D60084E870 /* DetailBadge.swift */,
|
3748186D26A769D60084E870 /* DetailBadge.swift */,
|
||||||
|
37599F37272B4D740087F250 /* FavoriteButton.swift */,
|
||||||
37152EE926EFEB95004FB96D /* LazyView.swift */,
|
37152EE926EFEB95004FB96D /* LazyView.swift */,
|
||||||
37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */,
|
37E70926271CDDAE00D34DDE /* OpenSettingsButton.swift */,
|
||||||
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */,
|
37E2EEAA270656EC00170416 /* PlayerControlsView.swift */,
|
||||||
@ -922,14 +939,15 @@
|
|||||||
name = Frameworks;
|
name = Frameworks;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
3788AC2126F683AB00F6BAA9 /* Watch Now */ = {
|
3788AC2126F683AB00F6BAA9 /* Favorites */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */,
|
37BF661E27308884008CCFB0 /* DropFavoriteOutside.swift */,
|
||||||
3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */,
|
37BF661B27308859008CCFB0 /* DropFavorite.swift */,
|
||||||
37A9965D26D6F9B9006E3224 /* WatchNowView.swift */,
|
3788AC2626F6840700F6BAA9 /* FavoriteItemView.swift */,
|
||||||
|
37A9965D26D6F9B9006E3224 /* FavoritesView.swift */,
|
||||||
);
|
);
|
||||||
path = "Watch Now";
|
path = Favorites;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
37992DC826CC50CD003D4C27 /* iOS */ = {
|
37992DC826CC50CD003D4C27 /* iOS */ = {
|
||||||
@ -1022,6 +1040,7 @@
|
|||||||
37D4B0C12671614700C925CA /* Shared */ = {
|
37D4B0C12671614700C925CA /* Shared */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
3788AC2126F683AB00F6BAA9 /* Favorites */,
|
||||||
37D526E12720B49200ED2F5E /* Gestures */,
|
37D526E12720B49200ED2F5E /* Gestures */,
|
||||||
3761AC0526F0F96100AA496F /* Modifiers */,
|
3761AC0526F0F96100AA496F /* Modifiers */,
|
||||||
371AAE2326CEB9E800901972 /* Navigation */,
|
371AAE2326CEB9E800901972 /* Navigation */,
|
||||||
@ -1031,7 +1050,6 @@
|
|||||||
371AAE2526CEBF0B00901972 /* Trending */,
|
371AAE2526CEBF0B00901972 /* Trending */,
|
||||||
371AAE2726CEBF4700901972 /* Videos */,
|
371AAE2726CEBF4700901972 /* Videos */,
|
||||||
371AAE2826CEC7D900901972 /* Views */,
|
371AAE2826CEC7D900901972 /* Views */,
|
||||||
3788AC2126F683AB00F6BAA9 /* Watch Now */,
|
|
||||||
375168D52700FAFF008F96A6 /* Debounce.swift */,
|
375168D52700FAFF008F96A6 /* Debounce.swift */,
|
||||||
372915E52687E3B900F5A35B /* Defaults.swift */,
|
372915E52687E3B900F5A35B /* Defaults.swift */,
|
||||||
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
|
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
|
||||||
@ -1082,6 +1100,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
37666BA927023AF000F869E5 /* AccountSelectionView.swift */,
|
37666BA927023AF000F869E5 /* AccountSelectionView.swift */,
|
||||||
|
37FADFFF272ED58000330459 /* EditFavorites.swift */,
|
||||||
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */,
|
3730D89F2712E2B70020ED53 /* NowPlayingView.swift */,
|
||||||
37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */,
|
37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */,
|
||||||
37D4B15E267164AF00C925CA /* Assets.xcassets */,
|
37D4B15E267164AF00C925CA /* Assets.xcassets */,
|
||||||
@ -1123,6 +1142,8 @@
|
|||||||
37C0698127260B2100F7F6CB /* ThumbnailsModel.swift */,
|
37C0698127260B2100F7F6CB /* ThumbnailsModel.swift */,
|
||||||
3705B181267B4E4900704544 /* TrendingCategory.swift */,
|
3705B181267B4E4900704544 /* TrendingCategory.swift */,
|
||||||
37D4B19626717E1500C925CA /* Video.swift */,
|
37D4B19626717E1500C925CA /* Video.swift */,
|
||||||
|
37599F2F272B42810087F250 /* FavoriteItem.swift */,
|
||||||
|
37599F33272B44000087F250 /* FavoritesModel.swift */,
|
||||||
);
|
);
|
||||||
path = Model;
|
path = Model;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1627,6 +1648,7 @@
|
|||||||
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||||
37CC3F45270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
37CC3F45270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
||||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
|
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
|
||||||
|
37599F38272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||||
37CB12792724C76D00213B45 /* VideoURLParser.swift in Sources */,
|
37CB12792724C76D00213B45 /* VideoURLParser.swift in Sources */,
|
||||||
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */,
|
3784B23D2728B85300B09468 /* ShareButton.swift in Sources */,
|
||||||
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||||
@ -1653,11 +1675,12 @@
|
|||||||
37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
37C0698227260B2100F7F6CB /* ThumbnailsModel.swift in Sources */,
|
||||||
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||||
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */,
|
37BE0BD326A1D4780092E2DB /* Player.swift in Sources */,
|
||||||
37A9965E26D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
37A9965E26D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||||
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
37CEE4C12677B697005A1EFE /* Stream.swift in Sources */,
|
||||||
37F4AE7226828F0900BD60EA /* VerticalCells.swift in Sources */,
|
37F4AE7226828F0900BD60EA /* VerticalCells.swift in Sources */,
|
||||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||||
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||||
|
37599F34272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||||
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
||||||
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
37CC3F4C270CFE1700608308 /* PlayerQueueView.swift in Sources */,
|
||||||
@ -1669,15 +1692,16 @@
|
|||||||
37E64DD126D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
|
37E64DD126D597EB00C71877 /* SubscriptionsModel.swift in Sources */,
|
||||||
376578892685471400D4EA09 /* Playlist.swift in Sources */,
|
376578892685471400D4EA09 /* Playlist.swift in Sources */,
|
||||||
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
373CFADB269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||||
3788AC2B26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
|
||||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||||
|
37BF661F27308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||||
3700155B271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
3700155B271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
||||||
37B044B726F7AB9000E1419D /* SettingsView.swift in Sources */,
|
37B044B726F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||||
377FC7E3267A084A00A6BBAF /* VideoCell.swift in Sources */,
|
377FC7E3267A084A00A6BBAF /* VideoCell.swift in Sources */,
|
||||||
37C3A251272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
37C3A251272366440087A57A /* ChannelPlaylistView.swift in Sources */,
|
||||||
37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
37E2EEAB270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||||
374C053527242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
374C053527242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
||||||
|
37BF661C27308859008CCFB0 /* DropFavorite.swift in Sources */,
|
||||||
376A33E42720CB35000C1D6B /* Account.swift in Sources */,
|
376A33E42720CB35000C1D6B /* Account.swift in Sources */,
|
||||||
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
37BA794326DBA973002A0235 /* PlaylistsModel.swift in Sources */,
|
||||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||||
@ -1711,13 +1735,14 @@
|
|||||||
37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
37D526DE2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||||
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */,
|
37484C2526FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||||
37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
37B767DB2677C3CA0098BAA8 /* PlayerModel.swift in Sources */,
|
||||||
3788AC2726F6840700F6BAA9 /* WatchNowSection.swift in Sources */,
|
3788AC2726F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||||
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */,
|
375DFB5826F9DA010013F468 /* InstancesModel.swift in Sources */,
|
||||||
37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||||
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
37141673267A8E10006CA35D /* Country.swift in Sources */,
|
||||||
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
3748186E26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||||
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
37AAF2A026741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||||
|
37599F30272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||||
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||||
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
37B81B0226D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||||
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
372915E62687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||||
@ -1751,8 +1776,8 @@
|
|||||||
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
37C194C826F6A9C8005D3B96 /* RecentsModel.swift in Sources */,
|
||||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
||||||
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
|
3743CA53270F284F00E4D32B /* View+Borders.swift in Sources */,
|
||||||
|
37599F39272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||||
37C3A24627235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
37C3A24627235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
||||||
3788AC2C26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
|
||||||
37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||||
374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
374C0540272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||||
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
374C053627242D9F009BDDBE /* ServicesSettings.swift in Sources */,
|
||||||
@ -1781,7 +1806,8 @@
|
|||||||
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
|
37484C3226FCB8F900287258 /* AccountValidator.swift in Sources */,
|
||||||
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||||
37EAD870267B9ED100D9E01B /* Segment.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 */,
|
37F64FE526FE70A60081B69E /* RedrawOnModifier.swift in Sources */,
|
||||||
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */,
|
||||||
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
37169AA72729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||||
@ -1796,12 +1822,14 @@
|
|||||||
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
37B044B826F7AB9000E1419D /* SettingsView.swift in Sources */,
|
||||||
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
3765788A2685471400D4EA09 /* Playlist.swift in Sources */,
|
||||||
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
37E2EEAC270656EC00170416 /* PlayerControlsView.swift in Sources */,
|
||||||
|
37BF662027308884008CCFB0 /* DropFavoriteOutside.swift in Sources */,
|
||||||
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
37E70924271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||||
374C0543272496E4009BDDBE /* AppDelegate.swift in Sources */,
|
374C0543272496E4009BDDBE /* AppDelegate.swift in Sources */,
|
||||||
373CFACC26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACC26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
37AAF29126740715007FC770 /* Channel.swift in Sources */,
|
37AAF29126740715007FC770 /* Channel.swift in Sources */,
|
||||||
376A33E12720CAD6000C1D6B /* VideosApp.swift in Sources */,
|
376A33E12720CAD6000C1D6B /* VideosApp.swift in Sources */,
|
||||||
37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
37BD07BC2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||||
|
37BF661D27308859008CCFB0 /* DropFavorite.swift in Sources */,
|
||||||
3748186F26A769D60084E870 /* DetailBadge.swift in Sources */,
|
3748186F26A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||||
372915E72687E3B900F5A35B /* Defaults.swift in Sources */,
|
372915E72687E3B900F5A35B /* Defaults.swift in Sources */,
|
||||||
37C3A242272359900087A57A /* Double+Format.swift in Sources */,
|
37C3A242272359900087A57A /* Double+Format.swift in Sources */,
|
||||||
@ -1815,7 +1843,7 @@
|
|||||||
37B81B0326D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
37B81B0326D2CAE700675966 /* PlaybackBar.swift in Sources */,
|
||||||
376A33E52720CB35000C1D6B /* Account.swift in Sources */,
|
376A33E52720CB35000C1D6B /* Account.swift in Sources */,
|
||||||
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||||
37A9965F26D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
37A9965F26D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||||
37F4AE7326828F0900BD60EA /* VerticalCells.swift in Sources */,
|
37F4AE7326828F0900BD60EA /* VerticalCells.swift in Sources */,
|
||||||
37001560271B12DD0049C794 /* SiestaConfiguration.swift in Sources */,
|
37001560271B12DD0049C794 /* SiestaConfiguration.swift in Sources */,
|
||||||
37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
37B81AFD26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
||||||
@ -1842,6 +1870,7 @@
|
|||||||
37CC3F46270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
37CC3F46270CE30600608308 /* PlayerQueueItem.swift in Sources */,
|
||||||
3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
3700155C271B0D4D0049C794 /* PipedAPI.swift in Sources */,
|
||||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||||
|
37599F31272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||||
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */,
|
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */,
|
||||||
37D526DF2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
37D526DF2720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||||
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||||
@ -1891,7 +1920,6 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
37AAF28026737550007FC770 /* SearchView.swift in Sources */,
|
37AAF28026737550007FC770 /* SearchView.swift in Sources */,
|
||||||
3788AC2D26F6842D00F6BAA9 /* WatchNowSectionBody.swift in Sources */,
|
|
||||||
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
|
37EAD871267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||||
37CC3F52270D010D00608308 /* VideoBanner.swift in Sources */,
|
37CC3F52270D010D00608308 /* VideoBanner.swift in Sources */,
|
||||||
37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
37F49BA526CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||||
@ -1911,7 +1939,7 @@
|
|||||||
376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */,
|
376B2E0926F920D600B1D64D /* SignInRequiredView.swift in Sources */,
|
||||||
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||||
37C3A24727235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
37C3A24727235DA70087A57A /* ChannelPlaylist.swift in Sources */,
|
||||||
3788AC2926F6840700F6BAA9 /* WatchNowSection.swift in Sources */,
|
3788AC2926F6840700F6BAA9 /* FavoriteItemView.swift in Sources */,
|
||||||
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
37319F0727103F94004ECCD0 /* PlayerQueue.swift in Sources */,
|
||||||
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
37E70925271CD43000D34DDE /* WelcomeScreen.swift in Sources */,
|
||||||
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
37DD87C9271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
|
||||||
@ -1940,11 +1968,12 @@
|
|||||||
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||||
37AAF27E26737323007FC770 /* PopularView.swift in Sources */,
|
37AAF27E26737323007FC770 /* PopularView.swift in Sources */,
|
||||||
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
3743CA50270EFE3400E4D32B /* PlayerQueueRow.swift in Sources */,
|
||||||
37A9966026D6F9B9006E3224 /* WatchNowView.swift in Sources */,
|
37A9966026D6F9B9006E3224 /* FavoritesView.swift in Sources */,
|
||||||
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
37001565271B1F250049C794 /* AccountsModel.swift in Sources */,
|
||||||
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
374C0541272472C0009BDDBE /* PlayerSponsorBlock.swift in Sources */,
|
||||||
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
|
||||||
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
376A33E62720CB35000C1D6B /* Account.swift in Sources */,
|
||||||
|
37599F3A272B4D740087F250 /* FavoriteButton.swift in Sources */,
|
||||||
37484C1F26FC83A400287258 /* InstancesSettings.swift in Sources */,
|
37484C1F26FC83A400287258 /* InstancesSettings.swift in Sources */,
|
||||||
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
37C7A1D7267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||||
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
376578932685490700D4EA09 /* PlaylistsView.swift in Sources */,
|
||||||
@ -1976,6 +2005,8 @@
|
|||||||
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
373CFACD26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||||
37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
37C3A24B27235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */,
|
||||||
37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */,
|
37FD43E52704847C0073EE42 /* View+Fixtures.swift in Sources */,
|
||||||
|
37FAE000272ED58000330459 /* EditFavorites.swift in Sources */,
|
||||||
|
37599F32272B42810087F250 /* FavoriteItem.swift in Sources */,
|
||||||
37141675267A8E10006CA35D /* Country.swift in Sources */,
|
37141675267A8E10006CA35D /* Country.swift in Sources */,
|
||||||
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */,
|
||||||
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
37484C2726FC83E000287258 /* InstanceForm.swift in Sources */,
|
||||||
@ -1984,6 +2015,7 @@
|
|||||||
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
378E50FD26FE8B9F00F49626 /* Instance.swift in Sources */,
|
||||||
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
37169AA82729E2CC0011DE61 /* AccountsBridge.swift in Sources */,
|
||||||
37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
37D526E02720AC4400ED2F5E /* VideosAPI.swift in Sources */,
|
||||||
|
37599F36272B44000087F250 /* FavoritesModel.swift in Sources */,
|
||||||
3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
3705B184267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||||
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
3761ABFF26F0F8DE00AA496F /* EnvironmentValues.swift in Sources */,
|
||||||
37C3A24F272360470087A57A /* ChannelPlaylist+Fixtures.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 sponsorBlockInstance = Key<String>("sponsorBlockInstance", default: "https://sponsor.ajay.app")
|
||||||
static let sponsorBlockCategories = Key<Set<String>>("sponsorBlockCategories", default: Set(SponsorBlockAPI.categories))
|
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 quality = Key<Stream.ResolutionSetting>("quality", default: .hd720pFirstThenBest)
|
||||||
|
|
||||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
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 {
|
var body: some View {
|
||||||
TabView(selection: navigation.tabSelectionBinding) {
|
TabView(selection: navigation.tabSelectionBinding) {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
LazyView(WatchNowView())
|
LazyView(FavoritesView())
|
||||||
.toolbar { toolbarContent }
|
.toolbar { toolbarContent }
|
||||||
}
|
}
|
||||||
.tabItem {
|
.tabItem {
|
||||||
Label("Watch Now", systemImage: "play.circle")
|
Label("Favorites", systemImage: "heart")
|
||||||
.accessibility(label: Text("Subscriptions"))
|
.accessibility(label: Text("Favorites"))
|
||||||
}
|
}
|
||||||
.tag(TabSelection.watchNow)
|
.tag(TabSelection.favorites)
|
||||||
|
|
||||||
if accounts.app.supportsSubscriptions {
|
if accounts.app.supportsSubscriptions {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
|
@ -28,9 +28,9 @@ struct Sidebar: View {
|
|||||||
|
|
||||||
var mainNavigationLinks: some View {
|
var mainNavigationLinks: some View {
|
||||||
Section("Videos") {
|
Section("Videos") {
|
||||||
NavigationLink(destination: LazyView(WatchNowView()), tag: TabSelection.watchNow, selection: $navigation.tabSelection) {
|
NavigationLink(destination: LazyView(FavoritesView()), tag: TabSelection.favorites, selection: $navigation.tabSelection) {
|
||||||
Label("Watch Now", systemImage: "play.circle")
|
Label("Favorites", systemImage: "heart")
|
||||||
.accessibility(label: Text("Watch Now"))
|
.accessibility(label: Text("Favorites"))
|
||||||
}
|
}
|
||||||
if accounts.app.supportsSubscriptions && accounts.signedIn {
|
if accounts.app.supportsSubscriptions && accounts.signedIn {
|
||||||
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
||||||
|
@ -13,13 +13,16 @@ struct VideoPlayerView: View {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@State private var playerSize: CGSize = .zero
|
|
||||||
@State private var fullScreen = false
|
@State private var fullScreen = false
|
||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@Environment(\.dismiss) private var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
@Environment(\.verticalSizeClass) private var verticalSizeClass
|
||||||
|
|
||||||
|
private var idiom: UIUserInterfaceIdiom {
|
||||||
|
UIDevice.current.userInterfaceIdiom
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@EnvironmentObject<PlayerModel> private var player
|
@EnvironmentObject<PlayerModel> private var player
|
||||||
@ -75,12 +78,6 @@ struct VideoPlayerView: View {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
.background(.black)
|
.background(.black)
|
||||||
.onAppear {
|
|
||||||
self.playerSize = geometry.size
|
|
||||||
}
|
|
||||||
.onChange(of: geometry.size) { size in
|
|
||||||
self.playerSize = size
|
|
||||||
}
|
|
||||||
|
|
||||||
Group {
|
Group {
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
@ -134,7 +131,7 @@ struct VideoPlayerView: View {
|
|||||||
|
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
var sidebarQueue: Bool {
|
var sidebarQueue: Bool {
|
||||||
horizontalSizeClass == .regular && playerSize.width > 750
|
horizontalSizeClass == .regular && idiom == .pad
|
||||||
}
|
}
|
||||||
|
|
||||||
var sidebarQueueBinding: Binding<Bool> {
|
var sidebarQueueBinding: Binding<Bool> {
|
||||||
|
@ -75,6 +75,8 @@ struct PlaylistsView: View {
|
|||||||
editPlaylistButton
|
editPlaylistButton
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
FavoriteButton(item: FavoriteItem(section: .playlist(selectedPlaylistID)))
|
||||||
|
|
||||||
newPlaylistButton
|
newPlaylistButton
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,6 +141,11 @@ struct PlaylistsView: View {
|
|||||||
editPlaylistButton
|
editPlaylistButton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let playlist = currentPlaylist {
|
||||||
|
FavoriteButton(item: FavoriteItem(section: .playlist(playlist.id)))
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
newPlaylistButton
|
newPlaylistButton
|
||||||
|
@ -95,6 +95,8 @@ struct ServicesSettings: View {
|
|||||||
|
|
||||||
struct ServicesSettings_Previews: PreviewProvider {
|
struct ServicesSettings_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
|
VStack {
|
||||||
ServicesSettings()
|
ServicesSettings()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,9 +11,11 @@ struct TrendingView: View {
|
|||||||
|
|
||||||
@State private var presentingCountrySelection = false
|
@State private var presentingCountrySelection = false
|
||||||
|
|
||||||
|
@State private var favoriteItem: FavoriteItem?
|
||||||
|
|
||||||
@EnvironmentObject<AccountsModel> private var accounts
|
@EnvironmentObject<AccountsModel> private var accounts
|
||||||
|
|
||||||
var popular: [ContentItem] {
|
var trending: [ContentItem] {
|
||||||
ContentItem.array(of: store.collection)
|
ContentItem.array(of: store.collection)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,12 +38,12 @@ struct TrendingView: View {
|
|||||||
VStack(alignment: .center, spacing: 0) {
|
VStack(alignment: .center, spacing: 0) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
toolbar
|
toolbar
|
||||||
HorizontalCells(items: popular)
|
HorizontalCells(items: trending)
|
||||||
.padding(.top, 40)
|
.padding(.top, 40)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
#else
|
#else
|
||||||
VerticalCells(items: popular)
|
VerticalCells(items: trending)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,6 +64,11 @@ struct TrendingView: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
#if os(macOS)
|
#if os(macOS)
|
||||||
ToolbarItemGroup {
|
ToolbarItemGroup {
|
||||||
|
if let favoriteItem = favoriteItem {
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem.id)
|
||||||
|
}
|
||||||
|
|
||||||
if accounts.app.supportsTrendingCategories {
|
if accounts.app.supportsTrendingCategories {
|
||||||
categoryButton
|
categoryButton
|
||||||
}
|
}
|
||||||
@ -70,8 +77,8 @@ struct TrendingView: View {
|
|||||||
#elseif os(iOS)
|
#elseif os(iOS)
|
||||||
ToolbarItemGroup(placement: .bottomBar) {
|
ToolbarItemGroup(placement: .bottomBar) {
|
||||||
Group {
|
Group {
|
||||||
if accounts.app.supportsTrendingCategories {
|
|
||||||
HStack {
|
HStack {
|
||||||
|
if accounts.app.supportsTrendingCategories {
|
||||||
Text("Category")
|
Text("Category")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
@ -80,7 +87,14 @@ struct TrendingView: View {
|
|||||||
// force redraw of the view when it changes
|
// force redraw of the view when it changes
|
||||||
.id(UUID())
|
.id(UUID())
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
if let favoriteItem = favoriteItem {
|
||||||
|
FavoriteButton(item: favoriteItem)
|
||||||
|
.id(favoriteItem.id)
|
||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +110,7 @@ struct TrendingView: View {
|
|||||||
}
|
}
|
||||||
.onChange(of: resource) { _ in
|
.onChange(of: resource) { _ in
|
||||||
resource.load()
|
resource.load()
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if videos.isEmpty {
|
if videos.isEmpty {
|
||||||
@ -104,10 +119,12 @@ struct TrendingView: View {
|
|||||||
} else {
|
} else {
|
||||||
store.replace(videos)
|
store.replace(videos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateFavoriteItem()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var toolbar: some View {
|
private var toolbar: some View {
|
||||||
HStack {
|
HStack {
|
||||||
if accounts.app.supportsTrendingCategories {
|
if accounts.app.supportsTrendingCategories {
|
||||||
HStack {
|
HStack {
|
||||||
@ -128,17 +145,25 @@ struct TrendingView: View {
|
|||||||
|
|
||||||
countryButton
|
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)
|
#if os(tvOS)
|
||||||
Button(category.name) {
|
Button(category.name) {
|
||||||
self.category = category.next()
|
self.category = category.next()
|
||||||
}
|
}
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
ForEach(TrendingCategory.allCases) { category in
|
ForEach(TrendingCategory.allCases) { category in
|
||||||
Button(category.name) { self.category = category }
|
Button(category.controlLabel) { self.category = category }
|
||||||
}
|
}
|
||||||
|
|
||||||
Button("Cancel", role: .cancel) {}
|
Button("Cancel", role: .cancel) {}
|
||||||
@ -147,13 +172,13 @@ struct TrendingView: View {
|
|||||||
#else
|
#else
|
||||||
Picker("Category", selection: $category) {
|
Picker("Category", selection: $category) {
|
||||||
ForEach(TrendingCategory.allCases) { category in
|
ForEach(TrendingCategory.allCases) { category in
|
||||||
Text(category.name).tag(category)
|
Text(category.controlLabel).tag(category)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var countryButton: some View {
|
private var countryButton: some View {
|
||||||
Button(action: {
|
Button(action: {
|
||||||
presentingCountrySelection.toggle()
|
presentingCountrySelection.toggle()
|
||||||
resource.removeObservers(ownedBy: store)
|
resource.removeObservers(ownedBy: store)
|
||||||
@ -161,6 +186,10 @@ struct TrendingView: View {
|
|||||||
Text("\(country.flag) \(country.id)")
|
Text("\(country.flag) \(country.id)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func updateFavoriteItem() {
|
||||||
|
favoriteItem = FavoriteItem(section: .trending(country.rawValue, category.rawValue))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TrendingView_Previews: PreviewProvider {
|
struct TrendingView_Previews: PreviewProvider {
|
||||||
|
@ -143,8 +143,6 @@ struct VideoCell: View {
|
|||||||
#endif
|
#endif
|
||||||
.padding(.bottom, 4)
|
.padding(.bottom, 4)
|
||||||
|
|
||||||
Group {
|
|
||||||
if additionalDetailsAvailable {
|
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
if let date = video.publishedDate {
|
if let date = video.publishedDate {
|
||||||
Image(systemName: "calendar")
|
Image(systemName: "calendar")
|
||||||
@ -157,10 +155,6 @@ struct VideoCell: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
} else {
|
|
||||||
Spacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.frame(minHeight: 30, alignment: .top)
|
.frame(minHeight: 30, alignment: .top)
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
|
@ -40,9 +40,16 @@ struct ChannelPlaylistView: View {
|
|||||||
var content: some View {
|
var content: some View {
|
||||||
VStack(alignment: .leading) {
|
VStack(alignment: .leading) {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
|
HStack {
|
||||||
Text(playlist.title)
|
Text(playlist.title)
|
||||||
.font(.title2)
|
.font(.title2)
|
||||||
.frame(alignment: .leading)
|
.frame(alignment: .leading)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
VerticalCells(items: items)
|
VerticalCells(items: items)
|
||||||
}
|
}
|
||||||
@ -66,12 +73,8 @@ struct ChannelPlaylistView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem {
|
||||||
if inNavigationView {
|
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||||
Button("Done") {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(playlist.title)
|
.navigationTitle(playlist.title)
|
||||||
|
@ -51,6 +51,9 @@ struct ChannelVideosView: View {
|
|||||||
|
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|
||||||
|
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||||
|
.labelStyle(.iconOnly)
|
||||||
|
|
||||||
if let subscribers = store.item?.subscriptionsString {
|
if let subscribers = store.item?.subscriptionsString {
|
||||||
Text("**\(subscribers)** subscribers")
|
Text("**\(subscribers)** subscribers")
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
@ -87,14 +90,8 @@ struct ChannelVideosView: View {
|
|||||||
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
|
.opacity(store.item?.subscriptionsString != nil ? 1 : 0)
|
||||||
|
|
||||||
subscriptionToggleButton
|
subscriptionToggleButton
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||||
if inNavigationView {
|
|
||||||
Button("Done") {
|
|
||||||
dismiss()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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")
|
.navigationTitle("\(playlist.title) Playlist")
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem {
|
||||||
|
FavoriteButton(item: FavoriteItem(section: .playlist(playlist.id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,5 +25,10 @@ struct PopularView: View {
|
|||||||
.navigationTitle("Popular")
|
.navigationTitle("Popular")
|
||||||
#endif
|
#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 {
|
.refreshable {
|
||||||
loadResources(force: true)
|
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 {
|
var body: some View {
|
||||||
TabView(selection: navigation.tabSelectionBinding) {
|
TabView(selection: navigation.tabSelectionBinding) {
|
||||||
WatchNowView()
|
FavoritesView()
|
||||||
.tabItem { Text("Watch Now") }
|
.tabItem { Text("Favorites") }
|
||||||
.tag(TabSelection.watchNow)
|
.tag(TabSelection.favorites)
|
||||||
|
|
||||||
if accounts.app.supportsSubscriptions {
|
if accounts.app.supportsSubscriptions {
|
||||||
SubscriptionsView()
|
SubscriptionsView()
|
||||||
|
Loading…
Reference in New Issue
Block a user