mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Favorites improvements
This commit is contained in:
parent
784fc8cfc6
commit
f6a261662c
@ -1,6 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
enum VideosApp: String, CaseIterable {
|
||||
enum AppType: String {
|
||||
case local
|
||||
case youTube
|
||||
case peerTube
|
||||
}
|
||||
|
||||
case local
|
||||
case invidious
|
||||
case piped
|
||||
@ -10,6 +16,19 @@ enum VideosApp: String, CaseIterable {
|
||||
rawValue.capitalized
|
||||
}
|
||||
|
||||
var appType: AppType {
|
||||
switch self {
|
||||
case .local:
|
||||
return .local
|
||||
case .invidious:
|
||||
return .youTube
|
||||
case .piped:
|
||||
return .youTube
|
||||
case .peerTube:
|
||||
return .peerTube
|
||||
}
|
||||
}
|
||||
|
||||
var supportsAccounts: Bool {
|
||||
self != .local
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
||||
case subscriptions
|
||||
case popular
|
||||
case trending(String, String?)
|
||||
case channel(String, String)
|
||||
case channel(String, String, String)
|
||||
case playlist(String)
|
||||
case channelPlaylist(String, String)
|
||||
case channelPlaylist(String, String, String)
|
||||
case searchQuery(String, String, String, String)
|
||||
|
||||
var label: String {
|
||||
@ -21,9 +21,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
|
||||
let trendingCountry = Country(rawValue: country)!
|
||||
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)
|
||||
return "\(trendingCountry.flag) \(trendingCountry.id) \(trendingCategory?.name ?? "Trending")"
|
||||
case let .channel(_, name):
|
||||
case let .channel(_, _, name):
|
||||
return name
|
||||
case let .channelPlaylist(_, name):
|
||||
case let .channelPlaylist(_, _, name):
|
||||
return name
|
||||
case let .searchQuery(text, date, duration, order):
|
||||
var label = "Search: \"\(text)\""
|
||||
|
@ -237,6 +237,13 @@ final class NavigationModel: ObservableObject {
|
||||
presentingAlert = true
|
||||
}
|
||||
|
||||
func hideViewsAboveBrowser() {
|
||||
player.hide()
|
||||
presentingChannel = false
|
||||
presentingPlaylist = false
|
||||
presentingOpenVideos = false
|
||||
}
|
||||
|
||||
func hideKeyboard() {
|
||||
#if os(iOS)
|
||||
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
|
||||
|
@ -4,32 +4,27 @@ import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct FavoriteItemView: View {
|
||||
let item: FavoriteItem
|
||||
var item: FavoriteItem
|
||||
|
||||
@Environment(\.navigationStyle) private var navigationStyle
|
||||
@StateObject private var store = FavoriteResourceObserver()
|
||||
|
||||
@Default(.favorites) private var favorites
|
||||
@Binding private var dragging: FavoriteItem?
|
||||
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
private var playlists = PlaylistsModel.shared
|
||||
private var favoritesModel = FavoritesModel.shared
|
||||
private var navigation = NavigationModel.shared
|
||||
|
||||
init(
|
||||
item: FavoriteItem,
|
||||
dragging: Binding<FavoriteItem?>
|
||||
) {
|
||||
init(item: FavoriteItem) {
|
||||
self.item = item
|
||||
_dragging = dragging
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if isVisible {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(label)
|
||||
.font(.title3.bold())
|
||||
.foregroundColor(.secondary)
|
||||
itemControl
|
||||
.contextMenu {
|
||||
Button {
|
||||
favoritesModel.remove(item)
|
||||
@ -46,19 +41,15 @@ struct FavoriteItemView: View {
|
||||
|
||||
HorizontalCells(items: store.contentItems)
|
||||
}
|
||||
|
||||
.contentShape(Rectangle())
|
||||
#if os(macOS)
|
||||
.opacity(dragging?.id == item.id ? 0.5 : 1)
|
||||
#endif
|
||||
.onAppear {
|
||||
resource?.addObserver(store)
|
||||
if item.section == .subscriptions {
|
||||
cacheFeed(resource?.loadIfNeeded())
|
||||
} else {
|
||||
resource?.loadIfNeeded()
|
||||
}
|
||||
.onAppear {
|
||||
resource?.addObserver(store)
|
||||
if item.section == .subscriptions {
|
||||
cacheFeed(resource?.loadIfNeeded())
|
||||
} else {
|
||||
resource?.loadIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: accounts.current) { _ in
|
||||
@ -71,6 +62,103 @@ struct FavoriteItemView: View {
|
||||
}
|
||||
}
|
||||
|
||||
var itemControl: some View {
|
||||
VStack {
|
||||
#if os(tvOS)
|
||||
itemButton
|
||||
#else
|
||||
if itemIsNavigationLink {
|
||||
itemNavigationLink
|
||||
} else {
|
||||
itemButton
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
var itemButton: some View {
|
||||
Button(action: itemButtonAction) {
|
||||
itemLabel
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
var itemNavigationLink: some View {
|
||||
NavigationLink(destination: itemNavigationLinkDestination) {
|
||||
itemLabel
|
||||
}
|
||||
}
|
||||
|
||||
var itemIsNavigationLink: Bool {
|
||||
switch item.section {
|
||||
case .channel:
|
||||
return navigationStyle == .tab
|
||||
case .channelPlaylist:
|
||||
return navigationStyle == .tab
|
||||
case .subscriptions:
|
||||
return navigationStyle == .tab
|
||||
case .popular:
|
||||
return navigationStyle == .tab
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder var itemNavigationLinkDestination: some View {
|
||||
Group {
|
||||
switch item.section {
|
||||
case let .channel(_, id, name):
|
||||
ChannelVideosView(channel: .init(id: id, name: name))
|
||||
case let .channelPlaylist(_, id, title):
|
||||
ChannelPlaylistView(playlist: .init(id: id, title: title))
|
||||
case .subscriptions:
|
||||
SubscriptionsView()
|
||||
case .popular:
|
||||
PopularView()
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.modifier(PlayerOverlayModifier())
|
||||
}
|
||||
|
||||
func itemButtonAction() {
|
||||
switch item.section {
|
||||
case let .channel(_, id, name):
|
||||
NavigationModel.shared.openChannel(.init(id: id, name: name), navigationStyle: navigationStyle)
|
||||
case let .channelPlaylist(_, id, title):
|
||||
NavigationModel.shared.openChannelPlaylist(.init(id: id, title: title), navigationStyle: navigationStyle)
|
||||
case .subscriptions:
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .subscriptions
|
||||
case .popular:
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .popular
|
||||
case let .trending(country, category):
|
||||
navigation.hideViewsAboveBrowser()
|
||||
Defaults[.trendingCountry] = .init(rawValue: country) ?? .us
|
||||
Defaults[.trendingCategory] = category.isNil ? .default : (.init(rawValue: category!) ?? .default)
|
||||
navigation.tabSelection = .trending
|
||||
case let .searchQuery(text, _, _, _):
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.openSearchQuery(text)
|
||||
case let .playlist(id):
|
||||
navigation.tabSelection = .playlist(id)
|
||||
}
|
||||
}
|
||||
|
||||
var itemLabel: some View {
|
||||
HStack {
|
||||
Text(label)
|
||||
.font(.title3.bold())
|
||||
Image(systemName: "chevron.right")
|
||||
.imageScale(.small)
|
||||
}
|
||||
.lineLimit(1)
|
||||
.padding(.trailing, 10)
|
||||
}
|
||||
|
||||
private func cacheFeed(_ request: Request?) {
|
||||
request?.onSuccess { response in
|
||||
if let videos: [Video] = response.typedContent() {
|
||||
@ -85,6 +173,12 @@ struct FavoriteItemView: View {
|
||||
return accounts.app.supportsSubscriptions && accounts.signedIn
|
||||
case .popular:
|
||||
return accounts.app.supportsPopular
|
||||
case let .channel(appType, _, _):
|
||||
guard let appType = VideosApp.AppType(rawValue: appType) else { return false }
|
||||
return accounts.app.appType == appType
|
||||
case let .channelPlaylist(appType, _, _):
|
||||
guard let appType = VideosApp.AppType(rawValue: appType) else { return false }
|
||||
return accounts.app.appType == appType
|
||||
default:
|
||||
return true
|
||||
}
|
||||
@ -108,10 +202,10 @@ struct FavoriteItemView: View {
|
||||
|
||||
return accounts.api.trending(country: trendingCountry, category: trendingCategory)
|
||||
|
||||
case let .channel(id, _):
|
||||
case let .channel(_, id, _):
|
||||
return accounts.api.channelVideos(id)
|
||||
|
||||
case let .channelPlaylist(id, _):
|
||||
case let .channelPlaylist(_, id, _):
|
||||
return accounts.api.channelPlaylist(id)
|
||||
|
||||
case let .playlist(id):
|
||||
@ -140,3 +234,16 @@ struct FavoriteItemView: View {
|
||||
return item.section.label.localized()
|
||||
}
|
||||
}
|
||||
|
||||
struct FavoriteItemView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
NavigationView {
|
||||
VStack {
|
||||
FavoriteItemView(item: .init(section: .channel("peerTube", "a", "Search: resistance body upper band workout")))
|
||||
.environment(\.navigationStyle, .tab)
|
||||
FavoriteItemView(item: .init(section: .channel("peerTube", "a", "Marques")))
|
||||
.environment(\.navigationStyle, .sidebar)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,7 @@ import UniformTypeIdentifiers
|
||||
struct HomeView: View {
|
||||
@ObservedObject private var accounts = AccountsModel.shared
|
||||
|
||||
@State private var dragging: FavoriteItem?
|
||||
@State private var presentingEditFavorites = false
|
||||
|
||||
@State private var favoritesChanged = false
|
||||
|
||||
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
|
||||
@ -75,11 +73,11 @@ struct HomeView: View {
|
||||
if !accounts.current.isNil, showFavoritesInHome {
|
||||
#if os(tvOS)
|
||||
ForEach(Defaults[.favorites]) { item in
|
||||
FavoriteItemView(item: item, dragging: $dragging)
|
||||
FavoriteItemView(item: item)
|
||||
}
|
||||
#else
|
||||
ForEach(favorites) { item in
|
||||
FavoriteItemView(item: item, dragging: $dragging)
|
||||
FavoriteItemView(item: item)
|
||||
#if os(macOS)
|
||||
.workaroundForVerticalScrollingBug()
|
||||
#endif
|
||||
|
@ -15,13 +15,6 @@ struct OpenURLHandler {
|
||||
var navigationStyle = NavigationStyle.sidebar
|
||||
|
||||
func handle(_ url: URL) {
|
||||
if Self.firstHandle {
|
||||
Self.firstHandle = false
|
||||
|
||||
Delay.by(1) { Self.shared.handle(url) }
|
||||
return
|
||||
}
|
||||
|
||||
if accounts.current.isNil {
|
||||
accounts.setCurrent(accounts.any)
|
||||
}
|
||||
@ -52,27 +45,27 @@ struct OpenURLHandler {
|
||||
case .search:
|
||||
handleSearchUrlOpen(parser)
|
||||
case .favorites:
|
||||
hideViewsAboveBrowser()
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .home
|
||||
#if os(macOS)
|
||||
focusMainWindow()
|
||||
#endif
|
||||
case .subscriptions:
|
||||
guard accounts.app.supportsSubscriptions, accounts.signedIn else { return }
|
||||
hideViewsAboveBrowser()
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .subscriptions
|
||||
#if os(macOS)
|
||||
focusMainWindow()
|
||||
#endif
|
||||
case .popular:
|
||||
guard accounts.app.supportsPopular else { return }
|
||||
hideViewsAboveBrowser()
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .popular
|
||||
#if os(macOS)
|
||||
focusMainWindow()
|
||||
#endif
|
||||
case .trending:
|
||||
hideViewsAboveBrowser()
|
||||
navigation.hideViewsAboveBrowser()
|
||||
navigation.tabSelection = .trending
|
||||
#if os(macOS)
|
||||
focusMainWindow()
|
||||
@ -86,13 +79,6 @@ struct OpenURLHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private func hideViewsAboveBrowser() {
|
||||
player.hide()
|
||||
navigation.presentingChannel = false
|
||||
navigation.presentingPlaylist = false
|
||||
navigation.presentingOpenVideos = false
|
||||
}
|
||||
|
||||
private func handleFileURLOpen(_ parser: URLParser) {
|
||||
guard let url = parser.fileURL else { return }
|
||||
|
||||
|
@ -87,7 +87,7 @@ struct ChannelPlaylistView: View {
|
||||
ShareButton(contentItem: contentItem)
|
||||
|
||||
if let playlist = presentedPlaylist {
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
|
||||
}
|
||||
|
||||
playButton
|
||||
|
@ -231,7 +231,7 @@ struct ChannelVideosView: View {
|
||||
contentTypePicker
|
||||
Section {
|
||||
subscriptionToggleButton
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name)))
|
||||
FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name)))
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
|
@ -66,7 +66,7 @@ struct PlaylistVideosView: View {
|
||||
.toolbar {
|
||||
ToolbarItem(placement: playlistButtonsPlacement) {
|
||||
HStack {
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title)))
|
||||
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
|
||||
|
||||
Button {
|
||||
player.play(videos)
|
||||
|
Loading…
Reference in New Issue
Block a user