Managing Favorites sections

This commit is contained in:
Arkadiusz Fal
2021-11-01 22:56:18 +01:00
parent f11125a399
commit 8df452752a
35 changed files with 665 additions and 203 deletions

View File

@@ -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 }

View File

@@ -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
View 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
}

View 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 } }
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -106,7 +106,7 @@ extension PlayerModel {
}
func isAutoplaying(_ item: AVPlayerItem) -> Bool {
player.currentItem == item
player.currentItem == item && presentingPlayer
}
@discardableResult func enqueueVideo(

View File

@@ -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
]

View File

@@ -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 {

View File

@@ -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
}
}