Replace environment objects with observed objects

This commit is contained in:
Arkadiusz Fal 2022-11-24 21:36:05 +01:00
parent 23fa0968c6
commit 0d333b5583
102 changed files with 427 additions and 723 deletions

View File

@ -5,8 +5,6 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
func body(content: Content) -> some View { func body(content: Content) -> some View {
content content
.environmentObject(AccountsModel()) .environmentObject(AccountsModel())
.environmentObject(comments)
.environmentObject(InstancesModel())
.environmentObject(InstancesManifest()) .environmentObject(InstancesManifest())
.environmentObject(invidious) .environmentObject(invidious)
.environmentObject(NavigationModel()) .environmentObject(NavigationModel())
@ -17,20 +15,11 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier {
.environmentObject(PlayerTimeModel()) .environmentObject(PlayerTimeModel())
.environmentObject(PlaylistsModel()) .environmentObject(PlaylistsModel())
.environmentObject(RecentsModel()) .environmentObject(RecentsModel())
.environmentObject(SearchModel())
.environmentObject(SettingsModel()) .environmentObject(SettingsModel())
.environmentObject(subscriptions) .environmentObject(subscriptions)
.environmentObject(ThumbnailsModel()) .environmentObject(ThumbnailsModel())
} }
private var comments: CommentsModel {
let comments = CommentsModel()
comments.loaded = true
comments.all = [.fixture]
return comments
}
private var invidious: InvidiousAPI { private var invidious: InvidiousAPI {
let api = InvidiousAPI() let api = InvidiousAPI()

View File

@ -3,6 +3,8 @@ import Defaults
import Foundation import Foundation
final class AccountsModel: ObservableObject { final class AccountsModel: ObservableObject {
static let shared = AccountsModel()
@Published private(set) var current: Account! @Published private(set) var current: Account!
@Published private var invidious = InvidiousAPI() @Published private var invidious = InvidiousAPI()
@ -61,8 +63,8 @@ final class AccountsModel: ObservableObject {
func configureAccount() { func configureAccount() {
if let account = lastUsed ?? if let account = lastUsed ??
InstancesModel.lastUsed?.anonymousAccount ?? InstancesModel.shared.lastUsed?.anonymousAccount ??
InstancesModel.all.first?.anonymousAccount InstancesModel.shared.all.first?.anonymousAccount
{ {
setCurrent(account) setCurrent(account)
} }

View File

@ -2,27 +2,29 @@ import Defaults
import Foundation import Foundation
final class InstancesModel: ObservableObject { final class InstancesModel: ObservableObject {
static var all: [Instance] { static var shared = InstancesModel()
var all: [Instance] {
Defaults[.instances] Defaults[.instances]
} }
static var forPlayer: Instance? { var forPlayer: Instance? {
guard let id = Defaults[.playerInstanceID] else { guard let id = Defaults[.playerInstanceID] else {
return nil return nil
} }
return InstancesModel.find(id) return InstancesModel.shared.find(id)
} }
static var lastUsed: Instance? { var lastUsed: Instance? {
guard let id = Defaults[.lastInstanceID] else { guard let id = Defaults[.lastInstanceID] else {
return nil return nil
} }
return InstancesModel.find(id) return InstancesModel.shared.find(id)
} }
static func find(_ id: Instance.ID?) -> Instance? { func find(_ id: Instance.ID?) -> Instance? {
guard id != nil else { guard id != nil else {
return nil return nil
} }
@ -30,11 +32,11 @@ final class InstancesModel: ObservableObject {
return Defaults[.instances].first { $0.id == id } return Defaults[.instances].first { $0.id == id }
} }
static func accounts(_ id: Instance.ID?) -> [Account] { func accounts(_ id: Instance.ID?) -> [Account] {
Defaults[.accounts].filter { $0.instanceID == id } Defaults[.accounts].filter { $0.instanceID == id }
} }
static func add(app: VideosApp, name: String, url: String) -> Instance { func add(app: VideosApp, name: String, url: String) -> Instance {
let instance = Instance( let instance = Instance(
app: app, id: UUID().uuidString, name: name, apiURL: standardizedURL(url) app: app, id: UUID().uuidString, name: name, apiURL: standardizedURL(url)
) )
@ -43,7 +45,7 @@ final class InstancesModel: ObservableObject {
return instance return instance
} }
static func setFrontendURL(_ instance: Instance, _ url: String) { func setFrontendURL(_ instance: Instance, _ url: String) {
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
var instance = Defaults[.instances][index] var instance = Defaults[.instances][index]
instance.frontendURL = standardizedURL(url) instance.frontendURL = standardizedURL(url)
@ -52,7 +54,7 @@ final class InstancesModel: ObservableObject {
} }
} }
static func setProxiesVideos(_ instance: Instance, _ proxiesVideos: Bool) { func setProxiesVideos(_ instance: Instance, _ proxiesVideos: Bool) {
guard let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) else { guard let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) else {
return return
} }
@ -63,15 +65,15 @@ final class InstancesModel: ObservableObject {
Defaults[.instances][index] = instance Defaults[.instances][index] = instance
} }
static func remove(_ instance: Instance) { func remove(_ instance: Instance) {
let accounts = Self.accounts(instance.id) let accounts = accounts(instance.id)
if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) {
Defaults[.instances].remove(at: index) Defaults[.instances].remove(at: index)
accounts.forEach { AccountsModel.remove($0) } accounts.forEach { AccountsModel.remove($0) }
} }
} }
static func standardizedURL(_ url: String) -> String { func standardizedURL(_ url: String) -> String {
if url.count > 7, url.last == "/" { if url.count > 7, url.last == "/" {
return String(url.dropLast()) return String(url.dropLast())
} else { } else {

View File

@ -3,6 +3,8 @@ import Foundation
import SwiftyJSON import SwiftyJSON
final class CommentsModel: ObservableObject { final class CommentsModel: ObservableObject {
static let shared = CommentsModel()
@Published var all = [Comment]() @Published var all = [Comment]()
@Published var nextPage: String? @Published var nextPage: String?
@ -15,10 +17,11 @@ final class CommentsModel: ObservableObject {
@Published var repliesPageID: String? @Published var repliesPageID: String?
@Published var repliesLoaded = false @Published var repliesLoaded = false
var player: PlayerModel! var player = PlayerModel.shared
var accounts = AccountsModel.shared
var instance: Instance? { var instance: Instance? {
player.accounts.current?.instance accounts.current?.instance
} }
var nextPageAvailable: Bool { var nextPageAvailable: Bool {
@ -80,7 +83,7 @@ final class CommentsModel: ObservableObject {
repliesPageID = page repliesPageID = page
repliesLoaded = false repliesLoaded = false
player.accounts.api.comments(player.currentVideo!.videoID, page: page)? accounts.api.comments(player.currentVideo!.videoID, page: page)?
.load() .load()
.onSuccess { [weak self] response in .onSuccess { [weak self] response in
if let page: CommentsPage = response.typedContent() { if let page: CommentsPage = response.typedContent() {

View File

@ -34,11 +34,11 @@ final class InstancesManifest: Service, ObservableObject {
} }
} }
func setPublicAccount(_ country: String?, accounts: AccountsModel, asCurrent: Bool = true) { func setPublicAccount(_ country: String?, asCurrent: Bool = true) {
guard let country else { guard let country else {
accounts.publicAccount = nil AccountsModel.shared.publicAccount = nil
if asCurrent { if asCurrent {
accounts.configureAccount() AccountsModel.shared.configureAccount()
} }
return return
} }
@ -47,42 +47,42 @@ final class InstancesManifest: Service, ObservableObject {
if let instances: [ManifestedInstance] = response.typedContent() { if let instances: [ManifestedInstance] = response.typedContent() {
guard let instance = instances.filter { $0.country == country }.randomElement() else { return } guard let instance = instances.filter { $0.country == country }.randomElement() else { return }
let account = instance.anonymousAccount let account = instance.anonymousAccount
accounts.publicAccount = account AccountsModel.shared.publicAccount = account
if asCurrent { if asCurrent {
accounts.setCurrent(account) AccountsModel.shared.setCurrent(account)
} }
} }
} }
} }
func changePublicAccount(_ accounts: AccountsModel, settings: SettingsModel) { func changePublicAccount() {
instancesList?.load().onSuccess { response in instancesList?.load().onSuccess { response in
if let instances: [ManifestedInstance] = response.typedContent() { if let instances: [ManifestedInstance] = response.typedContent() {
var countryInstances = instances.filter { $0.country == Defaults[.countryOfPublicInstances] } var countryInstances = instances.filter { $0.country == Defaults[.countryOfPublicInstances] }
let region = countryInstances.first?.region ?? "Europe" let region = countryInstances.first?.region ?? "Europe"
var regionInstances = instances.filter { $0.region == region } var regionInstances = instances.filter { $0.region == region }
if let publicAccountUrl = accounts.publicAccount?.url { if let publicAccountUrl = AccountsModel.shared.publicAccount?.url {
countryInstances = countryInstances.filter { $0.url.absoluteString != publicAccountUrl } countryInstances = countryInstances.filter { $0.url.absoluteString != publicAccountUrl }
regionInstances = regionInstances.filter { $0.url.absoluteString != publicAccountUrl } regionInstances = regionInstances.filter { $0.url.absoluteString != publicAccountUrl }
} }
var instance: ManifestedInstance? var instance: ManifestedInstance?
if accounts.current?.isPublic ?? false { if AccountsModel.shared.current?.isPublic ?? false {
instance = regionInstances.randomElement() instance = regionInstances.randomElement()
} else { } else {
instance = countryInstances.randomElement() ?? regionInstances.randomElement() instance = countryInstances.randomElement() ?? regionInstances.randomElement()
} }
guard let instance else { guard let instance else {
settings.presentAlert(title: "Could not change location", message: "No locations available at the moment") SettingsModel.shared.presentAlert(title: "Could not change location", message: "No locations available at the moment")
return return
} }
let account = instance.anonymousAccount let account = instance.anonymousAccount
accounts.publicAccount = account AccountsModel.shared.publicAccount = account
accounts.setCurrent(account) AccountsModel.shared.setCurrent(account)
} }
} }
} }

View File

@ -2,12 +2,15 @@ import Combine
import Foundation import Foundation
final class MenuModel: ObservableObject { final class MenuModel: ObservableObject {
@Published var accounts: AccountsModel? { didSet { registerChildModel(accounts) } } static let shared = MenuModel()
@Published var navigation: NavigationModel? { didSet { registerChildModel(navigation) } }
@Published var player: PlayerModel? { didSet { registerChildModel(player) } }
private var cancellables = [AnyCancellable]() private var cancellables = [AnyCancellable]()
init() {
registerChildModel(AccountsModel.shared)
registerChildModel(NavigationModel.shared)
registerChildModel(PlayerModel.shared)
}
func registerChildModel<T: ObservableObject>(_ model: T?) { func registerChildModel<T: ObservableObject>(_ model: T?) {
guard !model.isNil else { guard !model.isNil else {
return return

View File

@ -2,7 +2,11 @@ import Foundation
import SwiftUI import SwiftUI
final class NavigationModel: ObservableObject { final class NavigationModel: ObservableObject {
static var shared: NavigationModel! static var shared = NavigationModel()
var player = PlayerModel.shared
var recents = RecentsModel.shared
var search = SearchModel.shared
enum TabSelection: Hashable { enum TabSelection: Hashable {
case home case home
@ -89,21 +93,15 @@ final class NavigationModel: ObservableObject {
@Published var presentingFileImporter = false @Published var presentingFileImporter = false
static func openChannel( func openChannel(_ channel: Channel, navigationStyle: NavigationStyle) {
_ channel: Channel,
player: PlayerModel,
recents: RecentsModel,
navigation: NavigationModel,
navigationStyle: NavigationStyle
) {
guard channel.id != Video.fixtureChannelID else { guard channel.id != Video.fixtureChannelID else {
return return
} }
navigation.hideKeyboard() hideKeyboard()
let presentingPlayer = player.presentingPlayer let presentingPlayer = player.presentingPlayer
player.hide() player.hide()
navigation.presentingChannel = false presentingChannel = false
#if os(macOS) #if os(macOS)
Windows.main.open() Windows.main.open()
@ -113,8 +111,8 @@ final class NavigationModel: ObservableObject {
recents.add(RecentItem(from: channel)) recents.add(RecentItem(from: channel))
if navigationStyle == .sidebar { if navigationStyle == .sidebar {
navigation.sidebarSectionChanged.toggle() sidebarSectionChanged.toggle()
navigation.tabSelection = .recentlyOpened(recent.tag) tabSelection = .recentlyOpened(recent.tag)
} else { } else {
var delay = 0.0 var delay = 0.0
#if os(iOS) #if os(iOS)
@ -122,21 +120,15 @@ final class NavigationModel: ObservableObject {
#endif #endif
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
withAnimation(Constants.overlayAnimation) { withAnimation(Constants.overlayAnimation) {
navigation.presentingChannel = true self.presentingChannel = true
} }
} }
} }
} }
static func openChannelPlaylist( func openChannelPlaylist(_ playlist: ChannelPlaylist, navigationStyle: NavigationStyle) {
_ playlist: ChannelPlaylist, presentingChannel = false
player: PlayerModel, presentingPlaylist = false
recents: RecentsModel,
navigation: NavigationModel,
navigationStyle: NavigationStyle
) {
navigation.presentingChannel = false
navigation.presentingPlaylist = false
let recent = RecentItem(from: playlist) let recent = RecentItem(from: playlist)
#if os(macOS) #if os(macOS)
@ -145,16 +137,17 @@ final class NavigationModel: ObservableObject {
player.hide() player.hide()
#endif #endif
navigation.hideKeyboard() hideKeyboard()
let presentingPlayer = player.presentingPlayer let presentingPlayer = player.presentingPlayer
player.hide() player.hide()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
recents.add(recent) guard let self else { return }
self.recents.add(recent)
if navigationStyle == .sidebar { if navigationStyle == .sidebar {
navigation.sidebarSectionChanged.toggle() self.sidebarSectionChanged.toggle()
navigation.tabSelection = .recentlyOpened(recent.tag) self.tabSelection = .recentlyOpened(recent.tag)
} else { } else {
var delay = 0.0 var delay = 0.0
#if os(iOS) #if os(iOS)
@ -162,25 +155,19 @@ final class NavigationModel: ObservableObject {
#endif #endif
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
withAnimation(Constants.overlayAnimation) { withAnimation(Constants.overlayAnimation) {
navigation.presentingPlaylist = true self.presentingPlaylist = true
} }
} }
} }
} }
} }
static func openSearchQuery( func openSearchQuery(_ searchQuery: String?) {
_ searchQuery: String?, presentingChannel = false
player: PlayerModel, presentingPlaylist = false
recents: RecentsModel, tabSelection = .search
navigation: NavigationModel,
search: SearchModel
) {
navigation.presentingChannel = false
navigation.presentingPlaylist = false
navigation.tabSelection = .search
navigation.hideKeyboard() hideKeyboard()
let presentingPlayer = player.presentingPlayer let presentingPlayer = player.presentingPlayer
player.hide() player.hide()
@ -193,9 +180,10 @@ final class NavigationModel: ObservableObject {
#if os(iOS) #if os(iOS)
if presentingPlayer { delay = 1.0 } if presentingPlayer { delay = 1.0 }
#endif #endif
DispatchQueue.main.asyncAfter(deadline: .now() + delay) { DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in
search.queryText = searchQuery guard let self else { return }
search.changeQuery { query in query.query = searchQuery } self.search.queryText = searchQuery
self.search.changeQuery { query in query.query = searchQuery }
} }
} }

View File

@ -155,7 +155,7 @@ struct OpenVideosModel {
} }
var canOpenVideosByID: Bool { var canOpenVideosByID: Bool {
guard let app = player.accounts.current?.app else { return false } guard let app = AccountsModel.shared.current?.app else { return false }
return !player.accounts.isEmpty && app.supportsOpeningVideosByID return !AccountsModel.shared.isEmpty && app.supportsOpeningVideosByID
} }
} }

View File

@ -12,11 +12,11 @@ final class AVPlayerBackend: PlayerBackend {
private var logger = Logger(label: "avplayer-backend") private var logger = Logger(label: "avplayer-backend")
var model: PlayerModel! { .shared } var model: PlayerModel { .shared }
var controls: PlayerControlsModel! { .shared } var controls: PlayerControlsModel { .shared }
var playerTime: PlayerTimeModel! { .shared } var playerTime: PlayerTimeModel { .shared }
var networkState: NetworkStateModel! { .shared } var networkState: NetworkStateModel { .shared }
var seek: SeekModel! { .shared } var seek: SeekModel { .shared }
var stream: Stream? var stream: Stream?
var video: Video? var video: Video?

View File

@ -13,11 +13,11 @@ final class MPVBackend: PlayerBackend {
private var logger = Logger(label: "mpv-backend") private var logger = Logger(label: "mpv-backend")
var model: PlayerModel! { .shared } var model: PlayerModel { .shared }
var controls: PlayerControlsModel! { .shared } var controls: PlayerControlsModel { .shared }
var playerTime: PlayerTimeModel! { .shared } var playerTime: PlayerTimeModel { .shared }
var networkState: NetworkStateModel! { .shared } var networkState: NetworkStateModel { .shared }
var seek: SeekModel! { .shared } var seek: SeekModel { .shared }
var stream: Stream? var stream: Stream?
var video: Video? var video: Video?
@ -37,9 +37,9 @@ final class MPVBackend: PlayerBackend {
return return
} }
self.controls?.isLoadingVideo = self.isLoadingVideo self.controls.isLoadingVideo = self.isLoadingVideo
self.setNeedsNetworkStateUpdates(true) self.setNeedsNetworkStateUpdates(true)
self.model?.objectWillChange.send() self.model.objectWillChange.send()
} }
}} }}
@ -333,7 +333,7 @@ final class MPVBackend: PlayerBackend {
isPlaying = true isPlaying = true
startClientUpdates() startClientUpdates()
if controls?.presentingControls ?? false { if controls.presentingControls {
startControlsUpdates() startControlsUpdates()
} }
@ -428,9 +428,9 @@ final class MPVBackend: PlayerBackend {
} }
private func updateControlsIsPlaying() { private func updateControlsIsPlaying() {
guard model?.activeBackend == .mpv else { return } guard model.activeBackend == .mpv else { return }
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
self?.controls?.isPlaying = self?.isPlaying ?? false self?.controls.isPlaying = self?.isPlaying ?? false
} }
} }
@ -533,14 +533,11 @@ final class MPVBackend: PlayerBackend {
} }
func updateNetworkState() { func updateNetworkState() {
guard let client, let networkState else { DispatchQueue.main.async { [weak self] in
return guard let self else { return }
} self.networkState.pausedForCache = self.client.pausedForCache
self.networkState.cacheDuration = self.client.cacheDuration
DispatchQueue.main.async { self.networkState.bufferingState = self.client.bufferingState
networkState.pausedForCache = client.pausedForCache
networkState.cacheDuration = client.cacheDuration
networkState.bufferingState = client.bufferingState
} }
if !networkState.needsUpdates { if !networkState.needsUpdates {

View File

@ -312,7 +312,8 @@ final class MPVClient: ObservableObject {
} }
DispatchQueue.main.async { [weak self] in DispatchQueue.main.async { [weak self] in
guard let self, let model = self.backend.model else { return } guard let self else { return }
let model = self.backend.model
UIView.animate(withDuration: 0.2, animations: { UIView.animate(withDuration: 0.2, animations: {
let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio
let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()! let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()!
@ -329,7 +330,7 @@ final class MPVClient: ObservableObject {
self.glView?.queue.async { self.glView?.queue.async {
self.glView.display() self.glView.display()
} }
self.backend?.controls?.objectWillChange.send() self.backend?.controls.objectWillChange.send()
} }
} }
} }

View File

@ -7,10 +7,10 @@ import Foundation
protocol PlayerBackend { protocol PlayerBackend {
var suggestedPlaybackRates: [Double] { get } var suggestedPlaybackRates: [Double] { get }
var model: PlayerModel! { get } var model: PlayerModel { get }
var controls: PlayerControlsModel! { get } var controls: PlayerControlsModel { get }
var playerTime: PlayerTimeModel! { get } var playerTime: PlayerTimeModel { get }
var networkState: NetworkStateModel! { get } var networkState: NetworkStateModel { get }
var stream: Stream? { get set } var stream: Stream? { get set }
var video: Video? { get set } var video: Video? { get set }

View File

@ -45,7 +45,7 @@ final class PlayerModel: ObservableObject {
} }
} }
static var shared: PlayerModel! static var shared = PlayerModel()
let logger = Logger(label: "stream.yattee.app") let logger = Logger(label: "stream.yattee.app")
@ -76,7 +76,7 @@ final class PlayerModel: ObservableObject {
} }
} }
var playerBackendView = PlayerBackendView() lazy var playerBackendView = PlayerBackendView()
@Published var playerSize: CGSize = .zero { didSet { @Published var playerSize: CGSize = .zero { didSet {
#if !os(tvOS) #if !os(tvOS)
@ -125,13 +125,12 @@ final class PlayerModel: ObservableObject {
@Default(.rotateToPortraitOnExitFullScreen) private var rotateToPortraitOnExitFullScreen @Default(.rotateToPortraitOnExitFullScreen) private var rotateToPortraitOnExitFullScreen
#endif #endif
var accounts: AccountsModel var comments: CommentsModel { .shared }
var comments: CommentsModel
var controls: PlayerControlsModel { .shared } var controls: PlayerControlsModel { .shared }
var playerTime: PlayerTimeModel { .shared } var playerTime: PlayerTimeModel { .shared }
var networkState: NetworkStateModel { .shared } var networkState: NetworkStateModel { .shared }
var seek: SeekModel { .shared } var seek: SeekModel { .shared }
var navigation: NavigationModel var navigation: NavigationModel { .shared }
var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext
var backgroundContext = PersistenceController.shared.container.newBackgroundContext() var backgroundContext = PersistenceController.shared.container.newBackgroundContext()
@ -173,15 +172,7 @@ final class PlayerModel: ObservableObject {
var onPresentPlayer = [() -> Void]() var onPresentPlayer = [() -> Void]()
private var remoteCommandCenterConfigured = false private var remoteCommandCenterConfigured = false
init( init() {
accounts: AccountsModel = AccountsModel(),
comments: CommentsModel = CommentsModel(),
navigation: NavigationModel = NavigationModel()
) {
self.accounts = accounts
self.comments = comments
self.navigation = navigation
#if !os(macOS) #if !os(macOS)
mpvBackend.controller = mpvController mpvBackend.controller = mpvController
mpvBackend.client = mpvController.client mpvBackend.client = mpvController.client

View File

@ -83,11 +83,11 @@ extension PlayerModel {
} }
var playerInstance: Instance? { var playerInstance: Instance? {
InstancesModel.forPlayer ?? accounts.current?.instance ?? InstancesModel.all.first InstancesModel.shared.forPlayer ?? AccountsModel.shared.current?.instance ?? InstancesModel.shared.all.first
} }
var playerAPI: VideosAPI { var playerAPI: VideosAPI {
playerInstance?.anonymous ?? accounts.api playerInstance?.anonymous ?? AccountsModel.shared.api
} }
var qualityProfile: QualityProfile? { var qualityProfile: QualityProfile? {
@ -269,7 +269,7 @@ extension PlayerModel {
} }
func loadQueueVideoDetails(_ item: PlayerQueueItem) { func loadQueueVideoDetails(_ item: PlayerQueueItem) {
guard !accounts.current.isNil, !item.hasDetailsLoaded else { return } guard !AccountsModel.shared.current.isNil, !item.hasDetailsLoaded else { return }
let videoID = item.video?.videoID ?? item.videoID let videoID = item.video?.videoID ?? item.videoID

View File

@ -9,14 +9,14 @@ final class PlayerTimeModel: ObservableObject {
@Published var currentTime = CMTime.zero @Published var currentTime = CMTime.zero
@Published var duration = CMTime.zero @Published var duration = CMTime.zero
var player: PlayerModel! var player: PlayerModel { .shared }
var forceHours: Bool { var forceHours: Bool {
duration.seconds >= 60 * 60 duration.seconds >= 60 * 60
} }
var currentPlaybackTime: String { var currentPlaybackTime: String {
if player?.currentItem.isNil ?? true || duration.seconds.isZero { if player.currentItem.isNil || duration.seconds.isZero {
return Self.timePlaceholder return Self.timePlaceholder
} }
@ -24,7 +24,7 @@ final class PlayerTimeModel: ObservableObject {
} }
var durationPlaybackTime: String { var durationPlaybackTime: String {
if player?.currentItem.isNil ?? true { if player.currentItem.isNil {
return Self.timePlaceholder return Self.timePlaceholder
} }
@ -32,7 +32,7 @@ final class PlayerTimeModel: ObservableObject {
} }
var withoutSegmentsPlaybackTime: String { var withoutSegmentsPlaybackTime: String {
guard let withoutSegmentsDuration = player?.playerItemDurationWithoutSponsorSegments?.seconds else { return Self.timePlaceholder } guard let withoutSegmentsDuration = player.playerItemDurationWithoutSponsorSegments?.seconds else { return Self.timePlaceholder }
return withoutSegmentsDuration.formattedAsPlaybackTime(forceHours: forceHours) ?? Self.timePlaceholder return withoutSegmentsDuration.formattedAsPlaybackTime(forceHours: forceHours) ?? Self.timePlaceholder
} }
} }

View File

@ -4,10 +4,12 @@ import Siesta
import SwiftUI import SwiftUI
final class PlaylistsModel: ObservableObject { final class PlaylistsModel: ObservableObject {
static var shared = PlaylistsModel()
@Published var playlists = [Playlist]() @Published var playlists = [Playlist]()
@Published var reloadPlaylists = false @Published var reloadPlaylists = false
var accounts = AccountsModel() var accounts = AccountsModel.shared
init(_ playlists: [Playlist] = [Playlist]()) { init(_ playlists: [Playlist] = [Playlist]()) {
self.playlists = playlists self.playlists = playlists
@ -63,14 +65,13 @@ final class PlaylistsModel: ObservableObject {
playlistID: Playlist.ID, playlistID: Playlist.ID,
videoID: Video.ID, videoID: Video.ID,
onSuccess: @escaping () -> Void = {}, onSuccess: @escaping () -> Void = {},
navigation: NavigationModel?,
onFailure: ((RequestError) -> Void)? = nil onFailure: ((RequestError) -> Void)? = nil
) { ) {
accounts.api.addVideoToPlaylist( accounts.api.addVideoToPlaylist(
videoID, videoID,
playlistID, playlistID,
onFailure: onFailure ?? { requestError in onFailure: onFailure ?? { requestError in
navigation?.presentAlert( NavigationModel.shared.presentAlert(
title: "Error when adding to playlist", title: "Error when adding to playlist",
message: "(\(requestError.httpStatusCode ?? -1)) \(requestError.userMessage)" message: "(\(requestError.httpStatusCode ?? -1)) \(requestError.userMessage)"
) )

View File

@ -2,6 +2,8 @@ import Defaults
import Foundation import Foundation
final class RecentsModel: ObservableObject { final class RecentsModel: ObservableObject {
static var shared = RecentsModel()
@Default(.recentlyOpened) var items @Default(.recentlyOpened) var items
@Default(.saveRecents) var saveRecents @Default(.saveRecents) var saveRecents
@ -35,9 +37,9 @@ final class RecentsModel: ObservableObject {
} }
} }
func addQuery(_ query: String, navigation: NavigationModel? = nil) { func addQuery(_ query: String) {
if !query.isEmpty { if !query.isEmpty {
navigation?.tabSelection = .search NavigationModel.shared.tabSelection = .search
add(.init(from: query)) add(.init(from: query))
} }
} }

View File

@ -4,6 +4,8 @@ import Siesta
import SwiftUI import SwiftUI
final class SearchModel: ObservableObject { final class SearchModel: ObservableObject {
static var shared = SearchModel()
@Published var store = Store<[ContentItem]>() @Published var store = Store<[ContentItem]>()
@Published var page: SearchPage? @Published var page: SearchPage?
@ -14,7 +16,7 @@ final class SearchModel: ObservableObject {
@Published var querySuggestions = [String]() @Published var querySuggestions = [String]()
private var suggestionsDebouncer = Debouncer(.milliseconds(200)) private var suggestionsDebouncer = Debouncer(.milliseconds(200))
var accounts = AccountsModel() var accounts: AccountsModel { .shared }
private var resource: Resource! private var resource: Resource!
var isLoading: Bool { var isLoading: Bool {

View File

@ -2,6 +2,8 @@ import Foundation
import SwiftUI import SwiftUI
final class SettingsModel: ObservableObject { final class SettingsModel: ObservableObject {
static var shared = SettingsModel()
@Published var presentingAlert = false @Published var presentingAlert = false
@Published var alert = Alert(title: Text("Error")) @Published var alert = Alert(title: Text("Error"))

View File

@ -3,17 +3,15 @@ import Siesta
import SwiftUI import SwiftUI
final class SubscriptionsModel: ObservableObject { final class SubscriptionsModel: ObservableObject {
static var shared = SubscriptionsModel()
@Published var channels = [Channel]() @Published var channels = [Channel]()
var accounts: AccountsModel var accounts: AccountsModel { .shared }
var resource: Resource? { var resource: Resource? {
accounts.api.subscriptions accounts.api.subscriptions
} }
init(accounts: AccountsModel? = nil) {
self.accounts = accounts ?? AccountsModel()
}
var all: [Channel] { var all: [Channel] {
channels.sorted { $0.name.lowercased() < $1.name.lowercased() } channels.sorted { $0.name.lowercased() < $1.name.lowercased() }
} }

View File

@ -2,6 +2,8 @@ import Defaults
import Foundation import Foundation
final class ThumbnailsModel: ObservableObject { final class ThumbnailsModel: ObservableObject {
static var shared = ThumbnailsModel()
@Published var unloadable = Set<URL>() @Published var unloadable = Set<URL>()
func insertUnloadable(_ url: URL) { func insertUnloadable(_ url: URL) {

View File

@ -11,9 +11,8 @@ struct FavoriteItemView: View {
@Default(.favorites) private var favorites @Default(.favorites) private var favorites
@Binding private var dragging: FavoriteItem? @Binding private var dragging: FavoriteItem?
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists private var playlists = PlaylistsModel.shared
private var favoritesModel = FavoritesModel.shared private var favoritesModel = FavoritesModel.shared
init( init(

View File

@ -8,7 +8,7 @@ struct HistoryView: View {
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
var watches: FetchedResults<Watch> var watches: FetchedResults<Watch>
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
var body: some View { var body: some View {
LazyVStack { LazyVStack {

View File

@ -4,8 +4,7 @@ import SwiftUI
import UniformTypeIdentifiers import UniformTypeIdentifiers
struct HomeView: View { struct HomeView: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists
@State private var dragging: FavoriteItem? @State private var dragging: FavoriteItem?
@State private var presentingEditFavorites = false @State private var presentingEditFavorites = false

View File

@ -12,7 +12,7 @@ struct MenuCommands: Commands {
private var openVideosMenu: some Commands { private var openVideosMenu: some Commands {
CommandGroup(after: .newItem) { CommandGroup(after: .newItem) {
Button("Open Videos...") { model.navigation?.presentingOpenVideos = true } Button("Open Videos...") { NavigationModel.shared.presentingOpenVideos = true }
.keyboardShortcut("t") .keyboardShortcut("t")
} }
} }
@ -33,7 +33,7 @@ struct MenuCommands: Commands {
Button("Popular") { Button("Popular") {
setTabSelection(.popular) setTabSelection(.popular)
} }
.disabled(!(model.accounts?.app.supportsPopular ?? false)) .disabled(!AccountsModel.shared.app.supportsPopular)
.keyboardShortcut("3") .keyboardShortcut("3")
Button("Trending") { Button("Trending") {
@ -51,36 +51,30 @@ struct MenuCommands: Commands {
} }
private func setTabSelection(_ tabSelection: NavigationModel.TabSelection) { private func setTabSelection(_ tabSelection: NavigationModel.TabSelection) {
guard let navigation = model.navigation else { NavigationModel.shared.sidebarSectionChanged.toggle()
return NavigationModel.shared.tabSelection = tabSelection
}
navigation.sidebarSectionChanged.toggle()
navigation.tabSelection = tabSelection
} }
private var subscriptionsDisabled: Bool { private var subscriptionsDisabled: Bool {
!( !(AccountsModel.shared.app.supportsSubscriptions && AccountsModel.shared.signedIn)
(model.accounts?.app.supportsSubscriptions ?? false) && model.accounts?.signedIn ?? false
)
} }
private var playbackMenu: some Commands { private var playbackMenu: some Commands {
CommandMenu("Playback") { CommandMenu("Playback") {
Button((model.player?.isPlaying ?? true) ? "Pause" : "Play") { Button((PlayerModel.shared.isPlaying) ? "Pause" : "Play") {
model.player?.togglePlay() PlayerModel.shared.togglePlay()
} }
.disabled(model.player?.currentItem.isNil ?? true) .disabled(PlayerModel.shared.currentItem.isNil)
.keyboardShortcut("p") .keyboardShortcut("p")
Button("Play Next") { Button("Play Next") {
model.player?.advanceToNextItem() PlayerModel.shared.advanceToNextItem()
} }
.disabled(model.player?.queue.isEmpty ?? true) .disabled(PlayerModel.shared.queue.isEmpty)
.keyboardShortcut("s") .keyboardShortcut("s")
Button(togglePlayerLabel) { Button(togglePlayerLabel) {
model.player?.togglePlayer() PlayerModel.shared.togglePlayer()
} }
.keyboardShortcut("o") .keyboardShortcut("o")
} }
@ -90,7 +84,7 @@ struct MenuCommands: Commands {
#if os(macOS) #if os(macOS)
"Show Player" "Show Player"
#else #else
(model.player?.presentingPlayer ?? true) ? "Hide Player" : "Show Player" PlayerModel.shared.presentingPlayer ? "Hide Player" : "Show Player"
#endif #endif
} }
} }

View File

@ -2,7 +2,7 @@ import Defaults
import SwiftUI import SwiftUI
struct AccountsMenuView: View { struct AccountsMenuView: View {
@EnvironmentObject<AccountsModel> private var model @ObservedObject private var model = AccountsModel.shared
@Default(.accounts) private var accounts @Default(.accounts) private var accounts
@Default(.instances) private var instances @Default(.instances) private var instances

View File

@ -5,20 +5,11 @@ import SwiftUI
#endif #endif
struct AppSidebarNavigation: View { struct AppSidebarNavigation: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation private var navigation: NavigationModel { .shared }
#if os(iOS) #if os(iOS)
@State private var didApplyPrimaryViewWorkAround = false @State private var didApplyPrimaryViewWorkAround = false
@EnvironmentObject<CommentsModel> private var comments
@EnvironmentObject<InstancesModel> private var instances
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@EnvironmentObject<ThumbnailsModel> private var thumbnailsModel
#endif #endif
@Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem @Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem

View File

@ -1,9 +1,10 @@
import SwiftUI import SwiftUI
struct AppSidebarPlaylists: View { struct AppSidebarPlaylists: View {
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists private var player = PlayerModel.shared
@ObservedObject private var playlists = PlaylistsModel.shared
var body: some View { var body: some View {
Section(header: Text("Playlists")) { Section(header: Text("Playlists")) {
@ -35,7 +36,7 @@ struct AppSidebarPlaylists: View {
@ViewBuilder func playlistLabel(_ playlist: Playlist) -> some View { @ViewBuilder func playlistLabel(_ playlist: Playlist) -> some View {
let label = Label(playlist.title, systemImage: RecentsModel.symbolSystemImage(playlist.title)) let label = Label(playlist.title, systemImage: RecentsModel.symbolSystemImage(playlist.title))
if player.accounts.app.userPlaylistsEndpointIncludesVideos, !playlist.videos.isEmpty { if accounts.app.userPlaylistsEndpointIncludesVideos, !playlist.videos.isEmpty {
label label
.backport .backport
.badge(Text("\(playlist.videos.count)")) .badge(Text("\(playlist.videos.count)"))

View File

@ -2,7 +2,8 @@ import Defaults
import SwiftUI import SwiftUI
struct AppSidebarRecents: View { struct AppSidebarRecents: View {
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var navigation = NavigationModel.shared
var recents = RecentsModel.shared
@Default(.recentlyOpened) private var recentItems @Default(.recentlyOpened) private var recentItems
@ -47,8 +48,8 @@ struct AppSidebarRecents: View {
} }
struct RecentNavigationLink<DestinationContent: View>: View { struct RecentNavigationLink<DestinationContent: View>: View {
@EnvironmentObject<NavigationModel> private var navigation var recents = RecentsModel.shared
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var navigation = NavigationModel.shared
var recent: RecentItem var recent: RecentItem
var systemImage: String? var systemImage: String?

View File

@ -2,8 +2,8 @@ import Defaults
import SwiftUI import SwiftUI
struct AppSidebarSubscriptions: View { struct AppSidebarSubscriptions: View {
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<SubscriptionsModel> private var subscriptions @ObservedObject private var subscriptions = SubscriptionsModel.shared
var body: some View { var body: some View {
Section(header: Text("Subscriptions")) { Section(header: Text("Subscriptions")) {

View File

@ -2,16 +2,10 @@ import Defaults
import SwiftUI import SwiftUI
struct AppTabNavigation: View { struct AppTabNavigation: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<CommentsModel> private var comments @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<InstancesModel> private var instances private var player = PlayerModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@EnvironmentObject<ThumbnailsModel> private var thumbnailsModel
@Default(.showHome) private var showHome @Default(.showHome) private var showHome
@Default(.showDocuments) private var showDocuments @Default(.showDocuments) private var showDocuments
@ -187,11 +181,6 @@ struct AppTabNavigation: View {
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environment(\.inChannelView, true) .environment(\.inChannelView, true)
.environment(\.navigationStyle, .tab) .environment(\.navigationStyle, .tab)
.environmentObject(accounts)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
.id("channelVideos") .id("channelVideos")
.zIndex(player.presentingPlayer ? -1 : 2) .zIndex(player.presentingPlayer ? -1 : 2)
.transition(.move(edge: .bottom)) .transition(.move(edge: .bottom))
@ -202,11 +191,6 @@ struct AppTabNavigation: View {
if navigation.presentingPlaylist { if navigation.presentingPlaylist {
ChannelPlaylistView() ChannelPlaylistView()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(accounts)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
.id("channelPlaylist") .id("channelPlaylist")
.zIndex(player.presentingPlayer ? -1 : 1) .zIndex(player.presentingPlayer ? -1 : 1)
.transition(.move(edge: .bottom)) .transition(.move(edge: .bottom))

View File

@ -8,19 +8,11 @@ import Siesta
import SwiftUI import SwiftUI
struct ContentView: View { struct ContentView: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<CommentsModel> private var comments @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<InstancesModel> private var instances @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<NavigationModel> private var navigation private var playlists = PlaylistsModel.shared
@EnvironmentObject<PlayerModel> private var player private var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@EnvironmentObject<SettingsModel> private var settings
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@EnvironmentObject<ThumbnailsModel> private var thumbnailsModel
@EnvironmentObject<MenuModel> private var menu
#if os(iOS) #if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
@ -44,7 +36,6 @@ struct ContentView: View {
AppSidebarNavigation() AppSidebarNavigation()
#elseif os(tvOS) #elseif os(tvOS)
TVNavigationView() TVNavigationView()
.environmentObject(settings)
#endif #endif
} }
.onChange(of: accounts.current) { _ in .onChange(of: accounts.current) { _ in
@ -55,16 +46,6 @@ struct ContentView: View {
subscriptions.load(force: true) subscriptions.load(force: true)
playlists.load(force: true) playlists.load(force: true)
} }
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(search)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
#if os(iOS) #if os(iOS)
.overlay(videoPlayer) .overlay(videoPlayer)
@ -80,18 +61,11 @@ struct ContentView: View {
.background( .background(
EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) { EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) {
WelcomeScreen() WelcomeScreen()
.environmentObject(accounts)
.environmentObject(navigation)
} }
) )
.background( .background(
EmptyView().sheet(isPresented: $navigation.presentingSettings) { EmptyView().sheet(isPresented: $navigation.presentingSettings) {
SettingsView() SettingsView()
.environmentObject(accounts)
.environmentObject(instances)
.environmentObject(settings)
.environmentObject(navigation)
.environmentObject(player)
} }
) )
#if !os(tvOS) #if !os(tvOS)
@ -125,25 +99,17 @@ struct ContentView: View {
.background( .background(
EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) { EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) {
AddToPlaylistView(video: navigation.videoToAddToPlaylist) AddToPlaylistView(video: navigation.videoToAddToPlaylist)
.environmentObject(playlists)
} }
) )
.background( .background(
EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) { EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) {
PlaylistFormView(playlist: $navigation.editedPlaylist) PlaylistFormView(playlist: $navigation.editedPlaylist)
.environmentObject(accounts)
.environmentObject(playlists)
} }
) )
#endif #endif
.background( .background(
EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) { EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) {
OpenVideosView() OpenVideosView()
.environmentObject(accounts)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(recents)
.environmentObject(search)
} }
) )
.background(playerViewInitialize) .background(playerViewInitialize)
@ -174,16 +140,6 @@ struct ContentView: View {
var playerView: some View { var playerView: some View {
VideoPlayerView() VideoPlayerView()
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(subscriptions)
.environmentObject(thumbnailsModel)
.environment(\.navigationStyle, navigationStyle) .environment(\.navigationStyle, navigationStyle)
} }

View File

@ -2,8 +2,8 @@ import Defaults
import SwiftUI import SwiftUI
struct Sidebar: View { struct Sidebar: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var navigation = NavigationModel.shared
@Default(.showHome) private var showHome @Default(.showHome) private var showHome
@Default(.visibleSections) private var visibleSections @Default(.visibleSections) private var visibleSections

View File

@ -7,11 +7,11 @@ struct OpenURLHandler {
static var shared = OpenURLHandler() static var shared = OpenURLHandler()
static let yatteeProtocol = "yattee://" static let yatteeProtocol = "yattee://"
var accounts: AccountsModel! var accounts: AccountsModel { .shared }
var navigation: NavigationModel! var navigation: NavigationModel { .shared }
var recents: RecentsModel! var recents: RecentsModel { .shared }
var player: PlayerModel! var player: PlayerModel { .shared }
var search: SearchModel! var search: SearchModel { .shared }
var navigationStyle = NavigationStyle.sidebar var navigationStyle = NavigationStyle.sidebar
func handle(_ url: URL) { func handle(_ url: URL) {
@ -162,11 +162,8 @@ struct OpenURLHandler {
if var playlist: ChannelPlaylist = response.typedContent() { if var playlist: ChannelPlaylist = response.typedContent() {
playlist.id = playlistID playlist.id = playlistID
DispatchQueue.main.async { DispatchQueue.main.async {
NavigationModel.openChannelPlaylist( NavigationModel.shared.openChannelPlaylist(
playlist, playlist,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} }
@ -194,11 +191,8 @@ struct OpenURLHandler {
.onSuccess { response in .onSuccess { response in
if let channel: Channel = response.typedContent() { if let channel: Channel = response.typedContent() {
DispatchQueue.main.async { DispatchQueue.main.async {
NavigationModel.openChannel( NavigationModel.shared.openChannel(
channel, channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} }
@ -228,7 +222,7 @@ struct OpenURLHandler {
return accounts.api.channelByName(name) return accounts.api.channelByName(name)
} }
if let instance = InstancesModel.all.first(where: { $0.app.supportsOpeningChannelsByName }) { if let instance = InstancesModel.shared.all.first(where: { $0.app.supportsOpeningChannelsByName }) {
return instance.anonymous.channelByName(name) return instance.anonymous.channelByName(name)
} }
@ -242,7 +236,7 @@ struct OpenURLHandler {
return accounts.api.channelByUsername(username) return accounts.api.channelByUsername(username)
} }
if let instance = InstancesModel.all.first(where: { $0.app.supportsOpeningChannelsByName }) { if let instance = InstancesModel.shared.all.first(where: { $0.app.supportsOpeningChannelsByName }) {
return instance.anonymous.channelByUsername(username) return instance.anonymous.channelByUsername(username)
} }
@ -254,13 +248,7 @@ struct OpenURLHandler {
if alertIfNoMainWindowOpen() { return } if alertIfNoMainWindowOpen() { return }
#endif #endif
NavigationModel.openSearchQuery( NavigationModel.shared.openSearchQuery(parser.searchQuery)
parser.searchQuery,
player: player,
recents: recents,
navigation: navigation,
search: search
)
#if os(macOS) #if os(macOS)
focusMainWindow() focusMainWindow()

View File

@ -4,11 +4,8 @@ import SwiftUI
#if os(iOS) #if os(iOS)
struct AppleAVPlayerView: UIViewRepresentable { struct AppleAVPlayerView: UIViewRepresentable {
@EnvironmentObject<PlayerModel> private var player
func makeUIView(context _: Context) -> some UIView { func makeUIView(context _: Context) -> some UIView {
let playerLayerView = PlayerLayerView(frame: .zero) let playerLayerView = PlayerLayerView(frame: .zero)
playerLayerView.player = player
return playerLayerView return playerLayerView
} }
@ -16,30 +13,15 @@ import SwiftUI
} }
#else #else
struct AppleAVPlayerView: UIViewControllerRepresentable { struct AppleAVPlayerView: UIViewControllerRepresentable {
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<CommentsModel> private var comments
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<SubscriptionsModel> private var subscriptions
func makeUIViewController(context _: Context) -> AppleAVPlayerViewController { func makeUIViewController(context _: Context) -> AppleAVPlayerViewController {
let controller = AppleAVPlayerViewController() let controller = AppleAVPlayerViewController()
PlayerModel.shared.avPlayerBackend.controller = controller
controller.accountsModel = accounts
controller.commentsModel = comments
controller.navigationModel = navigation
controller.playerModel = player
controller.playlistsModel = playlists
controller.subscriptionsModel = subscriptions
player.avPlayerBackend.controller = controller
return controller return controller
} }
func updateUIViewController(_: AppleAVPlayerViewController, context _: Context) { func updateUIViewController(_: AppleAVPlayerViewController, context _: Context) {
player.rebuildTVMenu() PlayerModel.shared.rebuildTVMenu()
} }
} }
#endif #endif

View File

@ -4,12 +4,11 @@ import SwiftUI
final class AppleAVPlayerViewController: UIViewController { final class AppleAVPlayerViewController: UIViewController {
var playerLoaded = false var playerLoaded = false
var accountsModel: AccountsModel! var accountsModel: AccountsModel { .shared }
var commentsModel: CommentsModel! var navigationModel: NavigationModel { .shared }
var navigationModel: NavigationModel! var playerModel: PlayerModel { .shared }
var playerModel: PlayerModel! var playlistsModel: PlaylistsModel { .shared }
var playlistsModel: PlaylistsModel! var subscriptionsModel: SubscriptionsModel { .shared }
var subscriptionsModel: SubscriptionsModel!
var playerView = AVPlayerViewController() var playerView = AVPlayerViewController()
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
@ -66,12 +65,6 @@ final class AppleAVPlayerViewController: UIViewController {
AnyView( AnyView(
NowPlayingView(sections: sections, inInfoViewController: true) NowPlayingView(sections: sections, inInfoViewController: true)
.frame(maxHeight: 600) .frame(maxHeight: 600)
.environmentObject(accountsModel)
.environmentObject(commentsModel)
.environmentObject(navigationModel)
.environmentObject(playerModel)
.environmentObject(playlistsModel)
.environmentObject(subscriptionsModel)
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
) )
) )

View File

@ -2,8 +2,8 @@ import Defaults
import SwiftUI import SwiftUI
struct ControlsOverlay: View { struct ControlsOverlay: View {
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<PlayerControlsModel> private var model private var model = PlayerControlsModel.shared
@State private var contentSize: CGSize = .zero @State private var contentSize: CGSize = .zero
@ -399,8 +399,5 @@ struct ControlsOverlay: View {
struct ControlsOverlay_Previews: PreviewProvider { struct ControlsOverlay_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ControlsOverlay() ControlsOverlay()
.environmentObject(NetworkStateModel())
.environmentObject(PlayerModel())
.environmentObject(PlayerControlsModel())
} }
} }

View File

@ -10,7 +10,7 @@ struct Buffering: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.playerControlsLayout) private var regularPlayerControlsLayout
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout

View File

@ -15,7 +15,5 @@ struct NetworkState_Previews: PreviewProvider {
networkState.bufferingState = 30 networkState.bufferingState = 30
return NetworkState() return NetworkState()
.environmentObject(networkState)
.environmentObject(PlayerModel())
} }
} }

View File

@ -1,8 +1,8 @@
import SwiftUI import SwiftUI
struct OpeningStream: View { struct OpeningStream: View {
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<NetworkStateModel> private var model @ObservedObject private var model = NetworkStateModel.shared
var body: some View { var body: some View {
Buffering(reason: reason, state: state) Buffering(reason: reason, state: state)

View File

@ -6,7 +6,7 @@ struct Seek: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@EnvironmentObject<PlayerControlsModel> private var controls @ObservedObject private var controls = PlayerControlsModel.shared
@StateObject private var model = SeekModel.shared @StateObject private var model = SeekModel.shared
private var updateThrottle = Throttle(interval: 2) private var updateThrottle = Throttle(interval: 2)
@ -137,6 +137,5 @@ struct Seek: View {
struct Seek_Previews: PreviewProvider { struct Seek_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
Seek() Seek()
.environmentObject(PlayerTimeModel())
} }
} }

View File

@ -6,8 +6,8 @@ import SwiftUI
struct PlayerControls: View { struct PlayerControls: View {
static let animation = Animation.easeInOut(duration: 0.2) static let animation = Animation.easeInOut(duration: 0.2)
private var player: PlayerModel! private var player: PlayerModel { .shared }
private var thumbnails: ThumbnailsModel! private var thumbnails: ThumbnailsModel { .shared }
@ObservedObject private var model = PlayerControlsModel.shared @ObservedObject private var model = PlayerControlsModel.shared
@ -39,11 +39,6 @@ struct PlayerControls: View {
player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout
} }
init(player: PlayerModel, thumbnails: ThumbnailsModel) {
self.player = player
self.thumbnails = thumbnails
}
var body: some View { var body: some View {
ZStack(alignment: .topLeading) { ZStack(alignment: .topLeading) {
Seek() Seek()
@ -206,12 +201,12 @@ struct PlayerControls: View {
} }
var detailsWidth: Double { var detailsWidth: Double {
guard let player, player.playerSize.width.isFinite else { return 200 } guard player.playerSize.width.isFinite else { return 200 }
return [player.playerSize.width, 600].min()! return [player.playerSize.width, 600].min()!
} }
var detailsHeight: Double { var detailsHeight: Double {
guard let player, player.playerSize.height.isFinite else { return 200 } guard player.playerSize.height.isFinite else { return 200 }
var inset = 0.0 var inset = 0.0
#if os(iOS) #if os(iOS)
inset = SafeArea.insets.bottom inset = SafeArea.insets.bottom
@ -499,7 +494,7 @@ struct PlayerControls_Previews: PreviewProvider {
ZStack { ZStack {
Color.gray Color.gray
PlayerControls(player: PlayerModel(), thumbnails: ThumbnailsModel()) PlayerControls()
.injectFixtureEnvironmentObjects() .injectFixtureEnvironmentObjects()
} }
} }

View File

@ -3,8 +3,8 @@ import SwiftUI
struct TVControls: UIViewRepresentable { struct TVControls: UIViewRepresentable {
var model: PlayerControlsModel! var model: PlayerControlsModel!
var player: PlayerModel! var player: PlayerModel { .shared }
var thumbnails: ThumbnailsModel! var thumbnails: ThumbnailsModel { .shared }
@State private var direction = "" @State private var direction = ""
@State private var controlsArea = UIView() @State private var controlsArea = UIView()
@ -30,7 +30,7 @@ struct TVControls: UIViewRepresentable {
controlsArea.addGestureRecognizer(downSwipe) controlsArea.addGestureRecognizer(downSwipe)
controlsArea.addGestureRecognizer(tap) controlsArea.addGestureRecognizer(tap)
let controls = UIHostingController(rootView: PlayerControls(player: player, thumbnails: thumbnails)) let controls = UIHostingController(rootView: PlayerControls())
controls.view.frame = .init( controls.view.frame = .init(
origin: .init(x: SafeArea.insets.left, y: SafeArea.insets.top), origin: .init(x: SafeArea.insets.left, y: SafeArea.insets.top),
size: .init( size: .init(

View File

@ -45,9 +45,9 @@ struct TimelineView: View {
#endif #endif
@ObservedObject private var playerTime = PlayerTimeModel.shared @ObservedObject private var playerTime = PlayerTimeModel.shared
@ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<PlayerModel> private var player private var controls = PlayerControlsModel.shared
@EnvironmentObject<PlayerControlsModel> private var controls
@Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.playerControlsLayout) private var regularPlayerControlsLayout
@Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout
@ -124,6 +124,8 @@ struct TimelineView: View {
.frame(minWidth: 35) .frame(minWidth: 35)
.padding(.leading, playerControlsLayout.timeLeadingEdgePadding) .padding(.leading, playerControlsLayout.timeLeadingEdgePadding)
.padding(.trailing, playerControlsLayout.timeTrailingEdgePadding) .padding(.trailing, playerControlsLayout.timeTrailingEdgePadding)
.modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 4))
ZStack(alignment: .center) { ZStack(alignment: .center) {
ZStack(alignment: .leading) { ZStack(alignment: .leading) {
@ -172,6 +174,8 @@ struct TimelineView: View {
.padding(.leading, playerControlsLayout.timeTrailingEdgePadding) .padding(.leading, playerControlsLayout.timeTrailingEdgePadding)
.padding(.trailing, playerControlsLayout.timeLeadingEdgePadding) .padding(.trailing, playerControlsLayout.timeLeadingEdgePadding)
.frame(minWidth: 30, alignment: .trailing) .frame(minWidth: 30, alignment: .trailing)
.modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 4))
} }
#if !os(tvOS) #if !os(tvOS)
.highPriorityGesture( .highPriorityGesture(
@ -207,8 +211,6 @@ struct TimelineView: View {
} }
) )
#endif #endif
.modifier(ControlBackgroundModifier())
.clipShape(RoundedRectangle(cornerRadius: 4))
.font(.system(size: playerControlsLayout.timeFontSize).monospacedDigit()) .font(.system(size: playerControlsLayout.timeFontSize).monospacedDigit())
.zIndex(2) .zIndex(2)
} }
@ -369,14 +371,11 @@ struct TimelineView_Previews: PreviewProvider {
let playerModel = PlayerModel() let playerModel = PlayerModel()
playerModel.currentItem = .init(Video.fixture) playerModel.currentItem = .init(Video.fixture)
let playerTimeModel = PlayerTimeModel.shared let playerTimeModel = PlayerTimeModel.shared
playerTimeModel.player = playerModel
playerTimeModel.currentTime = .secondsInDefaultTimescale(33) playerTimeModel.currentTime = .secondsInDefaultTimescale(33)
playerTimeModel.duration = .secondsInDefaultTimescale(100) playerTimeModel.duration = .secondsInDefaultTimescale(100)
return VStack(spacing: 40) { return VStack(spacing: 40) {
TimelineView() TimelineView()
} }
.environmentObject(playerModel)
.environmentObject(PlayerControlsModel())
.padding() .padding()
} }
} }

View File

@ -2,7 +2,7 @@ import Defaults
import SwiftUI import SwiftUI
struct VideoDetailsOverlay: View { struct VideoDetailsOverlay: View {
@EnvironmentObject<PlayerControlsModel> private var controls @ObservedObject private var controls = PlayerControlsModel.shared
@State private var detailsPage = VideoDetails.DetailsPage.queue @State private var detailsPage = VideoDetails.DetailsPage.queue

View File

@ -4,8 +4,7 @@ struct PlayerBackendView: View {
#if os(iOS) #if os(iOS)
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<ThumbnailsModel> private var thumbnails
var body: some View { var body: some View {
ZStack(alignment: .top) { ZStack(alignment: .top) {
@ -29,7 +28,7 @@ struct PlayerBackendView: View {
#if !os(tvOS) #if !os(tvOS)
PlayerGestures() PlayerGestures()
PlayerControls(player: player, thumbnails: thumbnails) PlayerControls()
#if os(iOS) #if os(iOS)
.padding(.top, controlsTopPadding) .padding(.top, controlsTopPadding)
.padding(.bottom, controlsBottomPadding) .padding(.bottom, controlsBottomPadding)

View File

@ -1,8 +1,8 @@
import SwiftUI import SwiftUI
struct PlayerGestures: View { struct PlayerGestures: View {
@EnvironmentObject<PlayerModel> private var player private var player = PlayerModel.shared
@EnvironmentObject<PlayerControlsModel> private var model @ObservedObject private var model = PlayerControlsModel.shared
var body: some View { var body: some View {
HStack(spacing: 0) { HStack(spacing: 0) {

View File

@ -8,7 +8,7 @@ import Foundation
#if os(macOS) #if os(macOS)
final class PlayerLayerView: NSView { final class PlayerLayerView: NSView {
var player: PlayerModel! { didSet { var player = PlayerModel.shared { didSet {
wantsLayer = true wantsLayer = true
}} }}
@ -26,7 +26,7 @@ import Foundation
} }
#else #else
final class PlayerLayerView: UIView { final class PlayerLayerView: UIView {
var player: PlayerModel! var player: PlayerModel { .shared }
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)

View File

@ -9,7 +9,7 @@ struct PlayerQueueRow: View {
var autoplay = false var autoplay = false
@Binding var fullScreen: Bool @Binding var fullScreen: Bool
@EnvironmentObject<PlayerModel> private var player private var player = PlayerModel.shared
@Default(.closePiPOnNavigation) var closePiPOnNavigation @Default(.closePiPOnNavigation) var closePiPOnNavigation

View File

@ -2,10 +2,7 @@ import Defaults
import SwiftUI import SwiftUI
struct RelatedView: View { struct RelatedView: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<PlaylistsModel> private var playlists
var body: some View { var body: some View {
List { List {

View File

@ -9,7 +9,7 @@ struct StreamControl: View {
} }
#endif #endif
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
var body: some View { var body: some View {
Group { Group {

View File

@ -5,7 +5,7 @@ import SwiftUI
struct ChapterView: View { struct ChapterView: View {
var chapter: Chapter var chapter: Chapter
@EnvironmentObject<PlayerModel> private var player var player = PlayerModel.shared
var body: some View { var body: some View {
Button { Button {

View File

@ -3,7 +3,7 @@ import SDWebImageSwiftUI
import SwiftUI import SwiftUI
struct ChaptersView: View { struct ChaptersView: View {
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
var body: some View { var body: some View {
if let chapters = player.currentVideo?.chapters, !chapters.isEmpty { if let chapters = player.currentVideo?.chapters, !chapters.isEmpty {

View File

@ -14,11 +14,8 @@ struct CommentView: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<CommentsModel> private var comments @ObservedObject private var comments = CommentsModel.shared
@EnvironmentObject<NavigationModel> private var navigation var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SubscriptionsModel> private var subscriptions
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -252,11 +249,8 @@ struct CommentView: View {
} }
private func openChannelAction() { private func openChannelAction() {
NavigationModel.openChannel( NavigationModel.shared.openChannel(
comment.channel, comment.channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} }
@ -269,7 +263,6 @@ struct CommentView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
CommentView(comment: fixture, repliesID: .constant(fixture.id)) CommentView(comment: fixture, repliesID: .constant(fixture.id))
.environmentObject(SubscriptionsModel())
.padding(5) .padding(5)
} }
} }

View File

@ -4,7 +4,7 @@ struct CommentsView: View {
var embedInScrollView = false var embedInScrollView = false
@State private var repliesID: Comment.ID? @State private var repliesID: Comment.ID?
@EnvironmentObject<CommentsModel> private var comments @ObservedObject private var comments = CommentsModel.shared
var body: some View { var body: some View {
Group { Group {

View File

@ -3,7 +3,7 @@ import SwiftUI
struct InspectorView: View { struct InspectorView: View {
var video: Video? var video: Video?
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
var body: some View { var body: some View {
ScrollView { ScrollView {

View File

@ -9,10 +9,7 @@ struct PlayerQueueView: View {
@FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)])
var watches: FetchedResults<Watch> var watches: FetchedResults<Watch>
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlaylistsModel> private var playlists
@EnvironmentObject<PlayerModel> private var player
@Default(.saveHistory) private var saveHistory @Default(.saveHistory) private var saveHistory

View File

@ -2,10 +2,10 @@ import Defaults
import SwiftUI import SwiftUI
struct VideoActions: View { struct VideoActions: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation var navigation = NavigationModel.shared
@EnvironmentObject<SubscriptionsModel> private var subscriptions @ObservedObject private var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
var video: Video? var video: Video?

View File

@ -6,10 +6,7 @@ import Foundation
import SwiftUI import SwiftUI
struct VideoDescription: View { struct VideoDescription: View {
@EnvironmentObject<NavigationModel> private var navigation private var search: SearchModel { .shared }
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@Default(.showKeywords) private var showKeywords @Default(.showKeywords) private var showKeywords
var video: Video var video: Video
@ -56,7 +53,7 @@ struct VideoDescription: View {
HStack { HStack {
ForEach(video.keywords, id: \.self) { keyword in ForEach(video.keywords, id: \.self) { keyword in
Button { Button {
NavigationModel.openSearchQuery(keyword, player: player, recents: recents, navigation: navigation, search: search) NavigationModel.shared.openSearchQuery(keyword)
} label: { } label: {
HStack(alignment: .center, spacing: 0) { HStack(alignment: .center, spacing: 0) {
Text("#") Text("#")
@ -96,7 +93,8 @@ struct VideoDescription: View {
@State private var label = ActiveLabel() @State private var label = ActiveLabel()
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@EnvironmentObject<PlayerModel> private var player
var player = PlayerModel.shared
func makeUIView(context _: Context) -> some UIView { func makeUIView(context _: Context) -> some UIView {
customizeLabel() customizeLabel()

View File

@ -23,12 +23,9 @@ struct VideoDetails: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<CommentsModel> private var comments let comments = CommentsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
@Default(.detailsToolbarPosition) private var detailsToolbarPosition @Default(.detailsToolbarPosition) private var detailsToolbarPosition
@ -148,6 +145,7 @@ struct VideoDetails: View {
} }
} }
.contentShape(Rectangle()) .contentShape(Rectangle())
.frame(maxHeight: .infinity)
} }
@State private var detailsSize = CGSize.zero @State private var detailsSize = CGSize.zero

View File

@ -13,7 +13,7 @@ struct VideoDetailsToolbar: View {
@State private var startedToolPosition: CGRect = .zero @State private var startedToolPosition: CGRect = .zero
@State private var opacity = 1.0 @State private var opacity = 1.0
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle @Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle
var body: some View { var body: some View {

View File

@ -57,13 +57,10 @@ struct VideoPlayerView: View {
@State internal var orientationNotification: Any? @State internal var orientationNotification: Any?
#endif #endif
@EnvironmentObject<PlayerModel> internal var player internal var player: PlayerModel! = PlayerModel.shared
#if os(macOS) #if os(macOS)
@EnvironmentObject<NavigationModel> internal var navigation @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<SearchModel> internal var search
#endif
#if os(tvOS)
@EnvironmentObject<ThumbnailsModel> private var thumbnails
#endif #endif
@Default(.horizontalPlayerGestureEnabled) var horizontalPlayerGestureEnabled @Default(.horizontalPlayerGestureEnabled) var horizontalPlayerGestureEnabled
@ -482,7 +479,7 @@ struct VideoPlayerView: View {
#if os(tvOS) #if os(tvOS)
var tvControls: some View { var tvControls: some View {
TVControls(player: player, thumbnails: thumbnails) TVControls()
} }
#endif #endif
} }

View File

@ -13,8 +13,7 @@ struct AddToPlaylistView: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var model = PlaylistsModel.shared
@EnvironmentObject<PlaylistsModel> private var model
var body: some View { var body: some View {
Group { Group {
@ -164,7 +163,7 @@ struct AddToPlaylistView: View {
Defaults[.lastUsedPlaylistID] = id Defaults[.lastUsedPlaylistID] = id
model.addVideo(playlistID: id, videoID: video.videoID, navigation: navigation) model.addVideo(playlistID: id, videoID: video.videoID)
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
} }

View File

@ -16,8 +16,8 @@ struct PlaylistFormView: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists @ObservedObject private var playlists = PlaylistsModel.shared
var editing: Bool { var editing: Bool {
playlist != nil playlist != nil

View File

@ -14,9 +14,9 @@ struct PlaylistsView: View {
@StateObject private var channelPlaylist = Store<ChannelPlaylist>() @StateObject private var channelPlaylist = Store<ChannelPlaylist>()
@StateObject private var userPlaylist = Store<Playlist>() @StateObject private var userPlaylist = Store<Playlist>()
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlayerModel> private var player private var player = PlayerModel.shared
@EnvironmentObject<PlaylistsModel> private var model @ObservedObject private var model = PlaylistsModel.shared
@Namespace private var focusNamespace @Namespace private var focusNamespace
@ -26,7 +26,7 @@ struct PlaylistsView: View {
if videos.isEmpty { if videos.isEmpty {
videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? [] videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? []
if !player.accounts.app.userPlaylistsEndpointIncludesVideos { if !accounts.app.userPlaylistsEndpointIncludesVideos {
var i = 0 var i = 0
for index in videos.indices { for index in videos.indices {
@ -44,9 +44,9 @@ struct PlaylistsView: View {
private var resource: Resource? { private var resource: Resource? {
guard let playlist = currentPlaylist else { return nil } guard let playlist = currentPlaylist else { return nil }
let resource = player.accounts.api.playlist(playlist.id) let resource = accounts.api.playlist(playlist.id)
if player.accounts.app.userPlaylistsUseChannelPlaylistEndpoint { if accounts.app.userPlaylistsUseChannelPlaylistEndpoint {
resource?.addObserver(channelPlaylist) resource?.addObserver(channelPlaylist)
} else { } else {
resource?.addObserver(userPlaylist) resource?.addObserver(userPlaylist)
@ -150,11 +150,9 @@ struct PlaylistsView: View {
#if os(tvOS) #if os(tvOS)
.fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { .fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) {
PlaylistFormView(playlist: $createdPlaylist) PlaylistFormView(playlist: $createdPlaylist)
.environmentObject(accounts)
} }
.fullScreenCover(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { .fullScreenCover(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) {
PlaylistFormView(playlist: $editedPlaylist) PlaylistFormView(playlist: $editedPlaylist)
.environmentObject(accounts)
} }
.focusScope(focusNamespace) .focusScope(focusNamespace)
#else #else
@ -162,14 +160,12 @@ struct PlaylistsView: View {
EmptyView() EmptyView()
.sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { .sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) {
PlaylistFormView(playlist: $createdPlaylist) PlaylistFormView(playlist: $createdPlaylist)
.environmentObject(accounts)
} }
) )
.background( .background(
EmptyView() EmptyView()
.sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { .sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) {
PlaylistFormView(playlist: $editedPlaylist) PlaylistFormView(playlist: $editedPlaylist)
.environmentObject(accounts)
} }
) )
#endif #endif

View File

@ -4,9 +4,8 @@ import SwiftUI
struct SearchTextField: View { struct SearchTextField: View {
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<NavigationModel> private var navigation private var navigation = NavigationModel.shared
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var state = SearchModel.shared
@EnvironmentObject<SearchModel> private var state
@Binding var favoriteItem: FavoriteItem? @Binding var favoriteItem: FavoriteItem?
@ -34,7 +33,7 @@ struct SearchTextField: View {
query.query = state.queryText query.query = state.queryText
navigation.hideKeyboard() navigation.hideKeyboard()
} }
recents.addQuery(state.queryText, navigation: navigation) RecentsModel.shared.addQuery(state.queryText)
} }
.disableAutocorrection(true) .disableAutocorrection(true)
#if os(macOS) #if os(macOS)

View File

@ -1,9 +1,7 @@
import SwiftUI import SwiftUI
struct SearchSuggestions: View { struct SearchSuggestions: View {
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var state = SearchModel.shared
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var state
var body: some View { var body: some View {
List { List {
@ -80,10 +78,10 @@ struct SearchSuggestions: View {
state.changeQuery { query in state.changeQuery { query in
query.query = queryText query.query = queryText
navigation.hideKeyboard() NavigationModel.shared.hideKeyboard()
} }
recents.addQuery(queryText, navigation: navigation) RecentsModel.shared.addQuery(queryText)
} }
private var visibleSuggestions: [String] { private var visibleSuggestions: [String] {

View File

@ -14,17 +14,15 @@ struct SearchView: View {
#if os(tvOS) #if os(tvOS)
@State private var searchDebounce = Debounce() @State private var searchDebounce = Debounce()
@State private var recentsDebounce = Debounce() @State private var recentsDebounce = Debounce()
private var recents = RecentsModel.shared
#endif #endif
@State private var favoriteItem: FavoriteItem? @State private var favoriteItem: FavoriteItem?
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var state = SearchModel.shared
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var state
private var favorites = FavoritesModel.shared private var favorites = FavoritesModel.shared
@Default(.recentlyOpened) private var recentlyOpened @Default(.recentlyOpened) private var recentlyOpened
@ -313,20 +311,17 @@ struct SearchView: View {
case .query: case .query:
state.queryText = item.title state.queryText = item.title
state.changeQuery { query in query.query = item.title } state.changeQuery { query in query.query = item.title }
navigation.hideKeyboard() NavigationModel.shared.hideKeyboard()
updateFavoriteItem() updateFavoriteItem()
recents.add(item) RecentsModel.shared.add(item)
case .channel: case .channel:
guard let channel = item.channel else { guard let channel = item.channel else {
return return
} }
NavigationModel.openChannel( NavigationModel.shared.openChannel(
channel, channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
case .playlist: case .playlist:
@ -334,11 +329,8 @@ struct SearchView: View {
return return
} }
NavigationModel.openChannelPlaylist( NavigationModel.shared.openChannelPlaylist(
playlist, playlist,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} }
@ -359,7 +351,7 @@ struct SearchView: View {
private func removeButton(_ item: RecentItem) -> some View { private func removeButton(_ item: RecentItem) -> some View {
Button { Button {
recents.close(item) RecentsModel.shared.close(item)
recentsChanged.toggle() recentsChanged.toggle()
} label: { } label: {
Label("Remove", systemImage: "trash") Label("Remove", systemImage: "trash")
@ -368,12 +360,12 @@ struct SearchView: View {
private var clearHistoryButton: some View { private var clearHistoryButton: some View {
Button { Button {
navigation.presentAlert( NavigationModel.shared.presentAlert(
Alert( Alert(
title: Text("Are you sure you want to clear search history?"), title: Text("Are you sure you want to clear search history?"),
message: Text("This cannot be reverted"), message: Text("This cannot be reverted"),
primaryButton: .destructive(Text("Clear")) { primaryButton: .destructive(Text("Clear")) {
recents.clear() RecentsModel.shared.clear()
recentsChanged.toggle() recentsChanged.toggle()
}, },
secondaryButton: .cancel() secondaryButton: .cancel()

View File

@ -1,7 +1,7 @@
import SwiftUI import SwiftUI
struct AccountsNavigationLink: View { struct AccountsNavigationLink: View {
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
var instance: Instance var instance: Instance
var body: some View { var body: some View {
@ -30,6 +30,6 @@ struct AccountsNavigationLink: View {
if accounts.current?.instance == instance { if accounts.current?.instance == instance {
accounts.setCurrent(nil) accounts.setCurrent(nil)
} }
InstancesModel.remove(instance) InstancesModel.shared.remove(instance)
} }
} }

View File

@ -9,11 +9,6 @@ struct AdvancedSettings: View {
@Default(.countryOfPublicInstances) private var countryOfPublicInstances @Default(.countryOfPublicInstances) private var countryOfPublicInstances
@Default(.instances) private var instances @Default(.instances) private var instances
@EnvironmentObject<AccountsModel> private var accounts
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<SettingsModel> private var settings
@State private var countries = [String]() @State private var countries = [String]()
@State private var filesToShare = [MPVClient.logFile] @State private var filesToShare = [MPVClient.logFile]
@State private var presentingInstanceForm = false @State private var presentingInstanceForm = false
@ -39,7 +34,7 @@ struct AdvancedSettings: View {
#endif #endif
} }
.onChange(of: countryOfPublicInstances) { newCountry in .onChange(of: countryOfPublicInstances) { newCountry in
InstancesManifest.shared.setPublicAccount(newCountry, accounts: accounts, asCurrent: accounts.current?.isPublic ?? true) InstancesManifest.shared.setPublicAccount(newCountry, asCurrent: AccountsModel.shared.current?.isPublic ?? true)
} }
.sheet(isPresented: $presentingInstanceForm) { .sheet(isPresented: $presentingInstanceForm) {
InstanceForm(savedInstanceID: $savedFormInstanceID) InstanceForm(savedInstanceID: $savedFormInstanceID)

View File

@ -22,7 +22,7 @@ struct BrowsingSettings: View {
@Default(.homeHistoryItems) private var homeHistoryItems @Default(.homeHistoryItems) private var homeHistoryItems
@Default(.visibleSections) private var visibleSections @Default(.visibleSections) private var visibleSections
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@State private var homeHistoryItemsText = "" @State private var homeHistoryItemsText = ""
#if os(iOS) #if os(iOS)

View File

@ -2,8 +2,7 @@ import Defaults
import SwiftUI import SwiftUI
struct EditFavorites: View { struct EditFavorites: View {
@EnvironmentObject<PlaylistsModel> private var playlistsModel private var playlistsModel = PlaylistsModel.shared
private var model = FavoritesModel.shared private var model = FavoritesModel.shared
@Default(.favorites) private var favorites @Default(.favorites) private var favorites

View File

@ -4,8 +4,8 @@ import SwiftUI
struct HistorySettings: View { struct HistorySettings: View {
static let watchedThresholds = [50, 60, 70, 80, 90, 95, 100] static let watchedThresholds = [50, 60, 70, 80, 90, 95, 100]
@EnvironmentObject<PlayerModel> private var player private var player = PlayerModel.shared
@EnvironmentObject<SettingsModel> private var settings private var settings = SettingsModel.shared
@Default(.saveRecents) private var saveRecents @Default(.saveRecents) private var saveRecents
@Default(.saveLastPlayed) private var saveLastPlayed @Default(.saveLastPlayed) private var saveLastPlayed

View File

@ -16,7 +16,7 @@ struct InstanceForm: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
var body: some View { var body: some View {
VStack(alignment: .leading) { VStack(alignment: .leading) {
@ -140,7 +140,7 @@ struct InstanceForm: View {
return return
} }
let savedInstance = InstancesModel.add(app: app, name: name, url: url) let savedInstance = InstancesModel.shared.add(app: app, name: name, url: url)
savedInstanceID = savedInstance.id savedInstanceID = savedInstance.id
if accounts.isEmpty { if accounts.isEmpty {

View File

@ -13,7 +13,7 @@ struct InstanceSettings: View {
List { List {
Section(header: Text("Accounts".localized())) { Section(header: Text("Accounts".localized())) {
if instance.app.supportsAccounts { if instance.app.supportsAccounts {
ForEach(InstancesModel.accounts(instance.id), id: \.self) { account in ForEach(InstancesModel.shared.accounts(instance.id), id: \.self) { account in
#if os(tvOS) #if os(tvOS)
Button(account.description) {} Button(account.description) {}
.contextMenu { .contextMenu {
@ -70,7 +70,7 @@ struct InstanceSettings: View {
frontendURL = instance.frontendURL ?? "" frontendURL = instance.frontendURL ?? ""
} }
.onChange(of: frontendURL) { newValue in .onChange(of: frontendURL) { newValue in
InstancesModel.setFrontendURL(instance, newValue) InstancesModel.shared.setFrontendURL(instance, newValue)
} }
.labelsHidden() .labelsHidden()
.autocapitalization(.none) .autocapitalization(.none)
@ -84,7 +84,7 @@ struct InstanceSettings: View {
proxiesVideos = instance.proxiesVideos proxiesVideos = instance.proxiesVideos
} }
.onChange(of: proxiesVideos) { newValue in .onChange(of: proxiesVideos) { newValue in
InstancesModel.setProxiesVideos(instance, newValue) InstancesModel.shared.setProxiesVideos(instance, newValue)
} }
} }
} }

View File

@ -6,9 +6,8 @@ struct LocationsSettings: View {
@State private var presentingInstanceForm = false @State private var presentingInstanceForm = false
@State private var savedFormInstanceID: Instance.ID? @State private var savedFormInstanceID: Instance.ID?
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation private var model = SettingsModel.shared
@EnvironmentObject<SettingsModel> private var model
@Default(.countryOfPublicInstances) private var countryOfPublicInstances @Default(.countryOfPublicInstances) private var countryOfPublicInstances
@Default(.instances) private var instances @Default(.instances) private var instances
@ -30,7 +29,7 @@ struct LocationsSettings: View {
} }
.onAppear(perform: loadCountries) .onAppear(perform: loadCountries)
.onChange(of: countryOfPublicInstances) { newCountry in .onChange(of: countryOfPublicInstances) { newCountry in
InstancesManifest.shared.setPublicAccount(newCountry, accounts: accounts, asCurrent: accounts.current?.isPublic ?? true) InstancesManifest.shared.setPublicAccount(newCountry, asCurrent: accounts.current?.isPublic ?? true)
} }
.onChange(of: instancesManifest) { _ in .onChange(of: instancesManifest) { _ in
countryOfPublicInstances = nil countryOfPublicInstances = nil
@ -74,7 +73,7 @@ struct LocationsSettings: View {
.disabled(countries.isEmpty) .disabled(countries.isEmpty)
Button { Button {
InstancesManifest.shared.changePublicAccount(accounts, settings: model) InstancesManifest.shared.changePublicAccount()
} label: { } label: {
if let account = accounts.current, account.isPublic { if let account = accounts.current, account.isPublic {
Text("Switch to other public location") Text("Switch to other public location")
@ -89,7 +88,6 @@ struct LocationsSettings: View {
Section(header: SettingsHeader(text: "Custom Locations".localized())) { Section(header: SettingsHeader(text: "Custom Locations".localized())) {
#if os(macOS) #if os(macOS)
InstancesSettings() InstancesSettings()
.environmentObject(model)
#else #else
ForEach(instances) { instance in ForEach(instances) { instance in
AccountsNavigationLink(instance: instance) AccountsNavigationLink(instance: instance)
@ -137,8 +135,5 @@ struct LocationsSettings: View {
struct LocationsSettings_Previews: PreviewProvider { struct LocationsSettings_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
LocationsSettings() LocationsSettings()
.environmentObject(AccountsModel())
.environmentObject(NavigationModel())
.environmentObject(SettingsModel())
} }
} }

View File

@ -35,8 +35,8 @@ struct PlayerSettings: View {
@Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike
@Default(.systemControlsCommands) private var systemControlsCommands @Default(.systemControlsCommands) private var systemControlsCommands
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlayerModel> private var player private var player = PlayerModel.shared
#if os(iOS) #if os(iOS)
private var idiom: UIUserInterfaceIdiom { private var idiom: UIUserInterfaceIdiom {

View File

@ -5,7 +5,7 @@ struct QualitySettings: View {
@State private var presentingProfileForm = false @State private var presentingProfileForm = false
@State private var editedProfileID: QualityProfile.ID? @State private var editedProfileID: QualityProfile.ID?
@EnvironmentObject<SettingsModel> private var settings @ObservedObject private var settings = SettingsModel.shared
@Default(.qualityProfiles) private var qualityProfiles @Default(.qualityProfiles) private var qualityProfiles
@Default(.batteryCellularProfile) private var batteryCellularProfile @Default(.batteryCellularProfile) private var batteryCellularProfile

View File

@ -19,15 +19,13 @@ struct SettingsView: View {
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
#endif #endif
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var model = SettingsModel.shared
@EnvironmentObject<SettingsModel> private var model
@Default(.instances) private var instances @Default(.instances) private var instances
var body: some View { var body: some View {
settings settings
.environmentObject(model)
.alert(isPresented: $model.presentingAlert) { model.alert } .alert(isPresented: $model.presentingAlert) { model.alert }
} }

View File

@ -13,7 +13,7 @@ struct TrendingView: View {
@State private var favoriteItem: FavoriteItem? @State private var favoriteItem: FavoriteItem?
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
var trending: [ContentItem] { var trending: [ContentItem] {
ContentItem.array(of: store.collection) ContentItem.array(of: store.collection)

View File

@ -14,9 +14,7 @@ struct VideoCell: View {
@Environment(\.verticalSizeClass) private var verticalSizeClass @Environment(\.verticalSizeClass) private var verticalSizeClass
#endif #endif
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject var thumbnails = ThumbnailsModel.shared
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<ThumbnailsModel> private var thumbnails
@Default(.channelOnThumbnail) private var channelOnThumbnail @Default(.channelOnThumbnail) private var channelOnThumbnail
@Default(.timeOnThumbnail) private var timeOnThumbnail @Default(.timeOnThumbnail) private var timeOnThumbnail
@ -53,7 +51,6 @@ struct VideoCell: View {
.contentShape(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius)) .contentShape(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius))
.contextMenu { .contextMenu {
VideoContextMenuView(video: video) VideoContextMenuView(video: video)
.environmentObject(accounts)
} }
} }
@ -310,11 +307,8 @@ struct VideoCell: View {
return return
} }
NavigationModel.openChannel( NavigationModel.shared.openChannel(
video.channel, video.channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} label: { } label: {

View File

@ -7,17 +7,10 @@ struct ChannelCell: View {
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
var body: some View { var body: some View {
Button { Button {
NavigationModel.openChannel( NavigationModel.shared.openChannel(
channel, channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} label: { } label: {

View File

@ -6,13 +6,12 @@ struct ChannelPlaylistCell: View {
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<NavigationModel> private var navigation var navigation = NavigationModel.shared
@EnvironmentObject<RecentsModel> private var recents
var body: some View { var body: some View {
Button { Button {
let recent = RecentItem(from: playlist) let recent = RecentItem(from: playlist)
recents.add(recent) RecentsModel.shared.add(recent)
navigation.presentingPlaylist = true navigation.presentingPlaylist = true
if navigationStyle == .sidebar { if navigationStyle == .sidebar {

View File

@ -12,10 +12,9 @@ struct ChannelPlaylistView: View {
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation var player = PlayerModel.shared
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var recents = RecentsModel.shared
@EnvironmentObject<RecentsModel> private var recents
private var items: [ContentItem] { private var items: [ContentItem] {
ContentItem.array(of: store.item?.videos ?? []) ContentItem.array(of: store.item?.videos ?? [])
@ -89,7 +88,7 @@ struct ChannelPlaylistView: View {
if navigationStyle == .tab { if navigationStyle == .tab {
Button("Done") { Button("Done") {
withAnimation(Constants.overlayAnimation) { withAnimation(Constants.overlayAnimation) {
navigation.presentingPlaylist = false NavigationModel.shared.presentingPlaylist = false
} }
} }
} }

View File

@ -15,14 +15,12 @@ struct ChannelVideosView: View {
#if os(iOS) #if os(iOS)
@Environment(\.horizontalSizeClass) private var horizontalSizeClass @Environment(\.horizontalSizeClass) private var horizontalSizeClass
@EnvironmentObject<PlayerModel> private var player
#endif #endif
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var recents = RecentsModel.shared
@EnvironmentObject<SubscriptionsModel> private var subscriptions @ObservedObject private var subscriptions = SubscriptionsModel.shared
@Namespace private var focusNamespace @Namespace private var focusNamespace
var presentedChannel: Channel? { var presentedChannel: Channel? {

View File

@ -10,12 +10,11 @@ struct ControlsBar: View {
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation var navigation = NavigationModel.shared
@EnvironmentObject<PlayerModel> private var model @ObservedObject private var model = PlayerModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists @ObservedObject private var playlists = PlaylistsModel.shared
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@ObservedObject private var controls = PlayerControlsModel.shared @ObservedObject private var controls = PlayerControlsModel.shared
@ -139,11 +138,8 @@ struct ControlsBar: View {
HStack(spacing: 8) { HStack(spacing: 8) {
Button { Button {
if let video = model.currentVideo, !video.isLocal { if let video = model.currentVideo, !video.isLocal {
NavigationModel.openChannel( navigation.openChannel(
video.channel, video.channel,
player: model,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} }
@ -179,7 +175,7 @@ struct ControlsBar: View {
if let playlist = playlists.lastUsed, let video = model.currentVideo { if let playlist = playlists.lastUsed, let video = model.currentVideo {
Button { Button {
playlists.addVideo(playlistID: playlist.id, videoID: video.videoID, navigation: navigation) playlists.addVideo(playlistID: playlist.id, videoID: video.videoID)
} label: { } label: {
Label("Add to \(playlist.title)", systemImage: "text.badge.star") Label("Add to \(playlist.title)", systemImage: "text.badge.star")
} }
@ -194,11 +190,8 @@ struct ControlsBar: View {
Section { Section {
if !video.isLocal { if !video.isLocal {
Button { Button {
NavigationModel.openChannel( navigation.openChannel(
video.channel, video.channel,
player: model,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} label: { } label: {

View File

@ -2,10 +2,8 @@ import SwiftUI
#if !os(macOS) #if !os(macOS)
struct MPVPlayerView: UIViewControllerRepresentable { struct MPVPlayerView: UIViewControllerRepresentable {
@EnvironmentObject<PlayerModel> private var player
func makeUIViewController(context _: Context) -> some UIViewController { func makeUIViewController(context _: Context) -> some UIViewController {
player.mpvController PlayerModel.shared.mpvController
} }
func updateUIViewController(_: UIViewControllerType, context _: Context) {} func updateUIViewController(_: UIViewControllerType, context _: Context) {}
@ -15,10 +13,8 @@ import SwiftUI
@State private var client = MPVClient() @State private var client = MPVClient()
@State private var layer = VideoLayer() @State private var layer = VideoLayer()
@EnvironmentObject<PlayerModel> private var player
func makeNSView(context _: Context) -> some NSView { func makeNSView(context _: Context) -> some NSView {
player.mpvBackend.client = client PlayerModel.shared.mpvBackend.client = client
let view = MPVOGLView() let view = MPVOGLView()

View File

@ -4,7 +4,7 @@ struct OpenSettingsButton: View {
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
#if !os(macOS) #if !os(macOS)
@EnvironmentObject<NavigationModel> private var navigation private var navigation: NavigationModel { .shared }
#endif #endif
var body: some View { var body: some View {

View File

@ -6,11 +6,7 @@ struct OpenVideosView: View {
@State private var playbackMode = OpenVideosModel.PlaybackMode.playNow @State private var playbackMode = OpenVideosModel.PlaybackMode.playNow
@State private var removeQueueItems = false @State private var removeQueueItems = false
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<NavigationModel> private var navigation
@EnvironmentObject<PlayerModel> private var player
@EnvironmentObject<RecentsModel> private var recents
@EnvironmentObject<SearchModel> private var search
@Environment(\.openURL) private var openURL @Environment(\.openURL) private var openURL
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@ -132,8 +128,8 @@ struct OpenVideosView: View {
openURLs(urlsToOpen) openURLs(urlsToOpen)
} catch { } catch {
NavigationModel.shared.alert = Alert(title: Text("Could not open Files")) navigation.alert = Alert(title: Text("Could not open Files"))
NavigationModel.shared.presentingAlertInOpenVideos = true navigation.presentingAlertInOpenVideos = true
} }
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()

View File

@ -4,8 +4,9 @@ import SwiftUI
struct PlaylistVideosView: View { struct PlaylistVideosView: View {
let playlist: Playlist let playlist: Playlist
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<PlaylistsModel> private var model var player = PlayerModel.shared
@ObservedObject private var model = PlaylistsModel.shared
@StateObject private var channelPlaylist = Store<ChannelPlaylist>() @StateObject private var channelPlaylist = Store<ChannelPlaylist>()
@StateObject private var userPlaylist = Store<Playlist>() @StateObject private var userPlaylist = Store<Playlist>()
@ -15,7 +16,7 @@ struct PlaylistVideosView: View {
if videos.isEmpty { if videos.isEmpty {
videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? [] videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? []
if !player.accounts.app.userPlaylistsEndpointIncludesVideos { if !accounts.app.userPlaylistsEndpointIncludesVideos {
var i = 0 var i = 0
for index in videos.indices { for index in videos.indices {
@ -31,9 +32,9 @@ struct PlaylistVideosView: View {
} }
private var resource: Resource? { private var resource: Resource? {
let resource = player.accounts.api.playlist(playlist.id) let resource = accounts.api.playlist(playlist.id)
if player.accounts.app.userPlaylistsUseChannelPlaylistEndpoint { if accounts.app.userPlaylistsUseChannelPlaylistEndpoint {
resource?.addObserver(channelPlaylist) resource?.addObserver(channelPlaylist)
} else { } else {
resource?.addObserver(userPlaylist) resource?.addObserver(userPlaylist)

View File

@ -4,7 +4,7 @@ import SwiftUI
struct PopularView: View { struct PopularView: View {
@StateObject private var store = Store<[Video]>() @StateObject private var store = Store<[Video]>()
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
var resource: Resource? { var resource: Resource? {
accounts.api.popular accounts.api.popular

View File

@ -3,9 +3,9 @@ import SwiftUI
struct ShareButton<LabelView: View>: View { struct ShareButton<LabelView: View>: View {
let contentItem: ContentItem let contentItem: ContentItem
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation private var navigation: NavigationModel { .shared }
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
let label: LabelView? let label: LabelView?

View File

@ -5,7 +5,7 @@ struct SignInRequiredView<Content: View>: View {
let title: String let title: String
let content: Content let content: Content
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@Default(.instances) private var instances @Default(.instances) private var instances

View File

@ -4,7 +4,7 @@ import SwiftUI
struct SubscriptionsView: View { struct SubscriptionsView: View {
@StateObject private var store = Store<[Video]>() @StateObject private var store = Store<[Video]>()
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
var feed: Resource? { var feed: Resource? {
accounts.api.feed accounts.api.feed

View File

@ -11,12 +11,11 @@ struct VideoContextMenuView: View {
@Environment(\.navigationStyle) private var navigationStyle @Environment(\.navigationStyle) private var navigationStyle
@Environment(\.currentPlaylistID) private var playlistID @Environment(\.currentPlaylistID) private var playlistID
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<NavigationModel> private var navigation @ObservedObject private var navigation = NavigationModel.shared
@EnvironmentObject<PlayerModel> private var player @ObservedObject private var player = PlayerModel.shared
@EnvironmentObject<PlaylistsModel> private var playlists @ObservedObject private var playlists = PlaylistsModel.shared
@EnvironmentObject<RecentsModel> private var recents @ObservedObject private var subscriptions = SubscriptionsModel.shared
@EnvironmentObject<SubscriptionsModel> private var subscriptions
@FetchRequest private var watchRequest: FetchedResults<Watch> @FetchRequest private var watchRequest: FetchedResults<Watch>
@ -261,11 +260,8 @@ struct VideoContextMenuView: View {
private var openChannelButton: some View { private var openChannelButton: some View {
Button { Button {
NavigationModel.openChannel( NavigationModel.shared.openChannel(
video.channel, video.channel,
player: player,
recents: recents,
navigation: navigation,
navigationStyle: navigationStyle navigationStyle: navigationStyle
) )
} label: { } label: {
@ -308,7 +304,7 @@ struct VideoContextMenuView: View {
@ViewBuilder private var addToLastPlaylistButton: some View { @ViewBuilder private var addToLastPlaylistButton: some View {
if let playlist = playlists.lastUsed { if let playlist = playlists.lastUsed {
Button { Button {
playlists.addVideo(playlistID: playlist.id, videoID: video.videoID, navigation: navigation) playlists.addVideo(playlistID: playlist.id, videoID: video.videoID)
} label: { } label: {
Label("Add to \(playlist.title)", systemImage: "text.badge.star") Label("Add to \(playlist.title)", systemImage: "text.badge.star")
} }

View File

@ -5,7 +5,6 @@ import SwiftUI
struct WelcomeScreen: View { struct WelcomeScreen: View {
@Environment(\.presentationMode) private var presentationMode @Environment(\.presentationMode) private var presentationMode
@EnvironmentObject<AccountsModel> private var accounts
@State private var store = [ManifestedInstance]() @State private var store = [ManifestedInstance]()
var body: some View { var body: some View {
@ -25,7 +24,7 @@ struct WelcomeScreen: View {
ForEach(countries, id: \.self) { country in ForEach(countries, id: \.self) { country in
Button { Button {
Defaults[.countryOfPublicInstances] = country Defaults[.countryOfPublicInstances] = country
InstancesManifest.shared.setPublicAccount(country, accounts: accounts) InstancesManifest.shared.setPublicAccount(country)
presentationMode.wrappedValue.dismiss() presentationMode.wrappedValue.dismiss()
} label: { } label: {

View File

@ -32,19 +32,16 @@ struct YatteeApp: App {
@State private var configured = false @State private var configured = false
@StateObject private var accounts = AccountsModel() @StateObject private var comments = CommentsModel.shared
@StateObject private var comments = CommentsModel() @StateObject private var instances = InstancesModel.shared
@StateObject private var instances = InstancesModel() @StateObject private var menu = MenuModel.shared
@StateObject private var menu = MenuModel() @StateObject private var networkState = NetworkStateModel.shared
@StateObject private var navigation = NavigationModel() @StateObject private var player = PlayerModel.shared
@StateObject private var networkState = NetworkStateModel() @StateObject private var playlists = PlaylistsModel.shared
@StateObject private var player = PlayerModel() @StateObject private var recents = RecentsModel.shared
@StateObject private var playlists = PlaylistsModel() @StateObject private var settings = SettingsModel.shared
@StateObject private var recents = RecentsModel() @StateObject private var subscriptions = SubscriptionsModel.shared
@StateObject private var search = SearchModel() @StateObject private var thumbnails = ThumbnailsModel.shared
@StateObject private var settings = SettingsModel()
@StateObject private var subscriptions = SubscriptionsModel()
@StateObject private var thumbnails = ThumbnailsModel()
let persistenceController = PersistenceController.shared let persistenceController = PersistenceController.shared
@ -55,20 +52,6 @@ struct YatteeApp: App {
ContentView() ContentView()
.onAppear(perform: configure) .onAppear(perform: configure)
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(networkState)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(settings)
.environmentObject(subscriptions)
.environmentObject(thumbnails)
.environmentObject(menu)
.environmentObject(search)
#if os(macOS) #if os(macOS)
.background( .background(
HostingWindowFinder { window in HostingWindowFinder { window in
@ -100,7 +83,7 @@ struct YatteeApp: App {
CommandGroup(replacing: .newItem, addition: {}) CommandGroup(replacing: .newItem, addition: {})
MenuCommands(model: Binding<MenuModel>(get: { menu }, set: { _ in })) MenuCommands(model: Binding<MenuModel>(get: { MenuModel.shared }, set: { _ in }))
} }
#endif #endif
@ -127,18 +110,7 @@ struct YatteeApp: App {
.onDisappear { player.presentingPlayer = false } .onDisappear { player.presentingPlayer = false }
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environment(\.navigationStyle, .sidebar) .environment(\.navigationStyle, .sidebar)
.environmentObject(accounts)
.environmentObject(comments)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(networkState)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(playlists)
.environmentObject(recents)
.environmentObject(search)
.environmentObject(subscriptions)
.environmentObject(thumbnails)
.handlesExternalEvents(preferring: Set(["player", "*"]), allowing: Set(["player", "*"])) .handlesExternalEvents(preferring: Set(["player", "*"]), allowing: Set(["player", "*"]))
} }
.handlesExternalEvents(matching: Set(["player", "*"])) .handlesExternalEvents(matching: Set(["player", "*"]))
@ -146,12 +118,6 @@ struct YatteeApp: App {
Settings { Settings {
SettingsView() SettingsView()
.environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.managedObjectContext, persistenceController.container.viewContext)
.environmentObject(accounts)
.environmentObject(instances)
.environmentObject(navigation)
.environmentObject(player)
.environmentObject(playerControls)
.environmentObject(settings)
} }
#endif #endif
} }
@ -171,39 +137,14 @@ struct YatteeApp: App {
migrateAccounts() migrateAccounts()
if !Defaults[.lastAccountIsPublic] { if !Defaults[.lastAccountIsPublic] {
accounts.configureAccount() AccountsModel.shared.configureAccount()
} }
if let countryOfPublicInstances = Defaults[.countryOfPublicInstances] { if let countryOfPublicInstances = Defaults[.countryOfPublicInstances] {
InstancesManifest.shared.setPublicAccount(countryOfPublicInstances, accounts: accounts, asCurrent: accounts.current.isNil) InstancesManifest.shared.setPublicAccount(countryOfPublicInstances, asCurrent: AccountsModel.shared.current.isNil)
} }
playlists.accounts = accounts if !AccountsModel.shared.current.isNil {
search.accounts = accounts
subscriptions.accounts = accounts
comments.player = player
menu.accounts = accounts
menu.navigation = navigation
menu.player = player
player.accounts = accounts
player.comments = comments
player.navigation = navigation
PlayerModel.shared = player
PlayerTimeModel.shared.player = player
#if !os(tvOS)
OpenURLHandler.shared.accounts = accounts
OpenURLHandler.shared.navigation = navigation
OpenURLHandler.shared.recents = recents
OpenURLHandler.shared.player = player
OpenURLHandler.shared.search = search
#endif
if !accounts.current.isNil {
player.restoreQueue() player.restoreQueue()
} }
@ -219,9 +160,7 @@ struct YatteeApp: App {
} }
#endif #endif
navigation.tabSelection = section ?? .search NavigationModel.shared.tabSelection = section ?? .search
NavigationModel.shared = navigation
subscriptions.load() subscriptions.load()
playlists.load() playlists.load()

View File

@ -2,14 +2,8 @@ import Defaults
import SwiftUI import SwiftUI
struct AppleAVPlayerView: NSViewRepresentable { struct AppleAVPlayerView: NSViewRepresentable {
@EnvironmentObject<PlayerModel> private var player
func makeNSView(context _: Context) -> some NSView { func makeNSView(context _: Context) -> some NSView {
let playerLayerView = PlayerLayerView(frame: .zero) PlayerLayerView(frame: .zero)
playerLayerView.player = player
return playerLayerView
} }
func updateNSView(_: NSViewType, context _: Context) {} func updateNSView(_: NSViewType, context _: Context) {}

View File

@ -13,8 +13,8 @@ struct InstancesSettings: View {
@State private var proxiesVideos = false @State private var proxiesVideos = false
@Environment(\.colorScheme) private var colorScheme @Environment(\.colorScheme) private var colorScheme
@EnvironmentObject<AccountsModel> private var accounts @ObservedObject private var accounts = AccountsModel.shared
@EnvironmentObject<SettingsModel> private var settings private var settings = SettingsModel.shared
@Default(.instances) private var instances @Default(.instances) private var instances
@ -86,7 +86,7 @@ struct InstancesSettings: View {
frontendURL = selectedInstanceFrontendURL frontendURL = selectedInstanceFrontendURL
} }
.onChange(of: frontendURL) { newValue in .onChange(of: frontendURL) { newValue in
InstancesModel.setFrontendURL(selectedInstance, newValue) InstancesModel.shared.setFrontendURL(selectedInstance, newValue)
} }
.labelsHidden() .labelsHidden()
@ -101,7 +101,7 @@ struct InstancesSettings: View {
proxiesVideos = selectedInstance.proxiesVideos proxiesVideos = selectedInstance.proxiesVideos
} }
.onChange(of: proxiesVideos) { newValue in .onChange(of: proxiesVideos) { newValue in
InstancesModel.setProxiesVideos(selectedInstance, newValue) InstancesModel.shared.setProxiesVideos(selectedInstance, newValue)
} }
} }
@ -134,7 +134,7 @@ struct InstancesSettings: View {
accounts.setCurrent(nil) accounts.setCurrent(nil)
} }
InstancesModel.remove(selectedInstance!) InstancesModel.shared.remove(selectedInstance!)
selectedInstanceID = instances.last?.id selectedInstanceID = instances.last?.id
}, },
secondaryButton: .cancel() secondaryButton: .cancel()
@ -170,7 +170,7 @@ struct InstancesSettings: View {
} }
var selectedInstance: Instance! { var selectedInstance: Instance! {
InstancesModel.find(selectedInstanceID) InstancesModel.shared.find(selectedInstanceID)
} }
var selectedInstanceFrontendURL: String { var selectedInstanceFrontendURL: String {
@ -182,7 +182,7 @@ struct InstancesSettings: View {
return [] return []
} }
return InstancesModel.accounts(selectedInstanceID) return InstancesModel.shared.accounts(selectedInstanceID)
} }
private var proxiesVideosToggle: some View { private var proxiesVideosToggle: some View {

View File

@ -5,7 +5,7 @@ import SwiftUI
struct AccountSelectionView: View { struct AccountSelectionView: View {
var showHeader = true var showHeader = true
@EnvironmentObject<AccountsModel> private var accountsModel @ObservedObject private var accountsModel = AccountsModel.shared
@Default(.accounts) private var accounts @Default(.accounts) private var accounts
@Default(.instances) private var instances @Default(.instances) private var instances

Some files were not shown because too many files have changed in this diff Show More