mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 05:23:41 +00:00
Adding/removing videos to/from playlists
This commit is contained in:
parent
31bd2f7fe6
commit
f397b13720
97
Apple TV/AddToPlaylistView.swift
Normal file
97
Apple TV/AddToPlaylistView.swift
Normal file
@ -0,0 +1,97 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct AddToPlaylistView: View {
|
||||
@ObservedObject private var store = Store<[Playlist]>()
|
||||
|
||||
@State private var selectedPlaylist: Playlist?
|
||||
|
||||
@Default(.videoIDToAddToPlaylist) private var videoID
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
var resource: Resource {
|
||||
InvidiousAPI.shared.playlists
|
||||
}
|
||||
|
||||
init() {
|
||||
resource.addObserver(store)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
VStack {
|
||||
Spacer()
|
||||
|
||||
if !resource.isLoading && store.collection.isEmpty {
|
||||
CoverSectionView("You have no Playlists", inline: true) {
|
||||
Text("Open \"Playlists\" tab to create new one")
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
Button("Go back") {
|
||||
dismiss()
|
||||
}
|
||||
.padding()
|
||||
} else if !store.collection.isEmpty {
|
||||
CoverSectionView("Add to Playlist", inline: true) { selectPlaylistButton }
|
||||
|
||||
CoverSectionRowView {
|
||||
Button("Add", action: addToPlaylist)
|
||||
.disabled(currentPlaylist == nil)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.frame(maxWidth: 1200)
|
||||
|
||||
Spacer()
|
||||
}
|
||||
.background(.thinMaterial)
|
||||
.onAppear {
|
||||
resource.loadIfNeeded()?.onSuccess { _ in
|
||||
selectedPlaylist = store.collection.first
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selectPlaylistButton: some View {
|
||||
Button(currentPlaylist?.title ?? "Select playlist") {
|
||||
guard currentPlaylist != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
self.selectedPlaylist = store.collection.next(after: currentPlaylist!)
|
||||
}
|
||||
.contextMenu {
|
||||
ForEach(store.collection) { playlist in
|
||||
Button(playlist.title) {
|
||||
self.selectedPlaylist = playlist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var currentPlaylist: Playlist? {
|
||||
selectedPlaylist ?? store.collection.first
|
||||
}
|
||||
|
||||
func addToPlaylist() {
|
||||
guard currentPlaylist != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
let resource = InvidiousAPI.shared.playlistVideos(currentPlaylist!.id)
|
||||
let body = ["videoId": videoID]
|
||||
|
||||
resource.request(.post, json: body).onSuccess { _ in
|
||||
Defaults.reset(.videoIDToAddToPlaylist)
|
||||
InvidiousAPI.shared.playlists.load()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
@ -6,7 +6,6 @@ struct PlaylistsView: View {
|
||||
@ObservedObject private var store = Store<[Playlist]>()
|
||||
|
||||
@Default(.selectedPlaylistID) private var selectedPlaylistID
|
||||
@State private var selectedPlaylist: Playlist?
|
||||
|
||||
@State private var showingNewPlaylist = false
|
||||
@State private var createdPlaylist: Playlist?
|
||||
@ -26,18 +25,37 @@ struct PlaylistsView: View {
|
||||
Section {
|
||||
VStack(alignment: .center, spacing: 2) {
|
||||
HStack {
|
||||
selectPlaylistButton
|
||||
if store.collection.isEmpty {
|
||||
Text("No Playlists")
|
||||
.foregroundColor(.secondary)
|
||||
} else {
|
||||
Text("Current Playlist")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
selectPlaylistButton
|
||||
}
|
||||
|
||||
if currentPlaylist != nil {
|
||||
editPlaylistButton
|
||||
}
|
||||
|
||||
newPlaylistButton
|
||||
.padding(.leading, 40)
|
||||
}
|
||||
.scaleEffect(0.85)
|
||||
|
||||
if currentPlaylist != nil {
|
||||
VideosView(videos: currentPlaylist!.videos)
|
||||
if currentPlaylist!.videos.isEmpty {
|
||||
Spacer()
|
||||
|
||||
Text("Playlist is empty\n\nTap and hold on a video and then tap \"Add to Playlist\"")
|
||||
.foregroundColor(.secondary)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Spacer()
|
||||
} else {
|
||||
VideosView(videos: currentPlaylist!.videos)
|
||||
}
|
||||
} else {
|
||||
Spacer()
|
||||
}
|
||||
@ -57,7 +75,6 @@ struct PlaylistsView: View {
|
||||
}
|
||||
|
||||
func selectPlaylist(_ id: String?) {
|
||||
selectedPlaylist = store.collection.first { $0.id == id }
|
||||
selectedPlaylistID = id
|
||||
}
|
||||
|
||||
@ -86,7 +103,7 @@ struct PlaylistsView: View {
|
||||
}
|
||||
|
||||
var currentPlaylist: Playlist? {
|
||||
selectedPlaylist ?? store.collection.first
|
||||
store.collection.first { $0.id == selectedPlaylistID } ?? store.collection.first
|
||||
}
|
||||
|
||||
var selectPlaylistButton: some View {
|
||||
@ -111,13 +128,19 @@ struct PlaylistsView: View {
|
||||
self.editedPlaylist = self.currentPlaylist
|
||||
self.showingEditPlaylist = true
|
||||
}) {
|
||||
Image(systemName: "pencil")
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "pencil")
|
||||
Text("Edit")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var newPlaylistButton: some View {
|
||||
Button(action: { self.showingNewPlaylist = true }) {
|
||||
Image(systemName: "plus")
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "plus")
|
||||
Text("New Playlist")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,14 @@ struct TrendingView: View {
|
||||
Section {
|
||||
VStack(alignment: .center, spacing: 2) {
|
||||
HStack {
|
||||
Text("Category")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
categoryButton
|
||||
|
||||
Text("Country")
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
countryFlag
|
||||
countryButton
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ struct VideoContextMenuView: View {
|
||||
@Default(.openVideoID) var openVideoID
|
||||
@Default(.showingVideoDetails) var showDetails
|
||||
|
||||
@Default(.showingAddToPlaylist) var showingAddToPlaylist
|
||||
@Default(.videoIDToAddToPlaylist) var videoIDToAddToPlaylist
|
||||
|
||||
var body: some View {
|
||||
if tabSelection == .channel {
|
||||
closeChannelButton(from: video)
|
||||
@ -16,9 +19,12 @@ struct VideoContextMenuView: View {
|
||||
openChannelButton(from: video)
|
||||
}
|
||||
|
||||
Button("Open video details") {
|
||||
openVideoID = video.id
|
||||
showDetails = true
|
||||
openVideoDetailsButton
|
||||
|
||||
if tabSelection == .playlists {
|
||||
removeFromPlaylistButton
|
||||
} else {
|
||||
addToPlaylistButton
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,4 +40,27 @@ struct VideoContextMenuView: View {
|
||||
Defaults.reset(.openChannel)
|
||||
}
|
||||
}
|
||||
|
||||
var openVideoDetailsButton: some View {
|
||||
Button("Open video details") {
|
||||
openVideoID = video.id
|
||||
showDetails = true
|
||||
}
|
||||
}
|
||||
|
||||
var addToPlaylistButton: some View {
|
||||
Button("Add to playlist...") {
|
||||
videoIDToAddToPlaylist = video.id
|
||||
showingAddToPlaylist = true
|
||||
}
|
||||
}
|
||||
|
||||
var removeFromPlaylistButton: some View {
|
||||
Button("Remove from playlist", role: .destructive) {
|
||||
let resource = InvidiousAPI.shared.playlistVideo(Defaults[.selectedPlaylistID]!, video.indexID!)
|
||||
resource.request(.delete).onSuccess { _ in
|
||||
InvidiousAPI.shared.playlists.load()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ struct VideoDetailsView: View {
|
||||
|
||||
HStack {
|
||||
NavigationLink(destination: PlayerView(id: video.id)) {
|
||||
HStack(spacing: 10) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "play.rectangle.fill")
|
||||
|
||||
Text("Play")
|
||||
|
@ -7,15 +7,20 @@ struct VideosView: View {
|
||||
@Default(.layout) var layout
|
||||
@Default(.tabSelection) var tabSelection
|
||||
|
||||
@Default(.showingAddToPlaylist) var showingAddToPlaylist
|
||||
|
||||
var videos: [Video]
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
VStack {
|
||||
if layout == .cells {
|
||||
VideosCellsView(videos: videos, columns: self.profile.cellsColumns)
|
||||
} else {
|
||||
VideosListView(videos: videos)
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $showingAddToPlaylist) {
|
||||
AddToPlaylistView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -112,6 +112,14 @@ final class InvidiousAPI: Service {
|
||||
resource("/auth/playlists/\(id)")
|
||||
}
|
||||
|
||||
func playlistVideos(_ id: String) -> Resource {
|
||||
playlist(id).child("videos")
|
||||
}
|
||||
|
||||
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource {
|
||||
playlist(playlistID).child("videos").child(videoID)
|
||||
}
|
||||
|
||||
func search(_ query: SearchQuery) -> Resource {
|
||||
var resource = resource("/search")
|
||||
.withParam("q", searchQuery(query.query))
|
||||
|
@ -6,17 +6,20 @@ struct Playlist: Identifiable, Equatable, Hashable {
|
||||
var title: String
|
||||
var visibility: PlaylistVisibility
|
||||
|
||||
var updated: TimeInterval
|
||||
|
||||
var videos = [Video]()
|
||||
|
||||
init(_ json: JSON) {
|
||||
id = json["playlistId"].stringValue
|
||||
title = json["title"].stringValue
|
||||
visibility = json["isListed"].boolValue ? .public : .private
|
||||
updated = json["updated"].doubleValue
|
||||
videos = json["videos"].arrayValue.map { Video($0) }
|
||||
}
|
||||
|
||||
static func == (lhs: Playlist, rhs: Playlist) -> Bool {
|
||||
lhs.id == rhs.id && lhs.title == rhs.title && lhs.visibility == rhs.visibility
|
||||
lhs.id == rhs.id && lhs.updated == rhs.updated
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
|
8
Model/PlaylistVideo.swift
Normal file
8
Model/PlaylistVideo.swift
Normal file
@ -0,0 +1,8 @@
|
||||
//
|
||||
// PlaylistVideo.swift
|
||||
// Pearvidious
|
||||
//
|
||||
// Created by Arkadiusz Fal on 09/07/2021.
|
||||
//
|
||||
|
||||
import Foundation
|
@ -15,10 +15,21 @@ struct Video: Identifiable {
|
||||
var description: String
|
||||
var genre: String
|
||||
|
||||
let indexID: String?
|
||||
|
||||
var streams = [Stream]()
|
||||
|
||||
init(_ json: JSON) {
|
||||
id = json["videoId"].stringValue
|
||||
let videoID = json["videoId"].stringValue
|
||||
|
||||
if let id = json["indexId"].string {
|
||||
indexID = id
|
||||
self.id = videoID + id
|
||||
} else {
|
||||
indexID = nil
|
||||
id = videoID
|
||||
}
|
||||
|
||||
title = json["title"].stringValue
|
||||
author = json["author"].stringValue
|
||||
length = json["lengthSeconds"].doubleValue
|
||||
|
@ -60,6 +60,9 @@
|
||||
373CFAEB26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */; };
|
||||
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */; };
|
||||
373CFAED26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */; };
|
||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */; };
|
||||
3741B5302676213400125C5E /* PlayerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3741B52F2676213400125C5E /* PlayerViewController.swift */; };
|
||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
||||
376578862685429C00D4EA09 /* CaseIterable+Next.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376578842685429C00D4EA09 /* CaseIterable+Next.swift */; };
|
||||
@ -231,6 +234,7 @@
|
||||
373CFADA269663F1003CB2C6 /* Thumbnail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thumbnail.swift; sourceTree = "<group>"; };
|
||||
373CFAE226974812003CB2C6 /* PlaylistVisibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistVisibility.swift; sourceTree = "<group>"; };
|
||||
373CFAEA26975CBF003CB2C6 /* PlaylistFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistFormView.swift; sourceTree = "<group>"; };
|
||||
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToPlaylistView.swift; sourceTree = "<group>"; };
|
||||
3741B52F2676213400125C5E /* PlayerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewController.swift; sourceTree = "<group>"; };
|
||||
376578842685429C00D4EA09 /* CaseIterable+Next.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CaseIterable+Next.swift"; sourceTree = "<group>"; };
|
||||
376578882685471400D4EA09 /* Playlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlist.swift; sourceTree = "<group>"; };
|
||||
@ -420,6 +424,7 @@
|
||||
37D4B159267164AE00C925CA /* Apple TV */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
373CFAEE2697A78B003CB2C6 /* AddToPlaylistView.swift */,
|
||||
37AAF2892673AB89007FC770 /* ChannelView.swift */,
|
||||
373CFAC126966159003CB2C6 /* CoverSectionRowView.swift */,
|
||||
373CFABD26966115003CB2C6 /* CoverSectionView.swift */,
|
||||
@ -442,8 +447,8 @@
|
||||
37F4AE7126828F0900BD60EA /* VideosCellsView.swift */,
|
||||
37AAF29926740A01007FC770 /* VideosListView.swift */,
|
||||
371231832683E62F0000B307 /* VideosView.swift */,
|
||||
37D4B15E267164AF00C925CA /* Assets.xcassets */,
|
||||
37D4B1AE26729DEB00C925CA /* Info.plist */,
|
||||
37D4B15E267164AF00C925CA /* Assets.xcassets */,
|
||||
);
|
||||
path = "Apple TV";
|
||||
sourceTree = "<group>";
|
||||
@ -757,6 +762,7 @@
|
||||
37C7A1DC267CE9D90010EAD6 /* Profile.swift in Sources */,
|
||||
373CFAC026966149003CB2C6 /* CoverSectionView.swift in Sources */,
|
||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
377FC7E3267A084A00A6BBAF /* VideoListRowView.swift in Sources */,
|
||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||
37AAF2942674086B007FC770 /* TabSelection.swift in Sources */,
|
||||
@ -843,6 +849,7 @@
|
||||
37B17DA1268A1F89006AEE9B /* VideoContextMenuView.swift in Sources */,
|
||||
373CFAD4269662AB003CB2C6 /* SearchDate.swift in Sources */,
|
||||
373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -890,6 +897,7 @@
|
||||
3741B5302676213400125C5E /* PlayerViewController.swift in Sources */,
|
||||
373CFABE26966148003CB2C6 /* CoverSectionView.swift in Sources */,
|
||||
37B767DD2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||
373CFAF12697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
37D4B18E26717B3800C925CA /* VideoListRowView.swift in Sources */,
|
||||
37AAF27E26737323007FC770 /* PopularVideosView.swift in Sources */,
|
||||
37AAF29A26740A01007FC770 /* VideosListView.swift in Sources */,
|
||||
|
@ -7,10 +7,12 @@ extension Defaults.Keys {
|
||||
static let openChannel = Key<Channel?>("openChannel")
|
||||
|
||||
static let searchSortOrder = Key<SearchSortOrder>("searchSortOrder", default: .relevance)
|
||||
static let searchDate = Key<SearchDate?>("searchDate", default: nil)
|
||||
static let searchDuration = Key<SearchDuration?>("searchDuration", default: nil)
|
||||
static let searchDate = Key<SearchDate?>("searchDate")
|
||||
static let searchDuration = Key<SearchDuration?>("searchDuration")
|
||||
static let openVideoID = Key<String>("videoID", default: "")
|
||||
static let showingVideoDetails = Key<Bool>("showingVideoDetails", default: false)
|
||||
|
||||
static let selectedPlaylistID = Key<String?>("selectedPlaylistID")
|
||||
static let showingAddToPlaylist = Key<Bool>("showingAddToPlaylist", default: false)
|
||||
static let videoIDToAddToPlaylist = Key<String?>("videoIDToAddToPlaylist")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user