Extended Piped support

This commit is contained in:
Arkadiusz Fal
2021-10-21 00:21:50 +02:00
parent 2d075e7b3a
commit c3326a56af
46 changed files with 706 additions and 458 deletions

8
Shared/AppDelegate.swift Normal file
View File

@@ -0,0 +1,8 @@
//
// AppDelegate.swift
// Pearvidious
//
// Created by Arkadiusz Fal on 20/10/2021.
//
import Foundation

View File

@@ -9,13 +9,13 @@ extension Defaults.Keys {
.init(app: .piped, id: pipedInstanceID, name: "Public", url: "https://pipedapi.kavin.rocks"),
.init(app: .invidious, id: invidiousInstanceID, name: "Private", url: "https://invidious.home.arekf.net")
])
static let accounts = Key<[Instance.Account]>("accounts", default: [
static let accounts = Key<[Account]>("accounts", default: [
.init(instanceID: invidiousInstanceID,
name: "arekf",
url: "https://invidious.home.arekf.net",
sid: "ki55SJbaQmm0bOxUWctGAQLYPQRgk-CXDPw5Dp4oBmI=")
])
static let lastAccountID = Key<Instance.Account.ID?>("lastAccountID")
static let lastAccountID = Key<Account.ID?>("lastAccountID")
static let lastInstanceID = Key<Instance.ID?>("lastInstanceID")
static let quality = Key<Stream.ResolutionSetting>("quality", default: .hd720pFirstThenBest)

View File

@@ -0,0 +1,9 @@
//
// GestureTimer.swift
// SwiftUIKit
//
// Created by Daniel Saidi on 2021-02-17.
// Copyright © 2021 Daniel Saidi. All rights reserved.
//
import Foundation

View File

@@ -0,0 +1,22 @@
import SwiftUI
extension View {
func onSwipeGesture(
up: @escaping () -> Void = {},
down: @escaping () -> Void = {}
) -> some View {
gesture(
DragGesture(minimumDistance: 10)
.onEnded { gesture in
let translation = gesture.translation
if abs(translation.height) > 100_000 {
return
}
let isUp = translation.height < 0
isUp ? up() : down()
}
)
}
}

View File

@@ -22,11 +22,11 @@ struct AccountsMenuView: View {
.transaction { t in t.animation = .none }
}
private var allAccounts: [Instance.Account] {
private var allAccounts: [Account] {
accounts + instances.map(\.anonymousAccount)
}
private func accountButtonTitle(account: Instance.Account) -> String {
private func accountButtonTitle(account: Account) -> String {
instances.count > 1 ? "\(account.description)\(account.instance.description)" : account.description
}
}

View File

@@ -32,17 +32,18 @@ struct Sidebar: View {
Label("Watch Now", systemImage: "play.circle")
.accessibility(label: Text("Watch Now"))
}
if accounts.signedIn {
if accounts.app.supportsSubscriptions && accounts.signedIn {
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
Label("Subscriptions", systemImage: "star.circle")
.accessibility(label: Text("Subscriptions"))
}
}
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
Label("Popular", systemImage: "chart.bar")
.accessibility(label: Text("Popular"))
if accounts.app.supportsPopular {
NavigationLink(destination: LazyView(PopularView()), tag: TabSelection.popular, selection: $navigation.tabSelection) {
Label("Popular", systemImage: "chart.bar")
.accessibility(label: Text("Popular"))
}
}
NavigationLink(destination: LazyView(TrendingView()), tag: TabSelection.trending, selection: $navigation.tabSelection) {

View File

@@ -13,7 +13,7 @@ struct PlayerQueueView: View {
}
#if os(macOS)
.listStyle(.groupedWithInsets)
.listStyle(.inset)
#elseif os(iOS)
.listStyle(.insetGrouped)
#else

View File

@@ -16,6 +16,7 @@ struct VideoDetails: View {
@Environment(\.dismiss) private var dismiss
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@@ -86,7 +87,8 @@ struct VideoDetails: View {
}
}
.onAppear {
guard video != nil else {
guard video != nil, accounts.app.supportsSubscriptions else {
subscribed = false
return
}
@@ -155,41 +157,42 @@ struct VideoDetails: View {
}
.foregroundColor(.secondary)
Spacer()
if accounts.app.supportsSubscriptions {
Spacer()
Section {
if subscribed {
Button("Unsubscribe") {
confirmationShown = true
}
#if os(iOS)
.tint(.gray)
#endif
.confirmationDialog("Are you you want to unsubscribe from \(video!.channel.name)?", isPresented: $confirmationShown) {
Section {
if subscribed {
Button("Unsubscribe") {
subscriptions.unsubscribe(video!.channel.id)
confirmationShown = true
}
#if os(iOS)
.tint(.gray)
#endif
.confirmationDialog("Are you you want to unsubscribe from \(video!.channel.name)?", isPresented: $confirmationShown) {
Button("Unsubscribe") {
subscriptions.unsubscribe(video!.channel.id)
withAnimation {
subscribed.toggle()
}
}
}
} else {
Button("Subscribe") {
subscriptions.subscribe(video!.channel.id)
withAnimation {
subscribed.toggle()
}
}
.tint(.blue)
}
} else {
Button("Subscribe") {
subscriptions.subscribe(video!.channel.id)
withAnimation {
subscribed.toggle()
}
}
.tint(.blue)
}
.font(.system(size: 13))
.buttonStyle(.borderless)
.buttonBorderShape(.roundedRectangle)
}
.font(.system(size: 13))
.buttonStyle(.borderless)
.buttonBorderShape(.roundedRectangle)
}
Divider()
}
}
}
@@ -264,7 +267,10 @@ struct VideoDetails: View {
Group {
if let video = player.currentItem?.video {
Group {
publishedDateSection
HStack {
publishedDateSection
Spacer()
}
Divider()
@@ -274,8 +280,13 @@ struct VideoDetails: View {
Divider()
VStack(alignment: .leading, spacing: 10) {
Text(video.description)
.font(.caption)
if let description = video.description {
Text(description)
.font(.caption)
} else {
Text("No description")
.foregroundColor(.secondary)
}
ScrollView(.horizontal, showsIndicators: showScrollIndicators) {
HStack {

View File

@@ -2,9 +2,6 @@ import AVKit
import Defaults
import Siesta
import SwiftUI
#if !os(tvOS)
import SwiftUIKit
#endif
struct VideoPlayerView: View {
static let defaultAspectRatio: Double = 1.77777778

View File

@@ -170,7 +170,7 @@ struct PlaylistFormView: View {
let body = ["title": name, "privacy": visibility.rawValue]
resource.request(editing ? .patch : .post, json: body).onSuccess { response in
resource?.request(editing ? .patch : .post, json: body).onSuccess { response in
if let modifiedPlaylist: Playlist = response.typedContent() {
playlist = modifiedPlaylist
}
@@ -181,7 +181,7 @@ struct PlaylistFormView: View {
}
}
var resource: Resource {
var resource: Resource? {
editing ? api.playlist(playlist.id) : api.playlists
}
@@ -227,7 +227,7 @@ struct PlaylistFormView: View {
}
func deletePlaylistAndDismiss() {
api.playlist(playlist.id).request(.delete).onSuccess { _ in
api.playlist(playlist.id)?.request(.delete).onSuccess { _ in
playlist = nil
playlists.load(force: true)
dismiss()

View File

@@ -3,7 +3,7 @@ import SwiftUI
struct AccountFormView: View {
let instance: Instance
var selectedAccount: Binding<Instance.Account?>?
var selectedAccount: Binding<Account?>?
@State private var name = ""
@State private var sid = ""
@@ -134,7 +134,7 @@ struct AccountFormView: View {
AccountValidator(
app: .constant(instance.app),
url: instance.url,
account: Instance.Account(instanceID: instance.id, url: instance.url, sid: sid),
account: Account(instanceID: instance.id, url: instance.url, sid: sid),
id: $sid,
isValid: $isValid,
isValidated: $isValidated,

View File

@@ -14,8 +14,8 @@ struct AccountsSettingsView: View {
}
var body: some View {
Group {
if instance.supportsAccounts {
VStack {
if instance.app.supportsAccounts {
accounts
} else {
Text("Accounts are not supported for the application of this instance")
@@ -68,7 +68,7 @@ struct AccountsSettingsView: View {
#endif
}
private func removeAccount(_ account: Instance.Account) {
private func removeAccount(_ account: Account) {
AccountsModel.remove(account)
accountsChanged.toggle()
}

View File

@@ -5,7 +5,7 @@ struct InstanceFormView: View {
@State private var name = ""
@State private var url = ""
@State private var app = Instance.App.invidious
@State private var app = VideosApp.invidious
@State private var isValid = false
@State private var isValidated = false
@@ -75,7 +75,7 @@ struct InstanceFormView: View {
private var formFields: some View {
Group {
Picker("Application", selection: $app) {
ForEach(Instance.App.allCases, id: \.self) { app in
ForEach(VideosApp.allCases, id: \.self) { app in
Text(app.rawValue.capitalized).tag(app)
}
}

View File

@@ -10,7 +10,7 @@ struct InstancesSettingsView: View {
@EnvironmentObject<PlaylistsModel> private var playlists
@State private var selectedInstanceID: Instance.ID?
@State private var selectedAccount: Instance.Account?
@State private var selectedAccount: Account?
@State private var presentingInstanceForm = false
@State private var savedFormInstanceID: Instance.ID?

View File

@@ -18,10 +18,12 @@ struct TrendingView: View {
}
var resource: Resource {
let resource = accounts.invidious.trending(category: category, country: country)
resource.addObserver(store)
let newResource: Resource
return resource
newResource = accounts.api.trending(country: country, category: category)
newResource.addObserver(store)
return newResource
}
var body: some View {
@@ -56,20 +58,26 @@ struct TrendingView: View {
.toolbar {
#if os(macOS)
ToolbarItemGroup {
categoryButton
if accounts.app.supportsTrendingCategories {
categoryButton
}
countryButton
}
#elseif os(iOS)
ToolbarItemGroup(placement: .bottomBar) {
Group {
HStack {
Text("Category")
.foregroundColor(.secondary)
if accounts.app.supportsTrendingCategories {
HStack {
Text("Category")
.foregroundColor(.secondary)
categoryButton
// only way to disable Menu animation is to
// force redraw of the view when it changes
.id(UUID())
categoryButton
// only way to disable Menu animation is to
// force redraw of the view when it changes
.id(UUID())
}
} else {
Spacer()
}
HStack {
@@ -97,11 +105,13 @@ struct TrendingView: View {
var toolbar: some View {
HStack {
HStack {
Text("Category")
.foregroundColor(.secondary)
if accounts.app.supportsTrendingCategories {
HStack {
Text("Category")
.foregroundColor(.secondary)
categoryButton
categoryButton
}
}
#if os(iOS)

View File

@@ -7,6 +7,7 @@ struct VideoBanner: View {
var body: some View {
HStack(alignment: .center, spacing: 12) {
smallThumbnail
VStack(alignment: .leading, spacing: 4) {
Text(video.title)
.truncationMode(.middle)

View File

@@ -99,7 +99,7 @@ struct ChannelVideosView: View {
}
var resource: Resource {
let resource = accounts.invidious.channel(channel.id)
let resource = accounts.api.channel(channel.id)
resource.addObserver(store)
return resource
@@ -107,14 +107,16 @@ struct ChannelVideosView: View {
var subscriptionToggleButton: some View {
Group {
if subscriptions.isSubscribing(channel.id) {
Button("Unsubscribe") {
navigation.presentUnsubscribeAlert(channel)
}
} else {
Button("Subscribe") {
subscriptions.subscribe(channel.id) {
navigation.sidebarSectionChanged.toggle()
if accounts.app.supportsSubscriptions && accounts.signedIn {
if subscriptions.isSubscribing(channel.id) {
Button("Unsubscribe") {
navigation.presentUnsubscribeAlert(channel)
}
} else {
Button("Subscribe") {
subscriptions.subscribe(channel.id) {
navigation.sidebarSectionChanged.toggle()
}
}
}
}

View File

@@ -6,16 +6,16 @@ struct PopularView: View {
@EnvironmentObject<AccountsModel> private var accounts
var resource: Resource {
accounts.invidious.popular
var resource: Resource? {
accounts.api.popular
}
var body: some View {
PlayerControlsView {
VideosCellsVertical(videos: store.collection)
.onAppear {
resource.addObserver(store)
resource.loadIfNeeded()
resource?.addObserver(store)
resource?.loadIfNeeded()
}
#if !os(tvOS)
.navigationTitle("Popular")

View File

@@ -19,6 +19,7 @@ struct SearchView: View {
@Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var state
@@ -37,7 +38,9 @@ struct SearchView: View {
} else {
#if os(tvOS)
ScrollView(.vertical, showsIndicators: false) {
filtersHorizontalStack
if accounts.app.supportsSearchFilters {
filtersHorizontalStack
}
VideosCellsHorizontal(videos: state.store.collection)
}
@@ -61,27 +64,28 @@ struct SearchView: View {
.toolbar {
#if !os(tvOS)
ToolbarItemGroup(placement: toolbarPlacement) {
Section {
#if os(macOS)
HStack {
Text("Sort:")
.foregroundColor(.secondary)
if accounts.app.supportsSearchFilters {
Section {
#if os(macOS)
HStack {
Text("Sort:")
.foregroundColor(.secondary)
searchSortOrderPicker
}
#else
Menu("Sort: \(searchSortOrder.name)") {
searchSortOrderPicker
}
#endif
searchSortOrderPicker
}
#else
Menu("Sort: \(searchSortOrder.name)") {
searchSortOrderPicker
}
#endif
}
.transaction { t in t.animation = .none }
Spacer()
filtersMenu
}
.transaction { t in t.animation = .none }
Spacer()
filtersMenu
}
#endif
}
.onAppear {

View File

@@ -6,12 +6,8 @@ struct SubscriptionsView: View {
@EnvironmentObject<AccountsModel> private var accounts
var api: InvidiousAPI {
accounts.invidious
}
var feed: Resource {
api.feed
var feed: Resource? {
accounts.api.feed
}
var body: some View {
@@ -32,9 +28,9 @@ struct SubscriptionsView: View {
}
fileprivate func loadResources(force: Bool = false) {
feed.addObserver(store)
feed?.addObserver(store)
if let request = force ? api.home.load() : api.home.loadIfNeeded() {
if let request = force ? accounts.api.home?.load() : accounts.api.home?.loadIfNeeded() {
request.onSuccess { _ in
loadFeed(force: force)
}
@@ -44,6 +40,6 @@ struct SubscriptionsView: View {
}
fileprivate func loadFeed(force: Bool = false) {
_ = force ? feed.load() : feed.loadIfNeeded()
_ = force ? feed?.load() : feed?.loadIfNeeded()
}
}

View File

@@ -8,6 +8,7 @@ struct VideoContextMenuView: View {
@Environment(\.inNavigationView) private var inNavigationView
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlaylistsModel> private var playlists
@@ -25,18 +26,22 @@ struct VideoContextMenuView: View {
Section {
openChannelButton
subscriptionButton
if accounts.app.supportsSubscriptions {
subscriptionButton
}
}
Section {
if navigation.tabSelection != .playlists {
addToPlaylistButton
} else if let playlist = playlists.currentPlaylist {
removeFromPlaylistButton(playlistID: playlist.id)
}
if accounts.app.supportsUserPlaylists {
Section {
if navigation.tabSelection != .playlists {
addToPlaylistButton
} else if let playlist = playlists.currentPlaylist {
removeFromPlaylistButton(playlistID: playlist.id)
}
if case let .playlist(id) = navigation.tabSelection {
removeFromPlaylistButton(playlistID: id)
if case let .playlist(id) = navigation.tabSelection {
removeFromPlaylistButton(playlistID: id)
}
}
}

View File

@@ -3,14 +3,14 @@ import Siesta
import SwiftUI
struct WatchNowSection: View {
let resource: Resource
let resource: Resource?
let label: String
@StateObject private var store = Store<[Video]>()
@EnvironmentObject<AccountsModel> private var accounts
init(resource: Resource, label: String) {
init(resource: Resource?, label: String) {
self.resource = resource
self.label = label
}
@@ -18,11 +18,11 @@ struct WatchNowSection: View {
var body: some View {
WatchNowSectionBody(label: label, videos: store.collection)
.onAppear {
resource.addObserver(store)
resource.loadIfNeeded()
resource?.addObserver(store)
resource?.loadIfNeeded()
}
.onChange(of: accounts.current) { _ in
resource.load()
resource?.load()
}
}
}

View File

@@ -5,22 +5,22 @@ import SwiftUI
struct WatchNowView: View {
@EnvironmentObject<AccountsModel> private var accounts
var api: InvidiousAPI! {
accounts.invidious
}
var body: some View {
PlayerControlsView {
ScrollView(.vertical, showsIndicators: false) {
if !accounts.current.isNil {
VStack(alignment: .leading, spacing: 0) {
if api.signedIn {
WatchNowSection(resource: api.feed, label: "Subscriptions")
if accounts.api.signedIn {
WatchNowSection(resource: accounts.api.feed, label: "Subscriptions")
}
if accounts.app.supportsPopular {
WatchNowSection(resource: accounts.api.popular, label: "Popular")
}
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .default), label: "Trending")
if accounts.app.supportsTrendingCategories {
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .movies), label: "Movies")
WatchNowSection(resource: accounts.api.trending(country: .pl, category: .music), label: "Music")
}
WatchNowSection(resource: api.popular, label: "Popular")
WatchNowSection(resource: api.trending(category: .default, country: .pl), label: "Trending")
WatchNowSection(resource: api.trending(category: .movies, country: .pl), label: "Movies")
WatchNowSection(resource: api.trending(category: .music, country: .pl), label: "Music")
// TODO: adding sections to view
// ===================