Favorites improvements

This commit is contained in:
Arkadiusz Fal 2022-12-11 16:00:20 +01:00
parent 784fc8cfc6
commit f6a261662c
9 changed files with 169 additions and 52 deletions

View File

@ -1,6 +1,12 @@
import Foundation import Foundation
enum VideosApp: String, CaseIterable { enum VideosApp: String, CaseIterable {
enum AppType: String {
case local
case youTube
case peerTube
}
case local case local
case invidious case invidious
case piped case piped
@ -10,6 +16,19 @@ enum VideosApp: String, CaseIterable {
rawValue.capitalized 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 { var supportsAccounts: Bool {
self != .local self != .local
} }

View File

@ -6,9 +6,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
case subscriptions case subscriptions
case popular case popular
case trending(String, String?) case trending(String, String?)
case channel(String, String) case channel(String, String, String)
case playlist(String) case playlist(String)
case channelPlaylist(String, String) case channelPlaylist(String, String, String)
case searchQuery(String, String, String, String) case searchQuery(String, String, String, String)
var label: String { var label: String {
@ -21,9 +21,9 @@ struct FavoriteItem: Codable, Equatable, Identifiable, Defaults.Serializable {
let trendingCountry = Country(rawValue: country)! let trendingCountry = Country(rawValue: country)!
let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!) let trendingCategory = category.isNil ? nil : TrendingCategory(rawValue: category!)
return "\(trendingCountry.flag) \(trendingCountry.id) \(trendingCategory?.name ?? "Trending")" return "\(trendingCountry.flag) \(trendingCountry.id) \(trendingCategory?.name ?? "Trending")"
case let .channel(_, name): case let .channel(_, _, name):
return name return name
case let .channelPlaylist(_, name): case let .channelPlaylist(_, _, name):
return name return name
case let .searchQuery(text, date, duration, order): case let .searchQuery(text, date, duration, order):
var label = "Search: \"\(text)\"" var label = "Search: \"\(text)\""

View File

@ -237,6 +237,13 @@ final class NavigationModel: ObservableObject {
presentingAlert = true presentingAlert = true
} }
func hideViewsAboveBrowser() {
player.hide()
presentingChannel = false
presentingPlaylist = false
presentingOpenVideos = false
}
func hideKeyboard() { func hideKeyboard() {
#if os(iOS) #if os(iOS)
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)

View File

@ -4,32 +4,27 @@ import SwiftUI
import UniformTypeIdentifiers import UniformTypeIdentifiers
struct FavoriteItemView: View { struct FavoriteItemView: View {
let item: FavoriteItem var item: FavoriteItem
@Environment(\.navigationStyle) private var navigationStyle
@StateObject private var store = FavoriteResourceObserver() @StateObject private var store = FavoriteResourceObserver()
@Default(.favorites) private var favorites @Default(.favorites) private var favorites
@Binding private var dragging: FavoriteItem?
@ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var accounts = AccountsModel.shared
private var playlists = PlaylistsModel.shared private var playlists = PlaylistsModel.shared
private var favoritesModel = FavoritesModel.shared private var favoritesModel = FavoritesModel.shared
private var navigation = NavigationModel.shared
init( init(item: FavoriteItem) {
item: FavoriteItem,
dragging: Binding<FavoriteItem?>
) {
self.item = item self.item = item
_dragging = dragging
} }
var body: some View { var body: some View {
Group { Group {
if isVisible { if isVisible {
VStack(alignment: .leading, spacing: 2) { VStack(alignment: .leading, spacing: 2) {
Text(label) itemControl
.font(.title3.bold())
.foregroundColor(.secondary)
.contextMenu { .contextMenu {
Button { Button {
favoritesModel.remove(item) favoritesModel.remove(item)
@ -46,19 +41,15 @@ struct FavoriteItemView: View {
HorizontalCells(items: store.contentItems) HorizontalCells(items: store.contentItems)
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
#if os(macOS) .onAppear {
.opacity(dragging?.id == item.id ? 0.5 : 1) resource?.addObserver(store)
#endif if item.section == .subscriptions {
.onAppear { cacheFeed(resource?.loadIfNeeded())
resource?.addObserver(store) } else {
if item.section == .subscriptions { resource?.loadIfNeeded()
cacheFeed(resource?.loadIfNeeded())
} else {
resource?.loadIfNeeded()
}
} }
}
} }
} }
.onChange(of: accounts.current) { _ in .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?) { private func cacheFeed(_ request: Request?) {
request?.onSuccess { response in request?.onSuccess { response in
if let videos: [Video] = response.typedContent() { if let videos: [Video] = response.typedContent() {
@ -85,6 +173,12 @@ struct FavoriteItemView: View {
return accounts.app.supportsSubscriptions && accounts.signedIn return accounts.app.supportsSubscriptions && accounts.signedIn
case .popular: case .popular:
return accounts.app.supportsPopular 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: default:
return true return true
} }
@ -108,10 +202,10 @@ struct FavoriteItemView: View {
return accounts.api.trending(country: trendingCountry, category: trendingCategory) return accounts.api.trending(country: trendingCountry, category: trendingCategory)
case let .channel(id, _): case let .channel(_, id, _):
return accounts.api.channelVideos(id) return accounts.api.channelVideos(id)
case let .channelPlaylist(id, _): case let .channelPlaylist(_, id, _):
return accounts.api.channelPlaylist(id) return accounts.api.channelPlaylist(id)
case let .playlist(id): case let .playlist(id):
@ -140,3 +234,16 @@ struct FavoriteItemView: View {
return item.section.label.localized() 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)
}
}
}
}

View File

@ -6,9 +6,7 @@ import UniformTypeIdentifiers
struct HomeView: View { struct HomeView: View {
@ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var accounts = AccountsModel.shared
@State private var dragging: FavoriteItem?
@State private var presentingEditFavorites = false @State private var presentingEditFavorites = false
@State private var favoritesChanged = false @State private var favoritesChanged = false
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
@ -75,11 +73,11 @@ struct HomeView: View {
if !accounts.current.isNil, showFavoritesInHome { if !accounts.current.isNil, showFavoritesInHome {
#if os(tvOS) #if os(tvOS)
ForEach(Defaults[.favorites]) { item in ForEach(Defaults[.favorites]) { item in
FavoriteItemView(item: item, dragging: $dragging) FavoriteItemView(item: item)
} }
#else #else
ForEach(favorites) { item in ForEach(favorites) { item in
FavoriteItemView(item: item, dragging: $dragging) FavoriteItemView(item: item)
#if os(macOS) #if os(macOS)
.workaroundForVerticalScrollingBug() .workaroundForVerticalScrollingBug()
#endif #endif

View File

@ -15,13 +15,6 @@ struct OpenURLHandler {
var navigationStyle = NavigationStyle.sidebar var navigationStyle = NavigationStyle.sidebar
func handle(_ url: URL) { func handle(_ url: URL) {
if Self.firstHandle {
Self.firstHandle = false
Delay.by(1) { Self.shared.handle(url) }
return
}
if accounts.current.isNil { if accounts.current.isNil {
accounts.setCurrent(accounts.any) accounts.setCurrent(accounts.any)
} }
@ -52,27 +45,27 @@ struct OpenURLHandler {
case .search: case .search:
handleSearchUrlOpen(parser) handleSearchUrlOpen(parser)
case .favorites: case .favorites:
hideViewsAboveBrowser() navigation.hideViewsAboveBrowser()
navigation.tabSelection = .home navigation.tabSelection = .home
#if os(macOS) #if os(macOS)
focusMainWindow() focusMainWindow()
#endif #endif
case .subscriptions: case .subscriptions:
guard accounts.app.supportsSubscriptions, accounts.signedIn else { return } guard accounts.app.supportsSubscriptions, accounts.signedIn else { return }
hideViewsAboveBrowser() navigation.hideViewsAboveBrowser()
navigation.tabSelection = .subscriptions navigation.tabSelection = .subscriptions
#if os(macOS) #if os(macOS)
focusMainWindow() focusMainWindow()
#endif #endif
case .popular: case .popular:
guard accounts.app.supportsPopular else { return } guard accounts.app.supportsPopular else { return }
hideViewsAboveBrowser() navigation.hideViewsAboveBrowser()
navigation.tabSelection = .popular navigation.tabSelection = .popular
#if os(macOS) #if os(macOS)
focusMainWindow() focusMainWindow()
#endif #endif
case .trending: case .trending:
hideViewsAboveBrowser() navigation.hideViewsAboveBrowser()
navigation.tabSelection = .trending navigation.tabSelection = .trending
#if os(macOS) #if os(macOS)
focusMainWindow() 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) { private func handleFileURLOpen(_ parser: URLParser) {
guard let url = parser.fileURL else { return } guard let url = parser.fileURL else { return }

View File

@ -87,7 +87,7 @@ struct ChannelPlaylistView: View {
ShareButton(contentItem: contentItem) ShareButton(contentItem: contentItem)
if let playlist = presentedPlaylist { 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 playButton

View File

@ -231,7 +231,7 @@ struct ChannelVideosView: View {
contentTypePicker contentTypePicker
Section { Section {
subscriptionToggleButton subscriptionToggleButton
FavoriteButton(item: FavoriteItem(section: .channel(channel.id, channel.name))) FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name)))
} }
} }
} label: { } label: {

View File

@ -66,7 +66,7 @@ struct PlaylistVideosView: View {
.toolbar { .toolbar {
ToolbarItem(placement: playlistButtonsPlacement) { ToolbarItem(placement: playlistButtonsPlacement) {
HStack { HStack {
FavoriteButton(item: FavoriteItem(section: .channelPlaylist(playlist.id, playlist.title))) FavoriteButton(item: FavoriteItem(section: .channelPlaylist(accounts.app.appType.rawValue, playlist.id, playlist.title)))
Button { Button {
player.play(videos) player.play(videos)