mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Managing Favorites sections
This commit is contained in:
@@ -60,6 +60,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "channel/\(id)")
|
||||
}
|
||||
|
||||
func channelVideos(_ id: String) -> Resource {
|
||||
channel(id)
|
||||
}
|
||||
|
||||
func channelPlaylist(_ id: String) -> Resource? {
|
||||
resource(baseURL: account.url, path: "playlists/\(id)")
|
||||
}
|
||||
@@ -94,6 +98,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
|
||||
func channelSubscription(_: String) -> Resource? { nil }
|
||||
|
||||
func playlist(_: String) -> Resource? { nil }
|
||||
func playlistVideo(_: String, _: String) -> Resource? { nil }
|
||||
func playlistVideos(_: String) -> Resource? { nil }
|
||||
|
||||
|
@@ -6,6 +6,7 @@ protocol VideosAPI {
|
||||
var signedIn: Bool { get }
|
||||
|
||||
func channel(_ id: String) -> Resource
|
||||
func channelVideos(_ id: String) -> Resource
|
||||
func trending(country: Country, category: TrendingCategory?) -> Resource
|
||||
func search(_ query: SearchQuery) -> Resource
|
||||
func searchSuggestions(query: String) -> Resource
|
||||
@@ -20,6 +21,7 @@ protocol VideosAPI {
|
||||
|
||||
func channelSubscription(_ id: String) -> Resource?
|
||||
|
||||
func playlist(_ id: String) -> Resource?
|
||||
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?
|
||||
func playlistVideos(_ id: String) -> Resource?
|
||||
|
||||
|
39
Model/FavoriteItem.swift
Normal file
39
Model/FavoriteItem.swift
Normal file
@@ -0,0 +1,39 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
|
||||
struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
||||
enum Section: Codable, Equatable, Defaults.Serializable {
|
||||
case subscriptions
|
||||
case popular
|
||||
case trending(String, String?)
|
||||
case channel(String, String)
|
||||
case playlist(String)
|
||||
case channelPlaylist(String, String)
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .subscriptions:
|
||||
return "Subscriptions"
|
||||
case .popular:
|
||||
return "Popular"
|
||||
case let .trending(country, category):
|
||||
let trendingCountry = Country(rawValue: country)!
|
||||
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)!
|
||||
return "\(trendingCountry.flag) \(trendingCategory?.name ?? "")"
|
||||
case let .channel(_, name):
|
||||
return name
|
||||
case let .channelPlaylist(_, name):
|
||||
return name
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func == (lhs: FavoriteItem, rhs: FavoriteItem) -> Bool {
|
||||
lhs.section == rhs.section
|
||||
}
|
||||
|
||||
var id = UUID().uuidString
|
||||
var section: Section
|
||||
}
|
77
Model/FavoritesModel.swift
Normal file
77
Model/FavoritesModel.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
|
||||
struct FavoritesModel {
|
||||
static let shared = FavoritesModel()
|
||||
|
||||
@Default(.favorites) var all
|
||||
|
||||
func contains(_ item: FavoriteItem) -> Bool {
|
||||
all.contains { $0 == item }
|
||||
}
|
||||
|
||||
func toggle(_ item: FavoriteItem) {
|
||||
contains(item) ? remove(item) : add(item)
|
||||
}
|
||||
|
||||
func add(_ item: FavoriteItem) {
|
||||
all.append(item)
|
||||
}
|
||||
|
||||
func remove(_ item: FavoriteItem) {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
all.remove(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
func canMoveUp(_ item: FavoriteItem) -> Bool {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
return index > all.startIndex
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func canMoveDown(_ item: FavoriteItem) -> Bool {
|
||||
if let index = all.firstIndex(where: { $0 == item }) {
|
||||
return index < all.endIndex - 1
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func moveUp(_ item: FavoriteItem) {
|
||||
guard canMoveUp(item) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let from = all.firstIndex(where: { $0 == item }) {
|
||||
all.move(
|
||||
fromOffsets: IndexSet(integer: from),
|
||||
toOffset: from - 1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func moveDown(_ item: FavoriteItem) {
|
||||
guard canMoveDown(item) else {
|
||||
return
|
||||
}
|
||||
|
||||
if let from = all.firstIndex(where: { $0 == item }) {
|
||||
all.move(
|
||||
fromOffsets: IndexSet(integer: from),
|
||||
toOffset: from + 2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func addableItems() -> [FavoriteItem] {
|
||||
let allItems = [
|
||||
FavoriteItem(section: .subscriptions),
|
||||
FavoriteItem(section: .popular)
|
||||
]
|
||||
|
||||
return allItems.filter { item in !all.contains { $0.section == item.section } }
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ import SwiftUI
|
||||
|
||||
final class NavigationModel: ObservableObject {
|
||||
enum TabSelection: Hashable {
|
||||
case watchNow
|
||||
case favorites
|
||||
case subscriptions
|
||||
case popular
|
||||
case trending
|
||||
@@ -23,7 +23,7 @@ final class NavigationModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Published var tabSelection: TabSelection! = .watchNow
|
||||
@Published var tabSelection: TabSelection! = .favorites
|
||||
|
||||
@Published var presentingAddToPlaylist = false
|
||||
@Published var videoToAddToPlaylist: Video!
|
||||
@@ -44,7 +44,7 @@ final class NavigationModel: ObservableObject {
|
||||
var tabSelectionBinding: Binding<TabSelection> {
|
||||
Binding<TabSelection>(
|
||||
get: {
|
||||
self.tabSelection ?? .watchNow
|
||||
self.tabSelection ?? .favorites
|
||||
},
|
||||
set: { newValue in
|
||||
self.tabSelection = newValue
|
||||
|
@@ -168,8 +168,10 @@ final class PlayerModel: ObservableObject {
|
||||
try? AVAudioSession.sharedInstance().setActive(true)
|
||||
#endif
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
self?.play()
|
||||
if self.isAutoplaying(playerItem!) {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { [weak self] in
|
||||
self?.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +442,8 @@ final class PlayerModel: ObservableObject {
|
||||
MPMediaItemPropertyTitle: currentItem.video.title as AnyObject,
|
||||
MPMediaItemPropertyArtist: currentItem.video.author as AnyObject,
|
||||
MPMediaItemPropertyArtwork: currentArtwork as AnyObject,
|
||||
MPMediaItemPropertyPlaybackDuration: Int(currentItem.videoDuration ?? 0) as AnyObject,
|
||||
MPMediaItemPropertyPlaybackDuration: (currentItem.video.live ? nil : Int(currentItem.videoDuration ?? 0)) as AnyObject,
|
||||
MPNowPlayingInfoPropertyIsLiveStream: currentItem.video.live as AnyObject,
|
||||
MPNowPlayingInfoPropertyElapsedPlaybackTime: player.currentTime().seconds as AnyObject,
|
||||
MPNowPlayingInfoPropertyPlaybackQueueCount: queue.count as AnyObject,
|
||||
MPMediaItemPropertyMediaType: MPMediaType.anyVideo.rawValue as AnyObject
|
||||
|
@@ -106,7 +106,7 @@ extension PlayerModel {
|
||||
}
|
||||
|
||||
func isAutoplaying(_ item: AVPlayerItem) -> Bool {
|
||||
player.currentItem == item
|
||||
player.currentItem == item && presentingPlayer
|
||||
}
|
||||
|
||||
@discardableResult func enqueueVideo(
|
||||
|
@@ -11,8 +11,6 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
||||
return nil
|
||||
}
|
||||
|
||||
let videoID = value.videoID.isEmpty ? value.video!.videoID : value.videoID
|
||||
|
||||
var playbackTime = ""
|
||||
if let time = value.playbackTime {
|
||||
if time.seconds.isFinite {
|
||||
@@ -28,7 +26,7 @@ struct PlayerQueueItemBridge: Defaults.Bridge {
|
||||
}
|
||||
|
||||
return [
|
||||
"videoID": videoID,
|
||||
"videoID": value.videoID,
|
||||
"playbackTime": playbackTime,
|
||||
"videoDuration": videoDuration
|
||||
]
|
||||
|
@@ -2,7 +2,7 @@ import Defaults
|
||||
import Foundation
|
||||
|
||||
final class SearchQuery: ObservableObject {
|
||||
enum Date: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum Date: String, CaseIterable, Identifiable {
|
||||
case any, hour, today, week, month, year
|
||||
|
||||
var id: SearchQuery.Date.RawValue {
|
||||
@@ -14,7 +14,7 @@ final class SearchQuery: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
enum Duration: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum Duration: String, CaseIterable, Identifiable {
|
||||
case any, short, long
|
||||
|
||||
var id: SearchQuery.Duration.RawValue {
|
||||
@@ -26,7 +26,7 @@ final class SearchQuery: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
enum SortOrder: String, CaseIterable, Identifiable, DefaultsSerializable {
|
||||
enum SortOrder: String, CaseIterable, Identifiable {
|
||||
case relevance, rating, uploadDate, viewCount
|
||||
|
||||
var id: SearchQuery.SortOrder.RawValue {
|
||||
|
@@ -3,11 +3,19 @@ import Defaults
|
||||
enum TrendingCategory: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||
case `default`, music, gaming, movies
|
||||
|
||||
var id: TrendingCategory.RawValue {
|
||||
var id: RawValue {
|
||||
rawValue
|
||||
}
|
||||
|
||||
var name: String {
|
||||
var title: RawValue {
|
||||
rawValue.capitalized
|
||||
}
|
||||
|
||||
var name: String {
|
||||
id == "default" ? "Trending" : title
|
||||
}
|
||||
|
||||
var controlLabel: String {
|
||||
id == "default" ? "All" : title
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user