mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Recently opened for sidebar navigation
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
import Defaults
|
||||
|
||||
extension Defaults.Keys {
|
||||
#if os(tvOS)
|
||||
static let layout = Key<ListingLayout>("listingLayout", default: .cells)
|
||||
#endif
|
||||
|
||||
static let searchSortOrder = Key<SearchQuery.SortOrder>("searchSortOrder", default: .relevance)
|
||||
static let searchDate = Key<SearchQuery.Date?>("searchDate")
|
||||
static let searchDuration = Key<SearchQuery.Duration?>("searchDuration")
|
||||
|
||||
static let selectedPlaylistID = Key<String?>("selectedPlaylistID")
|
||||
static let showingAddToPlaylist = Key<Bool>("showingAddToPlaylist", default: false)
|
||||
static let videoIDToAddToPlaylist = Key<String?>("videoIDToAddToPlaylist")
|
||||
|
||||
static let recentlyOpened = Key<[RecentItem]>("recentlyOpened", default: [])
|
||||
}
|
||||
|
||||
enum ListingLayout: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||
case list, cells
|
||||
|
||||
@@ -16,18 +32,3 @@ enum ListingLayout: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Defaults.Keys {
|
||||
#if os(tvOS)
|
||||
static let layout = Key<ListingLayout>("listingLayout", default: .cells)
|
||||
#endif
|
||||
static let searchQuery = Key<String>("searchQuery", default: "")
|
||||
|
||||
static let searchSortOrder = Key<SearchQuery.SortOrder>("searchSortOrder", default: .relevance)
|
||||
static let searchDate = Key<SearchQuery.Date?>("searchDate")
|
||||
static let searchDuration = Key<SearchQuery.Duration?>("searchDuration")
|
||||
|
||||
static let selectedPlaylistID = Key<String?>("selectedPlaylistID")
|
||||
static let showingAddToPlaylist = Key<Bool>("showingAddToPlaylist", default: false)
|
||||
static let videoIDToAddToPlaylist = Key<String?>("videoIDToAddToPlaylist")
|
||||
}
|
||||
|
@@ -10,11 +10,7 @@ struct UnsubscribeAlertModifier: ViewModifier {
|
||||
.alert(unsubscribeAlertTitle, isPresented: $navigationState.presentingUnsubscribeAlert) {
|
||||
if let channel = navigationState.channelToUnsubscribe {
|
||||
Button("Unsubscribe", role: .destructive) {
|
||||
subscriptions.unsubscribe(channel.id) {
|
||||
navigationState.openChannel(channel)
|
||||
navigationState.tabSelection = .channel(channel.id)
|
||||
navigationState.sidebarSectionChanged.toggle()
|
||||
}
|
||||
subscriptions.unsubscribe(channel.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@ struct AppSidebarNavigation: View {
|
||||
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Playlists> private var playlists
|
||||
@EnvironmentObject<Recents> private var recents
|
||||
@EnvironmentObject<SearchState> private var searchState
|
||||
@EnvironmentObject<Subscriptions> private var subscriptions
|
||||
|
||||
@@ -66,6 +67,8 @@ struct AppSidebarNavigation: View {
|
||||
query.query = self.searchQuery
|
||||
}
|
||||
|
||||
recents.open(RecentItem(type: .query, identifier: self.searchQuery, title: self.searchQuery))
|
||||
|
||||
navigationState.tabSelection = .search
|
||||
}
|
||||
}
|
||||
@@ -111,7 +114,7 @@ struct AppSidebarNavigation: View {
|
||||
return Group {
|
||||
mainNavigationLinks
|
||||
|
||||
AppSidebarRecentlyOpened(selection: selection)
|
||||
AppSidebarRecents(selection: selection)
|
||||
.id("recentlyOpened")
|
||||
AppSidebarSubscriptions(selection: selection)
|
||||
AppSidebarPlaylists(selection: selection)
|
||||
@@ -130,7 +133,7 @@ struct AppSidebarNavigation: View {
|
||||
}
|
||||
|
||||
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: selection) {
|
||||
Label("Subscriptions", systemImage: "star.circle.fill")
|
||||
Label("Subscriptions", systemImage: "star.circle")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
}
|
||||
|
||||
|
@@ -1,54 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AppSidebarRecentlyOpened: View {
|
||||
@Binding var selection: TabSelection?
|
||||
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Subscriptions> private var subscriptions
|
||||
|
||||
@State private var subscriptionsChanged = false
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if !recentlyOpened.isEmpty {
|
||||
Section(header: Text("Recently Opened")) {
|
||||
ForEach(recentlyOpened) { channel in
|
||||
NavigationLink(tag: TabSelection.channel(channel.id), selection: $selection) {
|
||||
LazyView(ChannelVideosView(channel))
|
||||
} label: {
|
||||
HStack {
|
||||
Label(channel.name, systemImage: AppSidebarNavigation.symbolSystemImage(channel.name))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: { navigationState.closeChannel(channel) }) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// force recalculating the view on change of subscriptions
|
||||
.opacity(subscriptionsChanged ? 1 : 1)
|
||||
.id(channel.id)
|
||||
.contextMenu {
|
||||
Button("Subscribe") {
|
||||
subscriptions.subscribe(channel.id) {
|
||||
navigationState.sidebarSectionChanged.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: subscriptions.all) { _ in
|
||||
subscriptionsChanged.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var recentlyOpened: [Channel] {
|
||||
navigationState.openChannels.filter { !subscriptions.all.contains($0) }
|
||||
}
|
||||
}
|
91
Shared/Navigation/AppSidebarRecents.swift
Normal file
91
Shared/Navigation/AppSidebarRecents.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct AppSidebarRecents: View {
|
||||
@Binding var selection: TabSelection?
|
||||
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Recents> private var recents
|
||||
|
||||
@Default(.recentlyOpened) private var recentItems
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if !recentItems.isEmpty {
|
||||
Section(header: Text("Recents")) {
|
||||
ForEach(recentItems) { recent in
|
||||
Group {
|
||||
switch recent.type {
|
||||
case .channel:
|
||||
RecentNavigationLink(recent: recent, selection: $selection) {
|
||||
LazyView(ChannelVideosView(Channel(id: recent.id, name: recent.title)))
|
||||
}
|
||||
case .query:
|
||||
RecentNavigationLink(recent: recent, selection: $selection, systemImage: "magnifyingglass") {
|
||||
LazyView(SearchView(recent.query!))
|
||||
}
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Clear All Recents") {
|
||||
recents.clear()
|
||||
}
|
||||
|
||||
Button("Clear Search History") {
|
||||
recents.clearQueries()
|
||||
}
|
||||
.disabled(!recentItems.contains { $0.type == .query })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RecentNavigationLink<DestinationContent: View>: View {
|
||||
@EnvironmentObject<Recents> private var recents
|
||||
|
||||
var recent: RecentItem
|
||||
@Binding var selection: TabSelection?
|
||||
|
||||
var systemImage: String?
|
||||
let destination: DestinationContent
|
||||
|
||||
init(
|
||||
recent: RecentItem,
|
||||
selection: Binding<TabSelection?>,
|
||||
systemImage: String? = nil,
|
||||
@ViewBuilder destination: () -> DestinationContent
|
||||
) {
|
||||
self.recent = recent
|
||||
_selection = selection
|
||||
self.systemImage = systemImage
|
||||
self.destination = destination()
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
NavigationLink(tag: TabSelection.recentlyOpened(recent.tag), selection: $selection) {
|
||||
destination
|
||||
} label: {
|
||||
HStack {
|
||||
Label(recent.title, systemImage: labelSystemImage)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(action: {
|
||||
recents.close(recent)
|
||||
}) {
|
||||
Image(systemName: "xmark.circle.fill")
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.id(recent.tag)
|
||||
}
|
||||
|
||||
var labelSystemImage: String {
|
||||
systemImage != nil ? systemImage! : AppSidebarNavigation.symbolSystemImage(recent.title)
|
||||
}
|
||||
}
|
@@ -5,6 +5,8 @@ struct AppTabNavigation: View {
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<SearchState> private var searchState
|
||||
|
||||
@EnvironmentObject<Recents> private var recents
|
||||
|
||||
@State private var searchQuery = ""
|
||||
|
||||
var body: some View {
|
||||
@@ -82,18 +84,17 @@ struct AppTabNavigation: View {
|
||||
.tag(TabSelection.search)
|
||||
}
|
||||
.sheet(isPresented: $navigationState.isChannelOpen, onDismiss: {
|
||||
navigationState.closeChannel(presentedChannel)
|
||||
if let channel = recents.presentedChannel {
|
||||
let recent = RecentItem(from: channel)
|
||||
recents.close(recent)
|
||||
}
|
||||
}) {
|
||||
if presentedChannel != nil {
|
||||
if recents.presentedChannel != nil {
|
||||
NavigationView {
|
||||
ChannelVideosView(presentedChannel)
|
||||
ChannelVideosView(recents.presentedChannel!)
|
||||
.environment(\.inNavigationView, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var presentedChannel: Channel! {
|
||||
navigationState.openChannels.first
|
||||
}
|
||||
}
|
||||
|
@@ -3,9 +3,10 @@ import SwiftUI
|
||||
struct ContentView: View {
|
||||
@StateObject private var navigationState = NavigationState()
|
||||
@StateObject private var playbackState = PlaybackState()
|
||||
@StateObject private var playlists = Playlists()
|
||||
@StateObject private var recents = Recents()
|
||||
@StateObject private var searchState = SearchState()
|
||||
@StateObject private var subscriptions = Subscriptions()
|
||||
@StateObject private var playlists = Playlists()
|
||||
|
||||
#if os(iOS)
|
||||
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||
@@ -44,9 +45,10 @@ struct ContentView: View {
|
||||
#endif
|
||||
.environmentObject(navigationState)
|
||||
.environmentObject(playbackState)
|
||||
.environmentObject(playlists)
|
||||
.environmentObject(recents)
|
||||
.environmentObject(searchState)
|
||||
.environmentObject(subscriptions)
|
||||
.environmentObject(playlists)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -85,8 +85,6 @@ struct VideoPlayerView: View {
|
||||
.onDisappear {
|
||||
resource.removeObservers(ownedBy: store)
|
||||
resource.invalidate()
|
||||
|
||||
navigationState.showingVideoDetails = navigationState.returnToDetails
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 1000, minHeight: 700)
|
||||
|
@@ -3,13 +3,18 @@ import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct SearchView: View {
|
||||
@Default(.searchQuery) private var queryText
|
||||
@Default(.searchSortOrder) private var searchSortOrder
|
||||
@Default(.searchDate) private var searchDate
|
||||
@Default(.searchDuration) private var searchDuration
|
||||
|
||||
@EnvironmentObject<SearchState> private var state
|
||||
|
||||
private var query: SearchQuery?
|
||||
|
||||
init(_ query: SearchQuery? = nil) {
|
||||
self.query = query
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
VideosView(videos: state.store.collection)
|
||||
@@ -27,11 +32,8 @@ struct SearchView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
state.changeQuery { query in
|
||||
query.query = queryText
|
||||
query.sortBy = searchSortOrder
|
||||
query.date = searchDate
|
||||
query.duration = searchDuration
|
||||
if query != nil {
|
||||
state.resetQuery(query!)
|
||||
}
|
||||
}
|
||||
.onChange(of: state.query.query) { queryText in
|
||||
|
@@ -3,6 +3,7 @@ import SwiftUI
|
||||
|
||||
struct VideoContextMenuView: View {
|
||||
@EnvironmentObject<NavigationState> private var navigationState
|
||||
@EnvironmentObject<Recents> private var recents
|
||||
@EnvironmentObject<Subscriptions> private var subscriptions
|
||||
|
||||
let video: Video
|
||||
@@ -14,15 +15,11 @@ struct VideoContextMenuView: View {
|
||||
|
||||
var body: some View {
|
||||
Section {
|
||||
if navigationState.showOpenChannel(video.channel.id) {
|
||||
openChannelButton
|
||||
}
|
||||
openChannelButton
|
||||
|
||||
subscriptionButton
|
||||
.opacity(subscribed ? 1 : 1)
|
||||
|
||||
openVideoDetailsButton
|
||||
|
||||
if navigationState.tabSelection == .playlists {
|
||||
removeFromPlaylistButton
|
||||
} else {
|
||||
@@ -33,8 +30,10 @@ struct VideoContextMenuView: View {
|
||||
|
||||
var openChannelButton: some View {
|
||||
Button("\(video.author) Channel") {
|
||||
navigationState.openChannel(video.channel)
|
||||
navigationState.tabSelection = .channel(video.channel.id)
|
||||
let recent = RecentItem(from: video.channel)
|
||||
recents.open(recent)
|
||||
navigationState.tabSelection = .recentlyOpened(recent.tag)
|
||||
navigationState.isChannelOpen = true
|
||||
navigationState.sidebarSectionChanged.toggle()
|
||||
}
|
||||
}
|
||||
@@ -59,12 +58,6 @@ struct VideoContextMenuView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var openVideoDetailsButton: some View {
|
||||
Button("Open video details") {
|
||||
navigationState.openVideoDetails(video)
|
||||
}
|
||||
}
|
||||
|
||||
var addToPlaylistButton: some View {
|
||||
Button("Add to playlist...") {
|
||||
videoIDToAddToPlaylist = video.id
|
||||
|
Reference in New Issue
Block a user