mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Playlists and channels in the sidebar
This commit is contained in:
parent
1196a2a5e2
commit
1651110a5d
@ -3,7 +3,7 @@ import Defaults
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
struct Channel: Codable, Defaults.Serializable {
|
||||
struct Channel: Codable, Identifiable, Defaults.Serializable {
|
||||
var id: String
|
||||
var name: String
|
||||
var subscriptionsCount: String
|
||||
|
@ -54,6 +54,10 @@ final class InvidiousAPI: Service {
|
||||
content.json.arrayValue.map(Playlist.init)
|
||||
}
|
||||
|
||||
configureTransformer("/auth/playlists/*", requestMethods: [.get]) { (content: Entity<JSON>) -> Playlist in
|
||||
Playlist(content.json)
|
||||
}
|
||||
|
||||
configureTransformer("/auth/playlists", requestMethods: [.post, .patch]) { (content: Entity<Data>) -> Playlist in
|
||||
// hacky, to verify if possible to get it in easier way
|
||||
Playlist(JSON(parseJSON: String(data: content.content, encoding: .utf8)!))
|
||||
|
@ -2,6 +2,10 @@ import Foundation
|
||||
import SwiftUI
|
||||
|
||||
final class NavigationState: ObservableObject {
|
||||
enum TabSelection: Hashable {
|
||||
case subscriptions, popular, trending, playlists, channel(String), playlist(String), search
|
||||
}
|
||||
|
||||
@Published var tabSelection: TabSelection = .subscriptions
|
||||
|
||||
@Published var showingChannel = false
|
||||
@ -13,6 +17,12 @@ final class NavigationState: ObservableObject {
|
||||
|
||||
@Published var returnToDetails = false
|
||||
|
||||
@Published var presentingPlaylistForm = false
|
||||
@Published var editedPlaylist: Playlist!
|
||||
|
||||
@Published var presentingUnsubscribeAlert = false
|
||||
@Published var channelToUnsubscribe: Channel!
|
||||
|
||||
func openChannel(_ channel: Channel) {
|
||||
returnToDetails = false
|
||||
self.channel = channel
|
||||
@ -54,4 +64,21 @@ final class NavigationState: ObservableObject {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
func presentEditPlaylistForm(_ playlist: Playlist?) {
|
||||
editedPlaylist = playlist
|
||||
presentingPlaylistForm = editedPlaylist != nil
|
||||
}
|
||||
|
||||
func presentNewPlaylistForm() {
|
||||
editedPlaylist = nil
|
||||
presentingPlaylistForm = true
|
||||
}
|
||||
|
||||
func presentUnsubscribeAlert(_ channel: Channel?) {
|
||||
channelToUnsubscribe = channel
|
||||
presentingUnsubscribeAlert = channelToUnsubscribe != nil
|
||||
}
|
||||
}
|
||||
|
||||
typealias TabSelection = NavigationState.TabSelection
|
||||
|
35
Model/Playlists.swift
Normal file
35
Model/Playlists.swift
Normal file
@ -0,0 +1,35 @@
|
||||
import Foundation
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
final class Playlists: ObservableObject {
|
||||
@Published var playlists = [Playlist]()
|
||||
|
||||
var resource: Resource {
|
||||
InvidiousAPI.shared.playlists
|
||||
}
|
||||
|
||||
init() {
|
||||
load()
|
||||
}
|
||||
|
||||
var all: [Playlist] {
|
||||
playlists.sorted { $0.title.lowercased() < $1.title.lowercased() }
|
||||
}
|
||||
|
||||
func find(id: Playlist.ID) -> Playlist? {
|
||||
all.first { $0.id == id }
|
||||
}
|
||||
|
||||
func reload() {
|
||||
load()
|
||||
}
|
||||
|
||||
fileprivate func load() {
|
||||
resource.load().onSuccess { resource in
|
||||
if let playlists: [Playlist] = resource.typedContent() {
|
||||
self.playlists = playlists
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -13,6 +13,10 @@ final class Subscriptions: ObservableObject {
|
||||
load()
|
||||
}
|
||||
|
||||
var all: [Channel] {
|
||||
channels.sorted { $0.name.lowercased() < $1.name.lowercased() }
|
||||
}
|
||||
|
||||
func subscribe(_ channelID: String) {
|
||||
performChannelSubscriptionRequest(channelID, method: .post)
|
||||
}
|
||||
|
@ -120,6 +120,19 @@
|
||||
37B81B0526D2CEDA00675966 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81B0426D2CEDA00675966 /* PlaybackState.swift */; };
|
||||
37B81B0626D2CEDA00675966 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81B0426D2CEDA00675966 /* PlaybackState.swift */; };
|
||||
37B81B0726D2D6CF00675966 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37B81B0426D2CEDA00675966 /* PlaybackState.swift */; };
|
||||
37BA793B26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */; };
|
||||
37BA793C26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */; };
|
||||
37BA793D26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */; };
|
||||
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */; };
|
||||
37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */; };
|
||||
37BA794126DB8F97002A0235 /* ChannelVideosView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */; };
|
||||
37BA794326DBA973002A0235 /* Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794226DBA973002A0235 /* Playlists.swift */; };
|
||||
37BA794426DBA973002A0235 /* Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794226DBA973002A0235 /* Playlists.swift */; };
|
||||
37BA794526DBA973002A0235 /* Playlists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794226DBA973002A0235 /* Playlists.swift */; };
|
||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */; };
|
||||
37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */; };
|
||||
37BA794B26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */; };
|
||||
37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */; };
|
||||
37BAB54C269B39FD00E75ED1 /* TVNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */; };
|
||||
37BADCA52699FB72009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA42699FB72009BE4FB /* Alamofire */; };
|
||||
37BADCA7269A552E009BE4FB /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 37BADCA6269A552E009BE4FB /* Alamofire */; };
|
||||
@ -136,7 +149,6 @@
|
||||
37BD07C72698B27B003EBB87 /* Introspect in Frameworks */ = {isa = PBXBuildFile; productRef = 37BD07C62698B27B003EBB87 /* Introspect */; };
|
||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37D4B0C32671614700C925CA /* AppTabNavigation.swift */; };
|
||||
37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07B42698AA4D003EBB87 /* ContentView.swift */; };
|
||||
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */; };
|
||||
37BE0BCF26A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||
37BE0BD126A0E2D50092E2DB /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37BE0BCE26A0E2D50092E2DB /* VideoPlayerView.swift */; };
|
||||
@ -260,6 +272,11 @@
|
||||
37B81AFE26D2CA3700675966 /* VideoDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoDetails.swift; sourceTree = "<group>"; };
|
||||
37B81B0126D2CAE700675966 /* PlaybackBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackBar.swift; sourceTree = "<group>"; };
|
||||
37B81B0426D2CEDA00675966 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = "<group>"; };
|
||||
37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaylistVideosView.swift; sourceTree = "<group>"; };
|
||||
37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelVideosView.swift; sourceTree = "<group>"; };
|
||||
37BA794226DBA973002A0235 /* Playlists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Playlists.swift; sourceTree = "<group>"; };
|
||||
37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarSubscriptions.swift; sourceTree = "<group>"; };
|
||||
37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarPlaylists.swift; sourceTree = "<group>"; };
|
||||
37BAB54B269B39FD00E75ED1 /* TVNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVNavigationView.swift; sourceTree = "<group>"; };
|
||||
37BD07B42698AA4D003EBB87 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||
37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSidebarNavigation.swift; sourceTree = "<group>"; };
|
||||
@ -362,6 +379,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37BD07BA2698AB60003EBB87 /* AppSidebarNavigation.swift */,
|
||||
37BA794A26DC30EC002A0235 /* AppSidebarPlaylists.swift */,
|
||||
37BA794626DC2E56002A0235 /* AppSidebarSubscriptions.swift */,
|
||||
37D4B0C32671614700C925CA /* AppTabNavigation.swift */,
|
||||
37BD07B42698AA4D003EBB87 /* ContentView.swift */,
|
||||
);
|
||||
@ -417,6 +436,8 @@
|
||||
371AAE2826CEC7D900901972 /* Views */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
37BA793E26DB8F97002A0235 /* ChannelVideosView.swift */,
|
||||
37BA793A26DB8EE4002A0235 /* PlaylistVideosView.swift */,
|
||||
37AAF27D26737323007FC770 /* PopularView.swift */,
|
||||
37AAF27F26737550007FC770 /* SearchView.swift */,
|
||||
37AAF29F26741C97007FC770 /* SubscriptionsView.swift */,
|
||||
@ -574,6 +595,7 @@
|
||||
37B81B0426D2CEDA00675966 /* PlaybackState.swift */,
|
||||
37B767DA2677C3CA0098BAA8 /* PlayerState.swift */,
|
||||
376578882685471400D4EA09 /* Playlist.swift */,
|
||||
37BA794226DBA973002A0235 /* Playlists.swift */,
|
||||
37C7A1DB267CE9D90010EAD6 /* Profile.swift */,
|
||||
373CFACA26966264003CB2C6 /* SearchQuery.swift */,
|
||||
3711403E26B206A6005B3555 /* SearchState.swift */,
|
||||
@ -842,10 +864,12 @@
|
||||
37CEE4BD2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||
37BD07C82698B71C003EBB87 /* AppTabNavigation.swift in Sources */,
|
||||
37EAD86B267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
37BA793B26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */,
|
||||
37BD07B52698AA4D003EBB87 /* ContentView.swift in Sources */,
|
||||
37C7A1DA267CACF50010EAD6 /* TrendingCountry.swift in Sources */,
|
||||
37977583268922F600DD52A8 /* InvidiousAPI.swift in Sources */,
|
||||
37BE0BD626A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
|
||||
37BA793F26DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
||||
37754C9D26B7500000DBD602 /* VideosView.swift in Sources */,
|
||||
3711403F26B206A6005B3555 /* SearchState.swift in Sources */,
|
||||
37B81AF926D2C9A700675966 /* VideoPlayerSizeModifier.swift in Sources */,
|
||||
@ -854,6 +878,7 @@
|
||||
37F4AE7226828F0900BD60EA /* VideosCellsView.swift in Sources */,
|
||||
376578852685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||
3748186626A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||
37BA794726DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||
377FC7DC267A081800A6BBAF /* PopularView.swift in Sources */,
|
||||
3705B182267B4E4900704544 /* TrendingCategory.swift in Sources */,
|
||||
37EAD86F267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
@ -866,6 +891,7 @@
|
||||
3714166F267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
373CFAEF2697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
377FC7E3267A084A00A6BBAF /* VideoView.swift in Sources */,
|
||||
37BA794326DBA973002A0235 /* Playlists.swift in Sources */,
|
||||
37AAF29026740715007FC770 /* Channel.swift in Sources */,
|
||||
3748186A26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */,
|
||||
37B81AFF26D2CA3700675966 /* VideoDetails.swift in Sources */,
|
||||
@ -878,6 +904,7 @@
|
||||
37B81AFC26D2C9C900675966 /* VideoDetailsPaddingModifier.swift in Sources */,
|
||||
37C7A1D5267BFD9D0010EAD6 /* SponsorBlockSegment.swift in Sources */,
|
||||
37F49BA326CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
37BA794B26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */,
|
||||
37B767DB2677C3CA0098BAA8 /* PlayerState.swift in Sources */,
|
||||
373CFACB26966264003CB2C6 /* SearchQuery.swift in Sources */,
|
||||
373CFAC226966159003CB2C6 /* CoverSectionRowView.swift in Sources */,
|
||||
@ -903,6 +930,7 @@
|
||||
37BE0BDC26A2367F0092E2DB /* Player.swift in Sources */,
|
||||
37CEE4BE2677B670005A1EFE /* SingleAssetStream.swift in Sources */,
|
||||
373CFABF26966149003CB2C6 /* CoverSectionView.swift in Sources */,
|
||||
37BA794826DC2E56002A0235 /* AppSidebarSubscriptions.swift in Sources */,
|
||||
37EAD86C267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
37CEE4C22677B697005A1EFE /* Stream.swift in Sources */,
|
||||
371F2F1B269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
||||
@ -912,6 +940,7 @@
|
||||
37BD07C32698AD4F003EBB87 /* ContentView.swift in Sources */,
|
||||
37F49BA426CAA59B00304AC0 /* Playlist+Fixtures.swift in Sources */,
|
||||
37EAD870267B9ED100D9E01B /* Segment.swift in Sources */,
|
||||
37BA793C26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */,
|
||||
37141670267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
377FC7E2267A084A00A6BBAF /* VideoView.swift in Sources */,
|
||||
37B81B0626D2CEDA00675966 /* PlaybackState.swift in Sources */,
|
||||
@ -942,9 +971,11 @@
|
||||
3797758C2689345500DD52A8 /* Store.swift in Sources */,
|
||||
37141674267A8E10006CA35D /* Country.swift in Sources */,
|
||||
37AAF2A126741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||
37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */,
|
||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||
37D4B0E52671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||
37BD07C12698AD3B003EBB87 /* TrendingCountry.swift in Sources */,
|
||||
37BA794026DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
||||
3711404026B206A6005B3555 /* SearchState.swift in Sources */,
|
||||
37BE0BD026A0E2D50092E2DB /* VideoPlayerView.swift in Sources */,
|
||||
373CFAEC26975CBF003CB2C6 /* PlaylistFormView.swift in Sources */,
|
||||
@ -953,6 +984,7 @@
|
||||
3748186B26A764FB0084E870 /* Thumbnail+Fixtures.swift in Sources */,
|
||||
373CFADC269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
373CFAF02697A78B003CB2C6 /* AddToPlaylistView.swift in Sources */,
|
||||
37BA794426DBA973002A0235 /* Playlists.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -987,11 +1019,11 @@
|
||||
376578872685429C00D4EA09 /* CaseIterable+Next.swift in Sources */,
|
||||
37D4B1802671650A00C925CA /* PearvidiousApp.swift in Sources */,
|
||||
3748187026A769D60084E870 /* DetailBadge.swift in Sources */,
|
||||
37BD07CA2698FBE5003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||
373CFAC926966188003CB2C6 /* SearchOptionsView.swift in Sources */,
|
||||
37BD07C92698FBDB003EBB87 /* ContentView.swift in Sources */,
|
||||
37B17DA6268A285E006AEE9B /* VideoDetailsView.swift in Sources */,
|
||||
37141671267A8ACC006CA35D /* TrendingView.swift in Sources */,
|
||||
37BA794126DB8F97002A0235 /* ChannelVideosView.swift in Sources */,
|
||||
37AAF29226740715007FC770 /* Channel.swift in Sources */,
|
||||
37EAD86D267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
37B81B0726D2D6CF00675966 /* PlaybackState.swift in Sources */,
|
||||
@ -1012,10 +1044,12 @@
|
||||
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */,
|
||||
3748186826A7627F0084E870 /* Video+Fixtures.swift in Sources */,
|
||||
371F2F1C269B43D300E4A7AB /* NavigationState.swift in Sources */,
|
||||
37BA794526DBA973002A0235 /* Playlists.swift in Sources */,
|
||||
37B17DA0268A1F89006AEE9B /* VideoContextMenuView.swift in Sources */,
|
||||
37BE0BD726A1D4A90092E2DB /* PlayerViewController.swift in Sources */,
|
||||
37CEE4C32677B697005A1EFE /* Stream.swift in Sources */,
|
||||
37BE0BE526A336910092E2DB /* OptionsView.swift in Sources */,
|
||||
37BA793D26DB8EE4002A0235 /* PlaylistVideosView.swift in Sources */,
|
||||
3711404126B206A6005B3555 /* SearchState.swift in Sources */,
|
||||
379775952689365600DD52A8 /* Array+Next.swift in Sources */,
|
||||
37AAF28A2673AB89007FC770 /* ChannelView.swift in Sources */,
|
||||
|
@ -3,17 +3,17 @@ import SwiftUI
|
||||
import Introspect
|
||||
#endif
|
||||
|
||||
typealias TabSelection = AppSidebarNavigation.TabSelection
|
||||
|
||||
struct AppSidebarNavigation: View {
|
||||
enum TabSelection: String {
|
||||
case subscriptions, popular, trending, playlists, channel, search
|
||||
}
|
||||
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Playlists> private var playlists
|
||||
@EnvironmentObject<Subscriptions> private var subscriptions
|
||||
|
||||
@State private var didApplyPrimaryViewWorkAround = false
|
||||
|
||||
var selection: Binding<TabSelection?> {
|
||||
navigationState.tabSelectionOptionalBinding
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
#if os(iOS)
|
||||
content.introspectViewController { viewController in
|
||||
@ -38,53 +38,18 @@ struct AppSidebarNavigation: View {
|
||||
NavigationView {
|
||||
sidebar
|
||||
.frame(minWidth: 180)
|
||||
|
||||
Text("Select section")
|
||||
}
|
||||
}
|
||||
|
||||
var sidebar: some View {
|
||||
List {
|
||||
NavigationLink(tag: TabSelection.subscriptions, selection: navigationState.tabSelectionOptionalBinding) {
|
||||
SubscriptionsView()
|
||||
}
|
||||
label: {
|
||||
Label("Subscriptions", systemImage: "star.circle.fill")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
mainNavigationLinks
|
||||
|
||||
AppSidebarSubscriptions(selection: selection)
|
||||
AppSidebarPlaylists(selection: selection)
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.popular, selection: navigationState.tabSelectionOptionalBinding) {
|
||||
PopularView()
|
||||
}
|
||||
label: {
|
||||
Label("Popular", systemImage: "chart.bar")
|
||||
.accessibility(label: Text("Popular"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.trending, selection: navigationState.tabSelectionOptionalBinding) {
|
||||
TrendingView()
|
||||
}
|
||||
label: {
|
||||
Label("Trending", systemImage: "chart.line.uptrend.xyaxis")
|
||||
.accessibility(label: Text("Trending"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.playlists, selection: navigationState.tabSelectionOptionalBinding) {
|
||||
PlaylistsView()
|
||||
}
|
||||
label: {
|
||||
Label("Playlists", systemImage: "list.and.film")
|
||||
.accessibility(label: Text("Playlists"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.search, selection: navigationState.tabSelectionOptionalBinding) {
|
||||
SearchView()
|
||||
}
|
||||
label: {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
.accessibility(label: Text("Search"))
|
||||
}
|
||||
}
|
||||
#if os(macOS)
|
||||
.toolbar {
|
||||
Button(action: toggleSidebar) {
|
||||
@ -94,6 +59,59 @@ struct AppSidebarNavigation: View {
|
||||
#endif
|
||||
}
|
||||
|
||||
var mainNavigationLinks: some View {
|
||||
Group {
|
||||
NavigationLink(tag: TabSelection.subscriptions, selection: selection) {
|
||||
SubscriptionsView()
|
||||
}
|
||||
label: {
|
||||
Label("Subscriptions", systemImage: "star.circle.fill")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.popular, selection: selection) {
|
||||
PopularView()
|
||||
}
|
||||
label: {
|
||||
Label("Popular", systemImage: "chart.bar")
|
||||
.accessibility(label: Text("Popular"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.trending, selection: selection) {
|
||||
TrendingView()
|
||||
}
|
||||
label: {
|
||||
Label("Trending", systemImage: "chart.line.uptrend.xyaxis")
|
||||
.accessibility(label: Text("Trending"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.playlists, selection: selection) {
|
||||
PlaylistsView()
|
||||
}
|
||||
label: {
|
||||
Label("Playlists", systemImage: "list.and.film")
|
||||
.accessibility(label: Text("Playlists"))
|
||||
}
|
||||
|
||||
NavigationLink(tag: TabSelection.search, selection: selection) {
|
||||
SearchView()
|
||||
}
|
||||
label: {
|
||||
Label("Search", systemImage: "magnifyingglass")
|
||||
.accessibility(label: Text("Search"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func symbolSystemImage(_ name: String) -> String {
|
||||
let firstLetter = name.first?.lowercased()
|
||||
let regex = #"^[a-z0-9]$"#
|
||||
|
||||
let symbolName = firstLetter?.range(of: regex, options: .regularExpression) != nil ? firstLetter! : "questionmark"
|
||||
|
||||
return "\(symbolName).square"
|
||||
}
|
||||
|
||||
#if os(macOS)
|
||||
private func toggleSidebar() {
|
||||
NSApp.keyWindow?.contentViewController?.tryToPerform(#selector(NSSplitViewController.toggleSidebar(_:)), with: nil)
|
||||
|
37
Shared/Navigation/AppSidebarPlaylists.swift
Normal file
37
Shared/Navigation/AppSidebarPlaylists.swift
Normal file
@ -0,0 +1,37 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AppSidebarPlaylists: View {
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Playlists> private var playlists
|
||||
|
||||
@Binding var selection: TabSelection?
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Playlists")) {
|
||||
ForEach(playlists.all) { playlist in
|
||||
NavigationLink(tag: TabSelection.playlist(playlist.id), selection: $selection) {
|
||||
PlaylistVideosView(playlist)
|
||||
} label: {
|
||||
Label(playlist.title, systemImage: AppSidebarNavigation.symbolSystemImage(playlist.title))
|
||||
.badge(Text("\(playlist.videos.count)"))
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Edit") {
|
||||
navigationState.presentEditPlaylistForm(playlists.find(id: playlist.id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newPlaylistButton
|
||||
.padding(.top, 8)
|
||||
}
|
||||
}
|
||||
|
||||
var newPlaylistButton: some View {
|
||||
Button(action: { navigationState.presentNewPlaylistForm() }) {
|
||||
Label("New Playlist", systemImage: "plus.square")
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
40
Shared/Navigation/AppSidebarSubscriptions.swift
Normal file
40
Shared/Navigation/AppSidebarSubscriptions.swift
Normal file
@ -0,0 +1,40 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AppSidebarSubscriptions: View {
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Subscriptions> private var subscriptions
|
||||
|
||||
@Binding var selection: TabSelection?
|
||||
|
||||
var body: some View {
|
||||
Section(header: Text("Subscriptions")) {
|
||||
ForEach(subscriptions.all) { channel in
|
||||
NavigationLink(tag: TabSelection.channel(channel.id), selection: $selection) {
|
||||
ChannelVideosView(channel)
|
||||
} label: {
|
||||
Label(channel.name, systemImage: AppSidebarNavigation.symbolSystemImage(channel.name))
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Unsubscribe") {
|
||||
navigationState.presentUnsubscribeAlert(channel)
|
||||
}
|
||||
}
|
||||
.alert(unsubscribeAlertTitle, isPresented: $navigationState.presentingUnsubscribeAlert) {
|
||||
if let channel = navigationState.channelToUnsubscribe {
|
||||
Button("Unsubscribe", role: .destructive) {
|
||||
subscriptions.unsubscribe(channel.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var unsubscribeAlertTitle: String {
|
||||
if let channel = navigationState.channelToUnsubscribe {
|
||||
return "Unsubscribe from \(channel.name)"
|
||||
}
|
||||
|
||||
return "Unknown channel"
|
||||
}
|
||||
}
|
@ -5,6 +5,7 @@ struct ContentView: View {
|
||||
@StateObject private var playbackState = PlaybackState()
|
||||
@StateObject private var searchState = SearchState()
|
||||
@StateObject private var subscriptions = Subscriptions()
|
||||
@StateObject private var playlists = Playlists()
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@ -37,11 +38,15 @@ struct ContentView: View {
|
||||
#endif
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $navigationState.presentingPlaylistForm) {
|
||||
PlaylistFormView(playlist: $navigationState.editedPlaylist)
|
||||
}
|
||||
#endif
|
||||
.environmentObject(navigationState)
|
||||
.environmentObject(playbackState)
|
||||
.environmentObject(searchState)
|
||||
.environmentObject(subscriptions)
|
||||
.environmentObject(playlists)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ struct PlaylistFormView: View {
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
@EnvironmentObject<Playlists> private var playlists
|
||||
|
||||
var editing: Bool {
|
||||
playlist != nil
|
||||
}
|
||||
@ -139,6 +141,8 @@ struct PlaylistFormView: View {
|
||||
playlist = modifiedPlaylist
|
||||
}
|
||||
|
||||
playlists.reload()
|
||||
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
27
Shared/Views/ChannelVideosView.swift
Normal file
27
Shared/Views/ChannelVideosView.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct ChannelVideosView: View {
|
||||
@ObservedObject private var store = Store<[Video]>()
|
||||
|
||||
let channel: Channel
|
||||
|
||||
var resource: Resource {
|
||||
InvidiousAPI.shared.channelVideos(channel.id)
|
||||
}
|
||||
|
||||
init(_ channel: Channel) {
|
||||
self.channel = channel
|
||||
resource.addObserver(store)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VideosView(videos: store.collection)
|
||||
#if !os(tvOS)
|
||||
.navigationTitle("\(channel.name) Channel")
|
||||
#endif
|
||||
.onAppear {
|
||||
resource.loadIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
17
Shared/Views/PlaylistVideosView.swift
Normal file
17
Shared/Views/PlaylistVideosView.swift
Normal file
@ -0,0 +1,17 @@
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct PlaylistVideosView: View {
|
||||
let playlist: Playlist
|
||||
|
||||
init(_ playlist: Playlist) {
|
||||
self.playlist = playlist
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VideosView(videos: playlist.videos)
|
||||
#if !os(tvOS)
|
||||
.navigationTitle("\(playlist.title) Playlist")
|
||||
#endif
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user