mirror of
https://github.com/yattee/yattee.git
synced 2025-08-09 20:24:06 +00:00
Add Piped support
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
import Defaults
|
||||
|
||||
extension Defaults.Keys {
|
||||
static let instances = Key<[Instance]>("instances", default: [])
|
||||
static let instances = Key<[Instance]>("instances", default: [
|
||||
.init(app: .piped, name: "Public", url: "https://pipedapi.kavin.rocks"),
|
||||
.init(app: .invidious, name: "Private", url: "https://invidious.home.arekf.net")
|
||||
])
|
||||
static let accounts = Key<[Instance.Account]>("accounts", default: [])
|
||||
static let defaultAccountID = Key<String?>("defaultAccountID")
|
||||
|
||||
|
@@ -2,33 +2,27 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct AccountsMenuView: View {
|
||||
@EnvironmentObject<AccountsModel> private var model
|
||||
@EnvironmentObject<InstancesModel> private var instancesModel
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
|
||||
@Default(.instances) private var instances
|
||||
|
||||
var body: some View {
|
||||
Menu {
|
||||
ForEach(instances) { instance in
|
||||
Button(accountButtonTitle(instance: instance, account: instance.anonymousAccount)) {
|
||||
api.setAccount(instance.anonymousAccount)
|
||||
}
|
||||
|
||||
ForEach(instancesModel.accounts(instance.id)) { account in
|
||||
Button(accountButtonTitle(instance: instance, account: account)) {
|
||||
api.setAccount(account)
|
||||
}
|
||||
ForEach(model.all, id: \.id) { account in
|
||||
Button(accountButtonTitle(account: account)) {
|
||||
model.setAccount(account)
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Label(api.account?.name ?? "Accounts", systemImage: "person.crop.circle")
|
||||
Label(model.account?.name ?? "Select Account", systemImage: "person.crop.circle")
|
||||
.labelStyle(.titleAndIcon)
|
||||
}
|
||||
.disabled(instances.isEmpty)
|
||||
.transaction { t in t.animation = .none }
|
||||
}
|
||||
|
||||
func accountButtonTitle(instance: Instance, account: Instance.Account) -> String {
|
||||
instances.count > 1 ? "\(account.description) — \(instance.shortDescription)" : account.description
|
||||
func accountButtonTitle(account: Instance.Account) -> String {
|
||||
instances.count > 1 ? "\(account.description) — \(account.instance.description)" : account.description
|
||||
}
|
||||
}
|
||||
|
@@ -4,8 +4,7 @@ import SwiftUI
|
||||
#endif
|
||||
|
||||
struct AppSidebarNavigation: View {
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
#if os(iOS)
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@State private var didApplyPrimaryViewWorkAround = false
|
||||
@@ -58,8 +57,8 @@ struct AppSidebarNavigation: View {
|
||||
.help(
|
||||
"Switch Instances and Accounts\n" +
|
||||
"Current Instance: \n" +
|
||||
"\(api.account?.url ?? "Not Set")\n" +
|
||||
"Current User: \(api.account?.description ?? "Not set")"
|
||||
"\(accounts.account?.url ?? "Not Set")\n" +
|
||||
"Current User: \(accounts.account?.description ?? "Not set")"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,9 @@
|
||||
import Defaults
|
||||
import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@StateObject private var api = InvidiousAPI()
|
||||
@StateObject private var accounts = AccountsModel()
|
||||
@StateObject private var instances = InstancesModel()
|
||||
@StateObject private var navigation = NavigationModel()
|
||||
@StateObject private var player = PlayerModel()
|
||||
@@ -29,8 +30,8 @@ struct ContentView: View {
|
||||
TVNavigationView()
|
||||
#endif
|
||||
}
|
||||
.onAppear(perform: configureAPI)
|
||||
.environmentObject(api)
|
||||
.onAppear(perform: configure)
|
||||
.environmentObject(accounts)
|
||||
.environmentObject(instances)
|
||||
.environmentObject(navigation)
|
||||
.environmentObject(player)
|
||||
@@ -41,7 +42,7 @@ struct ContentView: View {
|
||||
#if os(iOS)
|
||||
.fullScreenCover(isPresented: $player.presentingPlayer) {
|
||||
VideoPlayerView()
|
||||
.environmentObject(api)
|
||||
.environmentObject(accounts)
|
||||
.environmentObject(instances)
|
||||
.environmentObject(navigation)
|
||||
.environmentObject(player)
|
||||
@@ -51,7 +52,7 @@ struct ContentView: View {
|
||||
.sheet(isPresented: $player.presentingPlayer) {
|
||||
VideoPlayerView()
|
||||
.frame(minWidth: 900, minHeight: 800)
|
||||
.environmentObject(api)
|
||||
.environmentObject(accounts)
|
||||
.environmentObject(instances)
|
||||
.environmentObject(navigation)
|
||||
.environmentObject(player)
|
||||
@@ -61,31 +62,30 @@ struct ContentView: View {
|
||||
#if !os(tvOS)
|
||||
.sheet(isPresented: $navigation.presentingAddToPlaylist) {
|
||||
AddToPlaylistView(video: navigation.videoToAddToPlaylist)
|
||||
.environmentObject(api)
|
||||
.environmentObject(playlists)
|
||||
}
|
||||
.sheet(isPresented: $navigation.presentingPlaylistForm) {
|
||||
PlaylistFormView(playlist: $navigation.editedPlaylist)
|
||||
.environmentObject(api)
|
||||
.environmentObject(playlists)
|
||||
}
|
||||
.sheet(isPresented: $navigation.presentingSettings) {
|
||||
SettingsView()
|
||||
.environmentObject(api)
|
||||
.environmentObject(instances)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
func configureAPI() {
|
||||
if let account = instances.defaultAccount, api.account.isEmpty {
|
||||
api.setAccount(account)
|
||||
func configure() {
|
||||
SiestaLog.Category.enabled = .common
|
||||
|
||||
if let account = instances.defaultAccount {
|
||||
accounts.setAccount(account)
|
||||
}
|
||||
|
||||
player.api = api
|
||||
playlists.api = api
|
||||
search.api = api
|
||||
subscriptions.api = api
|
||||
player.accounts = accounts
|
||||
playlists.accounts = accounts
|
||||
search.accounts = accounts
|
||||
subscriptions.accounts = accounts
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import SwiftUI
|
||||
|
||||
struct Sidebar: View {
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
|
||||
var body: some View {
|
||||
@@ -12,7 +12,7 @@ struct Sidebar: View {
|
||||
AppSidebarRecents()
|
||||
.id("recentlyOpened")
|
||||
|
||||
if api.signedIn {
|
||||
if accounts.signedIn {
|
||||
AppSidebarSubscriptions()
|
||||
AppSidebarPlaylists()
|
||||
}
|
||||
@@ -31,7 +31,7 @@ struct Sidebar: View {
|
||||
.accessibility(label: Text("Watch Now"))
|
||||
}
|
||||
|
||||
if api.signedIn {
|
||||
if accounts.signedIn {
|
||||
NavigationLink(destination: LazyView(SubscriptionsView()), tag: TabSelection.subscriptions, selection: $navigation.tabSelection) {
|
||||
Label("Subscriptions", systemImage: "star.circle")
|
||||
.accessibility(label: Text("Subscriptions"))
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
@@ -5,47 +6,71 @@ struct PlaybackBar: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.inNavigationView) private var inNavigationView
|
||||
|
||||
@EnvironmentObject<InstancesModel> private var instances
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
closeButton
|
||||
.frame(width: 80, alignment: .leading)
|
||||
|
||||
if player.currentItem != nil {
|
||||
Text(playbackStatus)
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption2)
|
||||
.frame(minWidth: 130, maxWidth: .infinity)
|
||||
|
||||
VStack {
|
||||
if player.stream != nil {
|
||||
Text(currentStreamString)
|
||||
} else {
|
||||
if player.currentVideo!.live {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
} else {
|
||||
Image(systemName: "bolt.horizontal.fill")
|
||||
}
|
||||
Spacer()
|
||||
|
||||
HStack(spacing: 4) {
|
||||
if player.currentVideo!.live {
|
||||
Image(systemName: "dot.radiowaves.left.and.right")
|
||||
} else if player.isLoadingAvailableStreams || player.isLoadingStream {
|
||||
Image(systemName: "bolt.horizontal.fill")
|
||||
}
|
||||
|
||||
streamControl
|
||||
.disabled(player.isLoadingAvailableStreams)
|
||||
.frame(alignment: .trailing)
|
||||
.environment(\.colorScheme, .dark)
|
||||
.onChange(of: player.streamSelection) { selection in
|
||||
guard !selection.isNil else {
|
||||
return
|
||||
}
|
||||
|
||||
player.upgradeToStream(selection!)
|
||||
}
|
||||
#if os(macOS)
|
||||
.frame(maxWidth: 180)
|
||||
#endif
|
||||
}
|
||||
.transaction { t in t.animation = .none }
|
||||
.foregroundColor(.gray)
|
||||
.font(.caption2)
|
||||
.frame(width: 80, alignment: .trailing)
|
||||
.fixedSize(horizontal: true, vertical: true)
|
||||
} else {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 0, maxWidth: .infinity)
|
||||
.padding(4)
|
||||
.background(.black)
|
||||
}
|
||||
|
||||
var currentStreamString: String {
|
||||
"\(player.stream!.resolution.height)p"
|
||||
private var closeButton: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label(
|
||||
"Close",
|
||||
systemImage: inNavigationView ? "chevron.backward.circle.fill" : "chevron.down.circle.fill"
|
||||
)
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
.accessibilityLabel(Text("Close"))
|
||||
.buttonStyle(.borderless)
|
||||
.foregroundColor(.gray)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
}
|
||||
|
||||
var playbackStatus: String {
|
||||
private var playbackStatus: String {
|
||||
if player.live {
|
||||
return "LIVE"
|
||||
}
|
||||
@@ -66,17 +91,61 @@ struct PlaybackBar: View {
|
||||
return "ends at \(timeFinishAtString)"
|
||||
}
|
||||
|
||||
var closeButton: some View {
|
||||
Button {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: inNavigationView ? "chevron.backward.circle.fill" : "chevron.down.circle.fill")
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
.accessibilityLabel(Text("Close"))
|
||||
.buttonStyle(.borderless)
|
||||
.foregroundColor(.gray)
|
||||
.keyboardShortcut(.cancelAction)
|
||||
private var streamControl: some View {
|
||||
#if os(macOS)
|
||||
Picker("", selection: $player.streamSelection) {
|
||||
ForEach(instances.all) { instance in
|
||||
let instanceStreams = availableStreamsForInstance(instance)
|
||||
if !instanceStreams.values.isEmpty {
|
||||
let kinds = Array(instanceStreams.keys).sorted { $0 < $1 }
|
||||
|
||||
Section(header: Text(instance.longDescription)) {
|
||||
ForEach(kinds, id: \.self) { key in
|
||||
ForEach(instanceStreams[key] ?? []) { stream in
|
||||
Text(stream.quality).tag(Stream?.some(stream))
|
||||
}
|
||||
|
||||
if kinds.count > 1 {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
Menu {
|
||||
ForEach(instances.all) { instance in
|
||||
let instanceStreams = availableStreamsForInstance(instance)
|
||||
if !instanceStreams.values.isEmpty {
|
||||
let kinds = Array(instanceStreams.keys).sorted { $0 < $1 }
|
||||
Picker("", selection: $player.streamSelection) {
|
||||
ForEach(kinds, id: \.self) { key in
|
||||
ForEach(instanceStreams[key] ?? []) { stream in
|
||||
Text(stream.description).tag(Stream?.some(stream))
|
||||
}
|
||||
|
||||
if kinds.count > 1 {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} label: {
|
||||
Text(player.streamSelection?.quality ?? "")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private func availableStreamsForInstance(_ instance: Instance) -> [Stream.Kind: [Stream]] {
|
||||
let streams = player.availableStreams.filter { $0.instance == instance }.sorted(by: streamsSorter)
|
||||
|
||||
return Dictionary(grouping: streams, by: \.kind!)
|
||||
}
|
||||
|
||||
private func streamsSorter(_ lhs: Stream, _ rhs: Stream) -> Bool {
|
||||
lhs.kind == rhs.kind ? (lhs.resolution.height > rhs.resolution.height) : (lhs.kind < rhs.kind)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,7 +2,6 @@ import Defaults
|
||||
import SwiftUI
|
||||
|
||||
struct Player: UIViewControllerRepresentable {
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<PlayerModel> private var player
|
||||
|
||||
var controller: PlayerViewController?
|
||||
@@ -18,11 +17,8 @@ struct Player: UIViewControllerRepresentable {
|
||||
|
||||
let controller = PlayerViewController()
|
||||
|
||||
player.controller = controller
|
||||
controller.playerModel = player
|
||||
controller.api = api
|
||||
|
||||
controller.resolution = Defaults[.quality]
|
||||
player.controller = controller
|
||||
|
||||
return controller
|
||||
}
|
||||
|
@@ -3,11 +3,9 @@ import Logging
|
||||
import SwiftUI
|
||||
|
||||
final class PlayerViewController: UIViewController {
|
||||
var api: InvidiousAPI!
|
||||
var playerLoaded = false
|
||||
var playerModel: PlayerModel!
|
||||
var playerViewController = AVPlayerViewController()
|
||||
var resolution: Stream.ResolutionSetting!
|
||||
var shouldResume = false
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
@@ -81,7 +79,7 @@ extension PlayerViewController: AVPlayerViewControllerDelegate {
|
||||
|
||||
func playerViewControllerDidEndDismissalTransition(_: AVPlayerViewController) {
|
||||
if shouldResume {
|
||||
playerModel.player.play()
|
||||
playerModel.play()
|
||||
}
|
||||
|
||||
dismiss(animated: false)
|
||||
|
@@ -4,9 +4,9 @@ import SwiftUI
|
||||
struct VideoDetailsPaddingModifier: ViewModifier {
|
||||
static var defaultAdditionalDetailsPadding: Double {
|
||||
#if os(macOS)
|
||||
20
|
||||
30
|
||||
#else
|
||||
35
|
||||
40
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@@ -57,10 +57,12 @@ struct VideoPlayerSizeModifier: ViewModifier {
|
||||
|
||||
var maxHeight: Double {
|
||||
#if os(iOS)
|
||||
verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
|
||||
let height = verticalSizeClass == .regular ? geometry.size.height - minimumHeightLeft : .infinity
|
||||
#else
|
||||
geometry.size.height - minimumHeightLeft
|
||||
let height = geometry.size.height - minimumHeightLeft
|
||||
#endif
|
||||
|
||||
return [height, 0].max()!
|
||||
}
|
||||
|
||||
var edgesIgnoringSafeArea: Edge.Set {
|
||||
|
@@ -115,7 +115,7 @@ struct AccountFormView: View {
|
||||
isValidating = true
|
||||
|
||||
validationDebounce.debouncing(1) {
|
||||
validator.validateAccount()
|
||||
validator.validateInvidiousAccount()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,6 +132,7 @@ struct AccountFormView: View {
|
||||
|
||||
private var validator: AccountValidator {
|
||||
AccountValidator(
|
||||
app: .constant(instance.app),
|
||||
url: instance.url,
|
||||
account: Instance.Account(instanceID: instance.id, url: instance.url, sid: sid),
|
||||
id: $sid,
|
||||
|
@@ -13,6 +13,18 @@ struct AccountsSettingsView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if instance.supportsAccounts {
|
||||
accounts
|
||||
} else {
|
||||
Text("Accounts are not supported for the application of this instance")
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
.navigationTitle(instance.shortDescription)
|
||||
}
|
||||
|
||||
var accounts: some View {
|
||||
List {
|
||||
Section(header: Text("Accounts"), footer: sectionFooter) {
|
||||
ForEach(instances.accounts(instanceID), id: \.self) { account in
|
||||
@@ -59,7 +71,6 @@ struct AccountsSettingsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(instance.shortDescription)
|
||||
.sheet(isPresented: $presentingAccountForm, onDismiss: { accountsChanged.toggle() }) {
|
||||
AccountFormView(instance: instance)
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ struct InstanceFormView: View {
|
||||
|
||||
@State private var name = ""
|
||||
@State private var url = ""
|
||||
@State private var app = Instance.App.invidious
|
||||
|
||||
@State private var isValid = false
|
||||
@State private var isValidated = false
|
||||
@@ -37,7 +38,7 @@ struct InstanceFormView: View {
|
||||
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
|
||||
.background(.thickMaterial)
|
||||
#else
|
||||
.frame(width: 400, height: 150)
|
||||
.frame(width: 400, height: 190)
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -73,6 +74,13 @@ struct InstanceFormView: View {
|
||||
|
||||
private var formFields: some View {
|
||||
Group {
|
||||
Picker("Application", selection: $app) {
|
||||
ForEach(Instance.App.allCases, id: \.self) { app in
|
||||
Text(app.rawValue.capitalized).tag(app)
|
||||
}
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
|
||||
TextField("Name", text: $name, prompt: Text("Instance Name (optional)"))
|
||||
.focused($nameFieldFocused)
|
||||
|
||||
@@ -104,6 +112,7 @@ struct InstanceFormView: View {
|
||||
|
||||
var validator: AccountValidator {
|
||||
AccountValidator(
|
||||
app: $app,
|
||||
url: url,
|
||||
id: $url,
|
||||
isValid: $isValid,
|
||||
@@ -137,7 +146,7 @@ struct InstanceFormView: View {
|
||||
return
|
||||
}
|
||||
|
||||
savedInstanceID = instancesModel.add(name: name, url: url).id
|
||||
savedInstanceID = instancesModel.add(app: app, name: name, url: url).id
|
||||
|
||||
dismiss()
|
||||
}
|
||||
|
@@ -19,8 +19,10 @@ struct InstancesSettingsView: View {
|
||||
Group {
|
||||
Section(header: Text("Instances"), footer: DefaultAccountHint()) {
|
||||
ForEach(instances) { instance in
|
||||
NavigationLink(instance.description) {
|
||||
AccountsSettingsView(instanceID: instance.id)
|
||||
Group {
|
||||
NavigationLink(instance.longDescription) {
|
||||
AccountsSettingsView(instanceID: instance.id)
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
|
5
Shared/SiestaConfiguration.swift
Normal file
5
Shared/SiestaConfiguration.swift
Normal file
@@ -0,0 +1,5 @@
|
||||
import Siesta
|
||||
import SwiftyJSON
|
||||
|
||||
let SwiftyJSONTransformer =
|
||||
ResponseContentTransformer(transformErrors: true) { JSON($0.content as AnyObject) }
|
@@ -11,14 +11,14 @@ struct TrendingView: View {
|
||||
|
||||
@State private var presentingCountrySelection = false
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
init(_ videos: [Video] = [Video]()) {
|
||||
self.videos = videos
|
||||
}
|
||||
|
||||
var resource: Resource {
|
||||
let resource = api.trending(category: category, country: country)
|
||||
let resource = accounts.invidious.trending(category: category, country: country)
|
||||
resource.addObserver(store)
|
||||
|
||||
return resource
|
||||
|
@@ -6,7 +6,7 @@ struct ChannelVideosView: View {
|
||||
|
||||
@StateObject private var store = Store<Channel>()
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
@EnvironmentObject<SubscriptionsModel> private var subscriptions
|
||||
|
||||
@@ -99,7 +99,7 @@ struct ChannelVideosView: View {
|
||||
}
|
||||
|
||||
var resource: Resource {
|
||||
let resource = api.channel(channel.id)
|
||||
let resource = accounts.invidious.channel(channel.id)
|
||||
resource.addObserver(store)
|
||||
|
||||
return resource
|
||||
|
@@ -52,6 +52,10 @@ struct PlayerControlsView<Content: View>: View {
|
||||
.padding(.vertical, 20)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
#if !os(tvOS)
|
||||
.keyboardShortcut("o")
|
||||
#endif
|
||||
|
||||
Group {
|
||||
if model.isPlaying {
|
||||
Button(action: {
|
||||
@@ -65,7 +69,7 @@ struct PlayerControlsView<Content: View>: View {
|
||||
}) {
|
||||
Label("Play", systemImage: "play.fill")
|
||||
}
|
||||
.disabled(model.player.currentItem == nil)
|
||||
.disabled(model.player.currentItem.isNil)
|
||||
}
|
||||
}
|
||||
.frame(minWidth: 30)
|
||||
|
@@ -4,10 +4,10 @@ import SwiftUI
|
||||
struct PopularView: View {
|
||||
@StateObject private var store = Store<[Video]>()
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
var resource: Resource {
|
||||
api.popular
|
||||
accounts.invidious.popular
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
|
@@ -5,12 +5,14 @@ struct SignInRequiredView<Content: View>: View {
|
||||
let title: String
|
||||
let content: Content
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@Default(.instances) private var instances
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
#if !os(macOS)
|
||||
@EnvironmentObject<NavigationModel> private var navigation
|
||||
#endif
|
||||
|
||||
@Default(.instances) private var instances
|
||||
|
||||
init(title: String, @ViewBuilder content: @escaping () -> Content) {
|
||||
self.title = title
|
||||
self.content = content()
|
||||
@@ -18,7 +20,7 @@ struct SignInRequiredView<Content: View>: View {
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if api.signedIn {
|
||||
if accounts.signedIn {
|
||||
content
|
||||
} else {
|
||||
prompt
|
||||
|
@@ -4,7 +4,11 @@ import SwiftUI
|
||||
struct SubscriptionsView: View {
|
||||
@StateObject private var store = Store<[Video]>()
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
var api: InvidiousAPI {
|
||||
accounts.invidious
|
||||
}
|
||||
|
||||
var feed: Resource {
|
||||
api.feed
|
||||
@@ -17,10 +21,7 @@ struct SubscriptionsView: View {
|
||||
.onAppear {
|
||||
loadResources()
|
||||
}
|
||||
.onChange(of: api.account) { _ in
|
||||
loadResources(force: true)
|
||||
}
|
||||
.onChange(of: feed) { _ in
|
||||
.onChange(of: accounts.account) { _ in
|
||||
loadResources(force: true)
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ struct WatchNowSection: View {
|
||||
|
||||
@StateObject private var store = Store<[Video]>()
|
||||
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
init(resource: Resource, label: String) {
|
||||
self.resource = resource
|
||||
@@ -21,7 +21,7 @@ struct WatchNowSection: View {
|
||||
resource.addObserver(store)
|
||||
resource.loadIfNeeded()
|
||||
}
|
||||
.onChange(of: api.account) { _ in
|
||||
.onChange(of: accounts.account) { _ in
|
||||
resource.load()
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,16 @@ import Siesta
|
||||
import SwiftUI
|
||||
|
||||
struct WatchNowView: View {
|
||||
@EnvironmentObject<InvidiousAPI> private var api
|
||||
@EnvironmentObject<AccountsModel> private var accounts
|
||||
|
||||
var api: InvidiousAPI! {
|
||||
accounts.invidious
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
PlayerControlsView {
|
||||
ScrollView(.vertical, showsIndicators: false) {
|
||||
if api.validInstance {
|
||||
if !accounts.account.isNil {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
if api.signedIn {
|
||||
WatchNowSection(resource: api.feed, label: "Subscriptions")
|
||||
|
Reference in New Issue
Block a user