diff --git a/Fixtures/View+Fixtures.swift b/Fixtures/View+Fixtures.swift index def85ef9..81d0837d 100644 --- a/Fixtures/View+Fixtures.swift +++ b/Fixtures/View+Fixtures.swift @@ -5,8 +5,6 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier { func body(content: Content) -> some View { content .environmentObject(AccountsModel()) - .environmentObject(comments) - .environmentObject(InstancesModel()) .environmentObject(InstancesManifest()) .environmentObject(invidious) .environmentObject(NavigationModel()) @@ -17,20 +15,11 @@ struct FixtureEnvironmentObjectsModifier: ViewModifier { .environmentObject(PlayerTimeModel()) .environmentObject(PlaylistsModel()) .environmentObject(RecentsModel()) - .environmentObject(SearchModel()) .environmentObject(SettingsModel()) .environmentObject(subscriptions) .environmentObject(ThumbnailsModel()) } - private var comments: CommentsModel { - let comments = CommentsModel() - comments.loaded = true - comments.all = [.fixture] - - return comments - } - private var invidious: InvidiousAPI { let api = InvidiousAPI() diff --git a/Model/Accounts/AccountsModel.swift b/Model/Accounts/AccountsModel.swift index d6b3b9a2..55101765 100644 --- a/Model/Accounts/AccountsModel.swift +++ b/Model/Accounts/AccountsModel.swift @@ -3,6 +3,8 @@ import Defaults import Foundation final class AccountsModel: ObservableObject { + static let shared = AccountsModel() + @Published private(set) var current: Account! @Published private var invidious = InvidiousAPI() @@ -61,8 +63,8 @@ final class AccountsModel: ObservableObject { func configureAccount() { if let account = lastUsed ?? - InstancesModel.lastUsed?.anonymousAccount ?? - InstancesModel.all.first?.anonymousAccount + InstancesModel.shared.lastUsed?.anonymousAccount ?? + InstancesModel.shared.all.first?.anonymousAccount { setCurrent(account) } diff --git a/Model/Accounts/InstancesModel.swift b/Model/Accounts/InstancesModel.swift index 722d99e4..542a43b0 100644 --- a/Model/Accounts/InstancesModel.swift +++ b/Model/Accounts/InstancesModel.swift @@ -2,27 +2,29 @@ import Defaults import Foundation final class InstancesModel: ObservableObject { - static var all: [Instance] { + static var shared = InstancesModel() + + var all: [Instance] { Defaults[.instances] } - static var forPlayer: Instance? { + var forPlayer: Instance? { guard let id = Defaults[.playerInstanceID] else { return nil } - return InstancesModel.find(id) + return InstancesModel.shared.find(id) } - static var lastUsed: Instance? { + var lastUsed: Instance? { guard let id = Defaults[.lastInstanceID] else { 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 { return nil } @@ -30,11 +32,11 @@ final class InstancesModel: ObservableObject { 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 } } - static func add(app: VideosApp, name: String, url: String) -> Instance { + func add(app: VideosApp, name: String, url: String) -> Instance { let instance = Instance( app: app, id: UUID().uuidString, name: name, apiURL: standardizedURL(url) ) @@ -43,7 +45,7 @@ final class InstancesModel: ObservableObject { 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 }) { var instance = Defaults[.instances][index] 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 { return } @@ -63,15 +65,15 @@ final class InstancesModel: ObservableObject { Defaults[.instances][index] = instance } - static func remove(_ instance: Instance) { - let accounts = Self.accounts(instance.id) + func remove(_ instance: Instance) { + let accounts = accounts(instance.id) if let index = Defaults[.instances].firstIndex(where: { $0.id == instance.id }) { Defaults[.instances].remove(at: index) accounts.forEach { AccountsModel.remove($0) } } } - static func standardizedURL(_ url: String) -> String { + func standardizedURL(_ url: String) -> String { if url.count > 7, url.last == "/" { return String(url.dropLast()) } else { diff --git a/Model/CommentsModel.swift b/Model/CommentsModel.swift index 6f544eb7..39e62f77 100644 --- a/Model/CommentsModel.swift +++ b/Model/CommentsModel.swift @@ -3,6 +3,8 @@ import Foundation import SwiftyJSON final class CommentsModel: ObservableObject { + static let shared = CommentsModel() + @Published var all = [Comment]() @Published var nextPage: String? @@ -15,10 +17,11 @@ final class CommentsModel: ObservableObject { @Published var repliesPageID: String? @Published var repliesLoaded = false - var player: PlayerModel! + var player = PlayerModel.shared + var accounts = AccountsModel.shared var instance: Instance? { - player.accounts.current?.instance + accounts.current?.instance } var nextPageAvailable: Bool { @@ -80,7 +83,7 @@ final class CommentsModel: ObservableObject { repliesPageID = page repliesLoaded = false - player.accounts.api.comments(player.currentVideo!.videoID, page: page)? + accounts.api.comments(player.currentVideo!.videoID, page: page)? .load() .onSuccess { [weak self] response in if let page: CommentsPage = response.typedContent() { diff --git a/Model/InstancesManifest.swift b/Model/InstancesManifest.swift index 45f0bd85..a1717816 100644 --- a/Model/InstancesManifest.swift +++ b/Model/InstancesManifest.swift @@ -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 { - accounts.publicAccount = nil + AccountsModel.shared.publicAccount = nil if asCurrent { - accounts.configureAccount() + AccountsModel.shared.configureAccount() } return } @@ -47,42 +47,42 @@ final class InstancesManifest: Service, ObservableObject { if let instances: [ManifestedInstance] = response.typedContent() { guard let instance = instances.filter { $0.country == country }.randomElement() else { return } let account = instance.anonymousAccount - accounts.publicAccount = account + AccountsModel.shared.publicAccount = account if asCurrent { - accounts.setCurrent(account) + AccountsModel.shared.setCurrent(account) } } } } - func changePublicAccount(_ accounts: AccountsModel, settings: SettingsModel) { + func changePublicAccount() { instancesList?.load().onSuccess { response in if let instances: [ManifestedInstance] = response.typedContent() { var countryInstances = instances.filter { $0.country == Defaults[.countryOfPublicInstances] } let region = countryInstances.first?.region ?? "Europe" 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 } regionInstances = regionInstances.filter { $0.url.absoluteString != publicAccountUrl } } var instance: ManifestedInstance? - if accounts.current?.isPublic ?? false { + if AccountsModel.shared.current?.isPublic ?? false { instance = regionInstances.randomElement() } else { instance = countryInstances.randomElement() ?? regionInstances.randomElement() } 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 } let account = instance.anonymousAccount - accounts.publicAccount = account - accounts.setCurrent(account) + AccountsModel.shared.publicAccount = account + AccountsModel.shared.setCurrent(account) } } } diff --git a/Model/MenuModel.swift b/Model/MenuModel.swift index 2945d24c..42c5daf3 100644 --- a/Model/MenuModel.swift +++ b/Model/MenuModel.swift @@ -2,12 +2,15 @@ import Combine import Foundation final class MenuModel: ObservableObject { - @Published var accounts: AccountsModel? { didSet { registerChildModel(accounts) } } - @Published var navigation: NavigationModel? { didSet { registerChildModel(navigation) } } - @Published var player: PlayerModel? { didSet { registerChildModel(player) } } - + static let shared = MenuModel() private var cancellables = [AnyCancellable]() + init() { + registerChildModel(AccountsModel.shared) + registerChildModel(NavigationModel.shared) + registerChildModel(PlayerModel.shared) + } + func registerChildModel(_ model: T?) { guard !model.isNil else { return diff --git a/Model/NavigationModel.swift b/Model/NavigationModel.swift index f7980563..f57244d0 100644 --- a/Model/NavigationModel.swift +++ b/Model/NavigationModel.swift @@ -2,7 +2,11 @@ import Foundation import SwiftUI 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 { case home @@ -89,21 +93,15 @@ final class NavigationModel: ObservableObject { @Published var presentingFileImporter = false - static func openChannel( - _ channel: Channel, - player: PlayerModel, - recents: RecentsModel, - navigation: NavigationModel, - navigationStyle: NavigationStyle - ) { + func openChannel(_ channel: Channel, navigationStyle: NavigationStyle) { guard channel.id != Video.fixtureChannelID else { return } - navigation.hideKeyboard() + hideKeyboard() let presentingPlayer = player.presentingPlayer player.hide() - navigation.presentingChannel = false + presentingChannel = false #if os(macOS) Windows.main.open() @@ -113,8 +111,8 @@ final class NavigationModel: ObservableObject { recents.add(RecentItem(from: channel)) if navigationStyle == .sidebar { - navigation.sidebarSectionChanged.toggle() - navigation.tabSelection = .recentlyOpened(recent.tag) + sidebarSectionChanged.toggle() + tabSelection = .recentlyOpened(recent.tag) } else { var delay = 0.0 #if os(iOS) @@ -122,21 +120,15 @@ final class NavigationModel: ObservableObject { #endif DispatchQueue.main.asyncAfter(deadline: .now() + delay) { withAnimation(Constants.overlayAnimation) { - navigation.presentingChannel = true + self.presentingChannel = true } } } } - static func openChannelPlaylist( - _ playlist: ChannelPlaylist, - player: PlayerModel, - recents: RecentsModel, - navigation: NavigationModel, - navigationStyle: NavigationStyle - ) { - navigation.presentingChannel = false - navigation.presentingPlaylist = false + func openChannelPlaylist(_ playlist: ChannelPlaylist, navigationStyle: NavigationStyle) { + presentingChannel = false + presentingPlaylist = false let recent = RecentItem(from: playlist) #if os(macOS) @@ -145,16 +137,17 @@ final class NavigationModel: ObservableObject { player.hide() #endif - navigation.hideKeyboard() + hideKeyboard() let presentingPlayer = player.presentingPlayer player.hide() - DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - recents.add(recent) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in + guard let self else { return } + self.recents.add(recent) if navigationStyle == .sidebar { - navigation.sidebarSectionChanged.toggle() - navigation.tabSelection = .recentlyOpened(recent.tag) + self.sidebarSectionChanged.toggle() + self.tabSelection = .recentlyOpened(recent.tag) } else { var delay = 0.0 #if os(iOS) @@ -162,25 +155,19 @@ final class NavigationModel: ObservableObject { #endif DispatchQueue.main.asyncAfter(deadline: .now() + delay) { withAnimation(Constants.overlayAnimation) { - navigation.presentingPlaylist = true + self.presentingPlaylist = true } } } } } - static func openSearchQuery( - _ searchQuery: String?, - player: PlayerModel, - recents: RecentsModel, - navigation: NavigationModel, - search: SearchModel - ) { - navigation.presentingChannel = false - navigation.presentingPlaylist = false - navigation.tabSelection = .search + func openSearchQuery(_ searchQuery: String?) { + presentingChannel = false + presentingPlaylist = false + tabSelection = .search - navigation.hideKeyboard() + hideKeyboard() let presentingPlayer = player.presentingPlayer player.hide() @@ -193,9 +180,10 @@ final class NavigationModel: ObservableObject { #if os(iOS) if presentingPlayer { delay = 1.0 } #endif - DispatchQueue.main.asyncAfter(deadline: .now() + delay) { - search.queryText = searchQuery - search.changeQuery { query in query.query = searchQuery } + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { [weak self] in + guard let self else { return } + self.search.queryText = searchQuery + self.search.changeQuery { query in query.query = searchQuery } } } diff --git a/Model/OpenVideosModel.swift b/Model/OpenVideosModel.swift index 9c136826..edc473b2 100644 --- a/Model/OpenVideosModel.swift +++ b/Model/OpenVideosModel.swift @@ -155,7 +155,7 @@ struct OpenVideosModel { } var canOpenVideosByID: Bool { - guard let app = player.accounts.current?.app else { return false } - return !player.accounts.isEmpty && app.supportsOpeningVideosByID + guard let app = AccountsModel.shared.current?.app else { return false } + return !AccountsModel.shared.isEmpty && app.supportsOpeningVideosByID } } diff --git a/Model/Player/Backends/AVPlayerBackend.swift b/Model/Player/Backends/AVPlayerBackend.swift index 9e0c1813..bde69f1e 100644 --- a/Model/Player/Backends/AVPlayerBackend.swift +++ b/Model/Player/Backends/AVPlayerBackend.swift @@ -12,11 +12,11 @@ final class AVPlayerBackend: PlayerBackend { private var logger = Logger(label: "avplayer-backend") - var model: PlayerModel! { .shared } - var controls: PlayerControlsModel! { .shared } - var playerTime: PlayerTimeModel! { .shared } - var networkState: NetworkStateModel! { .shared } - var seek: SeekModel! { .shared } + var model: PlayerModel { .shared } + var controls: PlayerControlsModel { .shared } + var playerTime: PlayerTimeModel { .shared } + var networkState: NetworkStateModel { .shared } + var seek: SeekModel { .shared } var stream: Stream? var video: Video? diff --git a/Model/Player/Backends/MPVBackend.swift b/Model/Player/Backends/MPVBackend.swift index b0ffac36..5c352891 100644 --- a/Model/Player/Backends/MPVBackend.swift +++ b/Model/Player/Backends/MPVBackend.swift @@ -13,11 +13,11 @@ final class MPVBackend: PlayerBackend { private var logger = Logger(label: "mpv-backend") - var model: PlayerModel! { .shared } - var controls: PlayerControlsModel! { .shared } - var playerTime: PlayerTimeModel! { .shared } - var networkState: NetworkStateModel! { .shared } - var seek: SeekModel! { .shared } + var model: PlayerModel { .shared } + var controls: PlayerControlsModel { .shared } + var playerTime: PlayerTimeModel { .shared } + var networkState: NetworkStateModel { .shared } + var seek: SeekModel { .shared } var stream: Stream? var video: Video? @@ -37,9 +37,9 @@ final class MPVBackend: PlayerBackend { return } - self.controls?.isLoadingVideo = self.isLoadingVideo + self.controls.isLoadingVideo = self.isLoadingVideo self.setNeedsNetworkStateUpdates(true) - self.model?.objectWillChange.send() + self.model.objectWillChange.send() } }} @@ -333,7 +333,7 @@ final class MPVBackend: PlayerBackend { isPlaying = true startClientUpdates() - if controls?.presentingControls ?? false { + if controls.presentingControls { startControlsUpdates() } @@ -428,9 +428,9 @@ final class MPVBackend: PlayerBackend { } private func updateControlsIsPlaying() { - guard model?.activeBackend == .mpv else { return } + guard model.activeBackend == .mpv else { return } 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() { - guard let client, let networkState else { - return - } - - DispatchQueue.main.async { - networkState.pausedForCache = client.pausedForCache - networkState.cacheDuration = client.cacheDuration - networkState.bufferingState = client.bufferingState + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.networkState.pausedForCache = self.client.pausedForCache + self.networkState.cacheDuration = self.client.cacheDuration + self.networkState.bufferingState = self.client.bufferingState } if !networkState.needsUpdates { diff --git a/Model/Player/Backends/MPVClient.swift b/Model/Player/Backends/MPVClient.swift index f819b7b4..1e750bc8 100644 --- a/Model/Player/Backends/MPVClient.swift +++ b/Model/Player/Backends/MPVClient.swift @@ -312,7 +312,8 @@ final class MPVClient: ObservableObject { } 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: { let aspectRatio = self.aspectRatio > 0 && self.aspectRatio < VideoPlayerView.defaultAspectRatio ? self.aspectRatio : VideoPlayerView.defaultAspectRatio let height = [model.playerSize.height, model.playerSize.width / aspectRatio].min()! @@ -329,7 +330,7 @@ final class MPVClient: ObservableObject { self.glView?.queue.async { self.glView.display() } - self.backend?.controls?.objectWillChange.send() + self.backend?.controls.objectWillChange.send() } } } diff --git a/Model/Player/Backends/PlayerBackend.swift b/Model/Player/Backends/PlayerBackend.swift index eac1592c..d8771770 100644 --- a/Model/Player/Backends/PlayerBackend.swift +++ b/Model/Player/Backends/PlayerBackend.swift @@ -7,10 +7,10 @@ import Foundation protocol PlayerBackend { var suggestedPlaybackRates: [Double] { get } - var model: PlayerModel! { get } - var controls: PlayerControlsModel! { get } - var playerTime: PlayerTimeModel! { get } - var networkState: NetworkStateModel! { get } + var model: PlayerModel { get } + var controls: PlayerControlsModel { get } + var playerTime: PlayerTimeModel { get } + var networkState: NetworkStateModel { get } var stream: Stream? { get set } var video: Video? { get set } diff --git a/Model/Player/PlayerModel.swift b/Model/Player/PlayerModel.swift index 8ce6adc6..54625577 100644 --- a/Model/Player/PlayerModel.swift +++ b/Model/Player/PlayerModel.swift @@ -45,7 +45,7 @@ final class PlayerModel: ObservableObject { } } - static var shared: PlayerModel! + static var shared = PlayerModel() 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 { #if !os(tvOS) @@ -125,13 +125,12 @@ final class PlayerModel: ObservableObject { @Default(.rotateToPortraitOnExitFullScreen) private var rotateToPortraitOnExitFullScreen #endif - var accounts: AccountsModel - var comments: CommentsModel + var comments: CommentsModel { .shared } var controls: PlayerControlsModel { .shared } var playerTime: PlayerTimeModel { .shared } var networkState: NetworkStateModel { .shared } var seek: SeekModel { .shared } - var navigation: NavigationModel + var navigation: NavigationModel { .shared } var context: NSManagedObjectContext = PersistenceController.shared.container.viewContext var backgroundContext = PersistenceController.shared.container.newBackgroundContext() @@ -173,15 +172,7 @@ final class PlayerModel: ObservableObject { var onPresentPlayer = [() -> Void]() private var remoteCommandCenterConfigured = false - init( - accounts: AccountsModel = AccountsModel(), - comments: CommentsModel = CommentsModel(), - navigation: NavigationModel = NavigationModel() - ) { - self.accounts = accounts - self.comments = comments - self.navigation = navigation - + init() { #if !os(macOS) mpvBackend.controller = mpvController mpvBackend.client = mpvController.client diff --git a/Model/Player/PlayerQueue.swift b/Model/Player/PlayerQueue.swift index 1a7ddf05..3ccde343 100644 --- a/Model/Player/PlayerQueue.swift +++ b/Model/Player/PlayerQueue.swift @@ -83,11 +83,11 @@ extension PlayerModel { } 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 { - playerInstance?.anonymous ?? accounts.api + playerInstance?.anonymous ?? AccountsModel.shared.api } var qualityProfile: QualityProfile? { @@ -269,7 +269,7 @@ extension PlayerModel { } 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 diff --git a/Model/Player/PlayerTimeModel.swift b/Model/Player/PlayerTimeModel.swift index 62733239..9a04b8f9 100644 --- a/Model/Player/PlayerTimeModel.swift +++ b/Model/Player/PlayerTimeModel.swift @@ -9,14 +9,14 @@ final class PlayerTimeModel: ObservableObject { @Published var currentTime = CMTime.zero @Published var duration = CMTime.zero - var player: PlayerModel! + var player: PlayerModel { .shared } var forceHours: Bool { duration.seconds >= 60 * 60 } var currentPlaybackTime: String { - if player?.currentItem.isNil ?? true || duration.seconds.isZero { + if player.currentItem.isNil || duration.seconds.isZero { return Self.timePlaceholder } @@ -24,7 +24,7 @@ final class PlayerTimeModel: ObservableObject { } var durationPlaybackTime: String { - if player?.currentItem.isNil ?? true { + if player.currentItem.isNil { return Self.timePlaceholder } @@ -32,7 +32,7 @@ final class PlayerTimeModel: ObservableObject { } 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 } } diff --git a/Model/PlaylistsModel.swift b/Model/PlaylistsModel.swift index 90f82f4d..0914724b 100644 --- a/Model/PlaylistsModel.swift +++ b/Model/PlaylistsModel.swift @@ -4,10 +4,12 @@ import Siesta import SwiftUI final class PlaylistsModel: ObservableObject { + static var shared = PlaylistsModel() + @Published var playlists = [Playlist]() @Published var reloadPlaylists = false - var accounts = AccountsModel() + var accounts = AccountsModel.shared init(_ playlists: [Playlist] = [Playlist]()) { self.playlists = playlists @@ -63,14 +65,13 @@ final class PlaylistsModel: ObservableObject { playlistID: Playlist.ID, videoID: Video.ID, onSuccess: @escaping () -> Void = {}, - navigation: NavigationModel?, onFailure: ((RequestError) -> Void)? = nil ) { accounts.api.addVideoToPlaylist( videoID, playlistID, onFailure: onFailure ?? { requestError in - navigation?.presentAlert( + NavigationModel.shared.presentAlert( title: "Error when adding to playlist", message: "(\(requestError.httpStatusCode ?? -1)) \(requestError.userMessage)" ) diff --git a/Model/RecentsModel.swift b/Model/RecentsModel.swift index 9f525ecb..23cd9ba7 100644 --- a/Model/RecentsModel.swift +++ b/Model/RecentsModel.swift @@ -2,6 +2,8 @@ import Defaults import Foundation final class RecentsModel: ObservableObject { + static var shared = RecentsModel() + @Default(.recentlyOpened) var items @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 { - navigation?.tabSelection = .search + NavigationModel.shared.tabSelection = .search add(.init(from: query)) } } diff --git a/Model/Search/SearchModel.swift b/Model/Search/SearchModel.swift index 05958d38..a4568d8a 100644 --- a/Model/Search/SearchModel.swift +++ b/Model/Search/SearchModel.swift @@ -4,6 +4,8 @@ import Siesta import SwiftUI final class SearchModel: ObservableObject { + static var shared = SearchModel() + @Published var store = Store<[ContentItem]>() @Published var page: SearchPage? @@ -14,7 +16,7 @@ final class SearchModel: ObservableObject { @Published var querySuggestions = [String]() private var suggestionsDebouncer = Debouncer(.milliseconds(200)) - var accounts = AccountsModel() + var accounts: AccountsModel { .shared } private var resource: Resource! var isLoading: Bool { diff --git a/Model/SettingsModel.swift b/Model/SettingsModel.swift index f0549069..41fdd3d6 100644 --- a/Model/SettingsModel.swift +++ b/Model/SettingsModel.swift @@ -2,6 +2,8 @@ import Foundation import SwiftUI final class SettingsModel: ObservableObject { + static var shared = SettingsModel() + @Published var presentingAlert = false @Published var alert = Alert(title: Text("Error")) diff --git a/Model/SubscriptionsModel.swift b/Model/SubscriptionsModel.swift index addfa07c..dd84fe11 100644 --- a/Model/SubscriptionsModel.swift +++ b/Model/SubscriptionsModel.swift @@ -3,17 +3,15 @@ import Siesta import SwiftUI final class SubscriptionsModel: ObservableObject { + static var shared = SubscriptionsModel() + @Published var channels = [Channel]() - var accounts: AccountsModel + var accounts: AccountsModel { .shared } var resource: Resource? { accounts.api.subscriptions } - init(accounts: AccountsModel? = nil) { - self.accounts = accounts ?? AccountsModel() - } - var all: [Channel] { channels.sorted { $0.name.lowercased() < $1.name.lowercased() } } diff --git a/Model/ThumbnailsModel.swift b/Model/ThumbnailsModel.swift index 52e5c0ac..86a71d04 100644 --- a/Model/ThumbnailsModel.swift +++ b/Model/ThumbnailsModel.swift @@ -2,6 +2,8 @@ import Defaults import Foundation final class ThumbnailsModel: ObservableObject { + static var shared = ThumbnailsModel() + @Published var unloadable = Set() func insertUnloadable(_ url: URL) { diff --git a/Shared/Home/FavoriteItemView.swift b/Shared/Home/FavoriteItemView.swift index 266c0904..bc94299e 100644 --- a/Shared/Home/FavoriteItemView.swift +++ b/Shared/Home/FavoriteItemView.swift @@ -11,9 +11,8 @@ struct FavoriteItemView: View { @Default(.favorites) private var favorites @Binding private var dragging: FavoriteItem? - @EnvironmentObject private var accounts - @EnvironmentObject private var playlists - + @ObservedObject private var accounts = AccountsModel.shared + private var playlists = PlaylistsModel.shared private var favoritesModel = FavoritesModel.shared init( diff --git a/Shared/Home/HistoryView.swift b/Shared/Home/HistoryView.swift index ea5443b8..ed9d6049 100644 --- a/Shared/Home/HistoryView.swift +++ b/Shared/Home/HistoryView.swift @@ -8,7 +8,7 @@ struct HistoryView: View { @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) var watches: FetchedResults - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared var body: some View { LazyVStack { diff --git a/Shared/Home/HomeView.swift b/Shared/Home/HomeView.swift index f86d2d6a..d81f9f28 100644 --- a/Shared/Home/HomeView.swift +++ b/Shared/Home/HomeView.swift @@ -4,8 +4,7 @@ import SwiftUI import UniformTypeIdentifiers struct HomeView: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var playlists + @ObservedObject private var accounts = AccountsModel.shared @State private var dragging: FavoriteItem? @State private var presentingEditFavorites = false diff --git a/Shared/MenuCommands.swift b/Shared/MenuCommands.swift index c0f8e85c..ba4c3b7f 100644 --- a/Shared/MenuCommands.swift +++ b/Shared/MenuCommands.swift @@ -12,7 +12,7 @@ struct MenuCommands: Commands { private var openVideosMenu: some Commands { CommandGroup(after: .newItem) { - Button("Open Videos...") { model.navigation?.presentingOpenVideos = true } + Button("Open Videos...") { NavigationModel.shared.presentingOpenVideos = true } .keyboardShortcut("t") } } @@ -33,7 +33,7 @@ struct MenuCommands: Commands { Button("Popular") { setTabSelection(.popular) } - .disabled(!(model.accounts?.app.supportsPopular ?? false)) + .disabled(!AccountsModel.shared.app.supportsPopular) .keyboardShortcut("3") Button("Trending") { @@ -51,36 +51,30 @@ struct MenuCommands: Commands { } private func setTabSelection(_ tabSelection: NavigationModel.TabSelection) { - guard let navigation = model.navigation else { - return - } - - navigation.sidebarSectionChanged.toggle() - navigation.tabSelection = tabSelection + NavigationModel.shared.sidebarSectionChanged.toggle() + NavigationModel.shared.tabSelection = tabSelection } private var subscriptionsDisabled: Bool { - !( - (model.accounts?.app.supportsSubscriptions ?? false) && model.accounts?.signedIn ?? false - ) + !(AccountsModel.shared.app.supportsSubscriptions && AccountsModel.shared.signedIn) } private var playbackMenu: some Commands { CommandMenu("Playback") { - Button((model.player?.isPlaying ?? true) ? "Pause" : "Play") { - model.player?.togglePlay() + Button((PlayerModel.shared.isPlaying) ? "Pause" : "Play") { + PlayerModel.shared.togglePlay() } - .disabled(model.player?.currentItem.isNil ?? true) + .disabled(PlayerModel.shared.currentItem.isNil) .keyboardShortcut("p") Button("Play Next") { - model.player?.advanceToNextItem() + PlayerModel.shared.advanceToNextItem() } - .disabled(model.player?.queue.isEmpty ?? true) + .disabled(PlayerModel.shared.queue.isEmpty) .keyboardShortcut("s") Button(togglePlayerLabel) { - model.player?.togglePlayer() + PlayerModel.shared.togglePlayer() } .keyboardShortcut("o") } @@ -90,7 +84,7 @@ struct MenuCommands: Commands { #if os(macOS) "Show Player" #else - (model.player?.presentingPlayer ?? true) ? "Hide Player" : "Show Player" + PlayerModel.shared.presentingPlayer ? "Hide Player" : "Show Player" #endif } } diff --git a/Shared/Navigation/AccountsMenuView.swift b/Shared/Navigation/AccountsMenuView.swift index 8fd90c3a..c89d6f4e 100644 --- a/Shared/Navigation/AccountsMenuView.swift +++ b/Shared/Navigation/AccountsMenuView.swift @@ -2,7 +2,7 @@ import Defaults import SwiftUI struct AccountsMenuView: View { - @EnvironmentObject private var model + @ObservedObject private var model = AccountsModel.shared @Default(.accounts) private var accounts @Default(.instances) private var instances diff --git a/Shared/Navigation/AppSidebarNavigation.swift b/Shared/Navigation/AppSidebarNavigation.swift index 04b30d4b..fa3a31de 100644 --- a/Shared/Navigation/AppSidebarNavigation.swift +++ b/Shared/Navigation/AppSidebarNavigation.swift @@ -5,20 +5,11 @@ import SwiftUI #endif struct AppSidebarNavigation: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation + @ObservedObject private var accounts = AccountsModel.shared + private var navigation: NavigationModel { .shared } #if os(iOS) @State private var didApplyPrimaryViewWorkAround = false - - @EnvironmentObject private var comments - @EnvironmentObject private var instances - @EnvironmentObject private var player - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var search - @EnvironmentObject private var subscriptions - @EnvironmentObject private var thumbnailsModel #endif @Default(.showOpenActionsToolbarItem) private var showOpenActionsToolbarItem diff --git a/Shared/Navigation/AppSidebarPlaylists.swift b/Shared/Navigation/AppSidebarPlaylists.swift index cb79f555..ced46dd6 100644 --- a/Shared/Navigation/AppSidebarPlaylists.swift +++ b/Shared/Navigation/AppSidebarPlaylists.swift @@ -1,9 +1,10 @@ import SwiftUI struct AppSidebarPlaylists: View { - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + private var player = PlayerModel.shared + @ObservedObject private var playlists = PlaylistsModel.shared var body: some View { Section(header: Text("Playlists")) { @@ -35,7 +36,7 @@ struct AppSidebarPlaylists: View { @ViewBuilder func playlistLabel(_ playlist: Playlist) -> some View { 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 .backport .badge(Text("\(playlist.videos.count)")) diff --git a/Shared/Navigation/AppSidebarRecents.swift b/Shared/Navigation/AppSidebarRecents.swift index e99ee77b..9b567a98 100644 --- a/Shared/Navigation/AppSidebarRecents.swift +++ b/Shared/Navigation/AppSidebarRecents.swift @@ -2,7 +2,8 @@ import Defaults import SwiftUI struct AppSidebarRecents: View { - @EnvironmentObject private var recents + @ObservedObject private var navigation = NavigationModel.shared + var recents = RecentsModel.shared @Default(.recentlyOpened) private var recentItems @@ -47,8 +48,8 @@ struct AppSidebarRecents: View { } struct RecentNavigationLink: View { - @EnvironmentObject private var navigation - @EnvironmentObject private var recents + var recents = RecentsModel.shared + @ObservedObject private var navigation = NavigationModel.shared var recent: RecentItem var systemImage: String? diff --git a/Shared/Navigation/AppSidebarSubscriptions.swift b/Shared/Navigation/AppSidebarSubscriptions.swift index 750f0fde..52e0cdcf 100644 --- a/Shared/Navigation/AppSidebarSubscriptions.swift +++ b/Shared/Navigation/AppSidebarSubscriptions.swift @@ -2,8 +2,8 @@ import Defaults import SwiftUI struct AppSidebarSubscriptions: View { - @EnvironmentObject private var navigation - @EnvironmentObject private var subscriptions + @ObservedObject private var navigation = NavigationModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared var body: some View { Section(header: Text("Subscriptions")) { diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 70cf3bbf..3c1772cb 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -2,16 +2,10 @@ import Defaults import SwiftUI struct AppTabNavigation: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var comments - @EnvironmentObject private var instances - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var search - @EnvironmentObject private var subscriptions - @EnvironmentObject private var thumbnailsModel + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + private var player = PlayerModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared @Default(.showHome) private var showHome @Default(.showDocuments) private var showDocuments @@ -187,11 +181,6 @@ struct AppTabNavigation: View { .environment(\.managedObjectContext, persistenceController.container.viewContext) .environment(\.inChannelView, true) .environment(\.navigationStyle, .tab) - .environmentObject(accounts) - .environmentObject(navigation) - .environmentObject(player) - .environmentObject(subscriptions) - .environmentObject(thumbnailsModel) .id("channelVideos") .zIndex(player.presentingPlayer ? -1 : 2) .transition(.move(edge: .bottom)) @@ -202,11 +191,6 @@ struct AppTabNavigation: View { if navigation.presentingPlaylist { ChannelPlaylistView() .environment(\.managedObjectContext, persistenceController.container.viewContext) - .environmentObject(accounts) - .environmentObject(navigation) - .environmentObject(player) - .environmentObject(subscriptions) - .environmentObject(thumbnailsModel) .id("channelPlaylist") .zIndex(player.presentingPlayer ? -1 : 1) .transition(.move(edge: .bottom)) diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 3475dd19..d421595f 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -8,19 +8,11 @@ import Siesta import SwiftUI struct ContentView: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var comments - @EnvironmentObject private var instances - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var search - @EnvironmentObject private var settings - @EnvironmentObject private var subscriptions - @EnvironmentObject private var thumbnailsModel - - @EnvironmentObject private var menu + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + @ObservedObject private var player = PlayerModel.shared + private var playlists = PlaylistsModel.shared + private var subscriptions = SubscriptionsModel.shared #if os(iOS) @Environment(\.horizontalSizeClass) private var horizontalSizeClass @@ -44,7 +36,6 @@ struct ContentView: View { AppSidebarNavigation() #elseif os(tvOS) TVNavigationView() - .environmentObject(settings) #endif } .onChange(of: accounts.current) { _ in @@ -55,99 +46,74 @@ struct ContentView: View { subscriptions.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) - .overlay(videoPlayer) - .sheet(isPresented: $navigation.presentingShareSheet) { - if let shareURL = navigation.shareURL { - ShareSheet(activityItems: [shareURL]) - } + .overlay(videoPlayer) + .sheet(isPresented: $navigation.presentingShareSheet) { + if let shareURL = navigation.shareURL { + ShareSheet(activityItems: [shareURL]) } + } #endif - // iOS 14 has problem with multiple sheets in one view - // but it's ok when it's in background - .background( - EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) { - WelcomeScreen() - .environmentObject(accounts) - .environmentObject(navigation) - } - ) - .background( - EmptyView().sheet(isPresented: $navigation.presentingSettings) { - SettingsView() - .environmentObject(accounts) - .environmentObject(instances) - .environmentObject(settings) - .environmentObject(navigation) - .environmentObject(player) - } - ) + // iOS 14 has problem with multiple sheets in one view + // but it's ok when it's in background + .background( + EmptyView().sheet(isPresented: $navigation.presentingWelcomeScreen) { + WelcomeScreen() + } + ) + .background( + EmptyView().sheet(isPresented: $navigation.presentingSettings) { + SettingsView() + } + ) #if !os(tvOS) - .fileImporter( - isPresented: $navigation.presentingFileImporter, - allowedContentTypes: [.audiovisualContent], - allowsMultipleSelection: true - ) { result in - do { - let selectedFiles = try result.get() - let urlsToOpen = selectedFiles.map { url in - if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(url) { - return bookmarkURL - } - - if url.startAccessingSecurityScopedResource() { - URLBookmarkModel.shared.saveBookmark(url) - } - - return url + .fileImporter( + isPresented: $navigation.presentingFileImporter, + allowedContentTypes: [.audiovisualContent], + allowsMultipleSelection: true + ) { result in + do { + let selectedFiles = try result.get() + let urlsToOpen = selectedFiles.map { url in + if let bookmarkURL = URLBookmarkModel.shared.loadBookmark(url) { + return bookmarkURL } - OpenVideosModel.shared.openURLs(urlsToOpen) - } catch { - NavigationModel.shared.presentAlert(title: "Could not open Files") + if url.startAccessingSecurityScopedResource() { + URLBookmarkModel.shared.saveBookmark(url) + } + + return url } - NavigationModel.shared.presentingOpenVideos = false + OpenVideosModel.shared.openURLs(urlsToOpen) + } catch { + NavigationModel.shared.presentAlert(title: "Could not open Files") } - .onOpenURL(perform: OpenURLHandler.shared.handle) - .background( - EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) { - AddToPlaylistView(video: navigation.videoToAddToPlaylist) - .environmentObject(playlists) - } - ) - .background( - EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) { - PlaylistFormView(playlist: $navigation.editedPlaylist) - .environmentObject(accounts) - .environmentObject(playlists) - } - ) + + NavigationModel.shared.presentingOpenVideos = false + } + .onOpenURL(perform: OpenURLHandler.shared.handle) + .background( + EmptyView().sheet(isPresented: $navigation.presentingAddToPlaylist) { + AddToPlaylistView(video: navigation.videoToAddToPlaylist) + } + ) + .background( + EmptyView().sheet(isPresented: $navigation.presentingPlaylistForm) { + PlaylistFormView(playlist: $navigation.editedPlaylist) + } + ) #endif - .background( - EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) { - OpenVideosView() - .environmentObject(accounts) - .environmentObject(navigation) - .environmentObject(player) - .environmentObject(recents) - .environmentObject(search) - } - ) - .background(playerViewInitialize) - .alert(isPresented: $navigation.presentingAlert) { navigation.alert } + .background( + EmptyView().sheet(isPresented: $navigation.presentingOpenVideos) { + OpenVideosView() + } + ) + .background(playerViewInitialize) + .alert(isPresented: $navigation.presentingAlert) { navigation.alert } } var navigationStyle: NavigationStyle { @@ -174,16 +140,6 @@ struct ContentView: View { var playerView: some View { VideoPlayerView() - .environmentObject(accounts) - .environmentObject(comments) - .environmentObject(instances) - .environmentObject(navigation) - .environmentObject(player) - .environmentObject(playerControls) - .environmentObject(playlists) - .environmentObject(recents) - .environmentObject(subscriptions) - .environmentObject(thumbnailsModel) .environment(\.navigationStyle, navigationStyle) } diff --git a/Shared/Navigation/Sidebar.swift b/Shared/Navigation/Sidebar.swift index 12ec9395..17b07740 100644 --- a/Shared/Navigation/Sidebar.swift +++ b/Shared/Navigation/Sidebar.swift @@ -2,8 +2,8 @@ import Defaults import SwiftUI struct Sidebar: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared @Default(.showHome) private var showHome @Default(.visibleSections) private var visibleSections diff --git a/Shared/OpenURLHandler.swift b/Shared/OpenURLHandler.swift index 2db2960d..82f4be5b 100644 --- a/Shared/OpenURLHandler.swift +++ b/Shared/OpenURLHandler.swift @@ -7,11 +7,11 @@ struct OpenURLHandler { static var shared = OpenURLHandler() static let yatteeProtocol = "yattee://" - var accounts: AccountsModel! - var navigation: NavigationModel! - var recents: RecentsModel! - var player: PlayerModel! - var search: SearchModel! + var accounts: AccountsModel { .shared } + var navigation: NavigationModel { .shared } + var recents: RecentsModel { .shared } + var player: PlayerModel { .shared } + var search: SearchModel { .shared } var navigationStyle = NavigationStyle.sidebar func handle(_ url: URL) { @@ -162,11 +162,8 @@ struct OpenURLHandler { if var playlist: ChannelPlaylist = response.typedContent() { playlist.id = playlistID DispatchQueue.main.async { - NavigationModel.openChannelPlaylist( + NavigationModel.shared.openChannelPlaylist( playlist, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } @@ -194,11 +191,8 @@ struct OpenURLHandler { .onSuccess { response in if let channel: Channel = response.typedContent() { DispatchQueue.main.async { - NavigationModel.openChannel( + NavigationModel.shared.openChannel( channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } @@ -228,7 +222,7 @@ struct OpenURLHandler { 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) } @@ -242,7 +236,7 @@ struct OpenURLHandler { 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) } @@ -254,13 +248,7 @@ struct OpenURLHandler { if alertIfNoMainWindowOpen() { return } #endif - NavigationModel.openSearchQuery( - parser.searchQuery, - player: player, - recents: recents, - navigation: navigation, - search: search - ) + NavigationModel.shared.openSearchQuery(parser.searchQuery) #if os(macOS) focusMainWindow() diff --git a/Shared/Player/AppleAVPlayerView.swift b/Shared/Player/AppleAVPlayerView.swift index a9519479..87eca885 100644 --- a/Shared/Player/AppleAVPlayerView.swift +++ b/Shared/Player/AppleAVPlayerView.swift @@ -4,11 +4,8 @@ import SwiftUI #if os(iOS) struct AppleAVPlayerView: UIViewRepresentable { - @EnvironmentObject private var player - func makeUIView(context _: Context) -> some UIView { let playerLayerView = PlayerLayerView(frame: .zero) - playerLayerView.player = player return playerLayerView } @@ -16,30 +13,15 @@ import SwiftUI } #else struct AppleAVPlayerView: UIViewControllerRepresentable { - @EnvironmentObject private var accounts - @EnvironmentObject private var comments - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists - @EnvironmentObject private var subscriptions - func makeUIViewController(context _: Context) -> AppleAVPlayerViewController { let controller = AppleAVPlayerViewController() - - controller.accountsModel = accounts - controller.commentsModel = comments - controller.navigationModel = navigation - controller.playerModel = player - controller.playlistsModel = playlists - controller.subscriptionsModel = subscriptions - - player.avPlayerBackend.controller = controller + PlayerModel.shared.avPlayerBackend.controller = controller return controller } func updateUIViewController(_: AppleAVPlayerViewController, context _: Context) { - player.rebuildTVMenu() + PlayerModel.shared.rebuildTVMenu() } } #endif diff --git a/Shared/Player/AppleAVPlayerViewController.swift b/Shared/Player/AppleAVPlayerViewController.swift index 119cd0fd..346dfd90 100644 --- a/Shared/Player/AppleAVPlayerViewController.swift +++ b/Shared/Player/AppleAVPlayerViewController.swift @@ -4,12 +4,11 @@ import SwiftUI final class AppleAVPlayerViewController: UIViewController { var playerLoaded = false - var accountsModel: AccountsModel! - var commentsModel: CommentsModel! - var navigationModel: NavigationModel! - var playerModel: PlayerModel! - var playlistsModel: PlaylistsModel! - var subscriptionsModel: SubscriptionsModel! + var accountsModel: AccountsModel { .shared } + var navigationModel: NavigationModel { .shared } + var playerModel: PlayerModel { .shared } + var playlistsModel: PlaylistsModel { .shared } + var subscriptionsModel: SubscriptionsModel { .shared } var playerView = AVPlayerViewController() let persistenceController = PersistenceController.shared @@ -66,12 +65,6 @@ final class AppleAVPlayerViewController: UIViewController { AnyView( NowPlayingView(sections: sections, inInfoViewController: true) .frame(maxHeight: 600) - .environmentObject(accountsModel) - .environmentObject(commentsModel) - .environmentObject(navigationModel) - .environmentObject(playerModel) - .environmentObject(playlistsModel) - .environmentObject(subscriptionsModel) .environment(\.managedObjectContext, persistenceController.container.viewContext) ) ) diff --git a/Shared/Player/Controls/ControlsOverlay.swift b/Shared/Player/Controls/ControlsOverlay.swift index 4b116411..d2b6bafb 100644 --- a/Shared/Player/Controls/ControlsOverlay.swift +++ b/Shared/Player/Controls/ControlsOverlay.swift @@ -2,8 +2,8 @@ import Defaults import SwiftUI struct ControlsOverlay: View { - @EnvironmentObject private var player - @EnvironmentObject private var model + @ObservedObject private var player = PlayerModel.shared + private var model = PlayerControlsModel.shared @State private var contentSize: CGSize = .zero @@ -399,8 +399,5 @@ struct ControlsOverlay: View { struct ControlsOverlay_Previews: PreviewProvider { static var previews: some View { ControlsOverlay() - .environmentObject(NetworkStateModel()) - .environmentObject(PlayerModel()) - .environmentObject(PlayerControlsModel()) } } diff --git a/Shared/Player/Controls/OSD/Buffering.swift b/Shared/Player/Controls/OSD/Buffering.swift index 6e0ef19e..d7811d35 100644 --- a/Shared/Player/Controls/OSD/Buffering.swift +++ b/Shared/Player/Controls/OSD/Buffering.swift @@ -10,7 +10,7 @@ struct Buffering: View { @Environment(\.verticalSizeClass) private var verticalSizeClass #endif - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared @Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout diff --git a/Shared/Player/Controls/OSD/NetworkState.swift b/Shared/Player/Controls/OSD/NetworkState.swift index 56f8bc27..bab58dca 100644 --- a/Shared/Player/Controls/OSD/NetworkState.swift +++ b/Shared/Player/Controls/OSD/NetworkState.swift @@ -15,7 +15,5 @@ struct NetworkState_Previews: PreviewProvider { networkState.bufferingState = 30 return NetworkState() - .environmentObject(networkState) - .environmentObject(PlayerModel()) } } diff --git a/Shared/Player/Controls/OSD/OpeningStream.swift b/Shared/Player/Controls/OSD/OpeningStream.swift index 5399f172..2684ecb7 100644 --- a/Shared/Player/Controls/OSD/OpeningStream.swift +++ b/Shared/Player/Controls/OSD/OpeningStream.swift @@ -1,8 +1,8 @@ import SwiftUI struct OpeningStream: View { - @EnvironmentObject private var player - @EnvironmentObject private var model + @ObservedObject private var player = PlayerModel.shared + @ObservedObject private var model = NetworkStateModel.shared var body: some View { Buffering(reason: reason, state: state) diff --git a/Shared/Player/Controls/OSD/Seek.swift b/Shared/Player/Controls/OSD/Seek.swift index 17bab5b2..3813c7f7 100644 --- a/Shared/Player/Controls/OSD/Seek.swift +++ b/Shared/Player/Controls/OSD/Seek.swift @@ -6,7 +6,7 @@ struct Seek: View { @Environment(\.verticalSizeClass) private var verticalSizeClass #endif - @EnvironmentObject private var controls + @ObservedObject private var controls = PlayerControlsModel.shared @StateObject private var model = SeekModel.shared private var updateThrottle = Throttle(interval: 2) @@ -137,6 +137,5 @@ struct Seek: View { struct Seek_Previews: PreviewProvider { static var previews: some View { Seek() - .environmentObject(PlayerTimeModel()) } } diff --git a/Shared/Player/Controls/PlayerControls.swift b/Shared/Player/Controls/PlayerControls.swift index 2777f62f..731add8c 100644 --- a/Shared/Player/Controls/PlayerControls.swift +++ b/Shared/Player/Controls/PlayerControls.swift @@ -6,8 +6,8 @@ import SwiftUI struct PlayerControls: View { static let animation = Animation.easeInOut(duration: 0.2) - private var player: PlayerModel! - private var thumbnails: ThumbnailsModel! + private var player: PlayerModel { .shared } + private var thumbnails: ThumbnailsModel { .shared } @ObservedObject private var model = PlayerControlsModel.shared @@ -39,11 +39,6 @@ struct PlayerControls: View { player.playingFullScreen ? fullScreenPlayerControlsLayout : regularPlayerControlsLayout } - init(player: PlayerModel, thumbnails: ThumbnailsModel) { - self.player = player - self.thumbnails = thumbnails - } - var body: some View { ZStack(alignment: .topLeading) { Seek() @@ -206,12 +201,12 @@ struct PlayerControls: View { } 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()! } 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 #if os(iOS) inset = SafeArea.insets.bottom @@ -499,7 +494,7 @@ struct PlayerControls_Previews: PreviewProvider { ZStack { Color.gray - PlayerControls(player: PlayerModel(), thumbnails: ThumbnailsModel()) + PlayerControls() .injectFixtureEnvironmentObjects() } } diff --git a/Shared/Player/Controls/TVControls.swift b/Shared/Player/Controls/TVControls.swift index 3c1631e7..933e84db 100644 --- a/Shared/Player/Controls/TVControls.swift +++ b/Shared/Player/Controls/TVControls.swift @@ -3,8 +3,8 @@ import SwiftUI struct TVControls: UIViewRepresentable { var model: PlayerControlsModel! - var player: PlayerModel! - var thumbnails: ThumbnailsModel! + var player: PlayerModel { .shared } + var thumbnails: ThumbnailsModel { .shared } @State private var direction = "" @State private var controlsArea = UIView() @@ -30,7 +30,7 @@ struct TVControls: UIViewRepresentable { controlsArea.addGestureRecognizer(downSwipe) controlsArea.addGestureRecognizer(tap) - let controls = UIHostingController(rootView: PlayerControls(player: player, thumbnails: thumbnails)) + let controls = UIHostingController(rootView: PlayerControls()) controls.view.frame = .init( origin: .init(x: SafeArea.insets.left, y: SafeArea.insets.top), size: .init( diff --git a/Shared/Player/Controls/TimelineView.swift b/Shared/Player/Controls/TimelineView.swift index 788748ea..2680961f 100644 --- a/Shared/Player/Controls/TimelineView.swift +++ b/Shared/Player/Controls/TimelineView.swift @@ -45,9 +45,9 @@ struct TimelineView: View { #endif @ObservedObject private var playerTime = PlayerTimeModel.shared + @ObservedObject private var player = PlayerModel.shared - @EnvironmentObject private var player - @EnvironmentObject private var controls + private var controls = PlayerControlsModel.shared @Default(.playerControlsLayout) private var regularPlayerControlsLayout @Default(.fullScreenPlayerControlsLayout) private var fullScreenPlayerControlsLayout @@ -124,6 +124,8 @@ struct TimelineView: View { .frame(minWidth: 35) .padding(.leading, playerControlsLayout.timeLeadingEdgePadding) .padding(.trailing, playerControlsLayout.timeTrailingEdgePadding) + .modifier(ControlBackgroundModifier()) + .clipShape(RoundedRectangle(cornerRadius: 4)) ZStack(alignment: .center) { ZStack(alignment: .leading) { @@ -172,6 +174,8 @@ struct TimelineView: View { .padding(.leading, playerControlsLayout.timeTrailingEdgePadding) .padding(.trailing, playerControlsLayout.timeLeadingEdgePadding) .frame(minWidth: 30, alignment: .trailing) + .modifier(ControlBackgroundModifier()) + .clipShape(RoundedRectangle(cornerRadius: 4)) } #if !os(tvOS) .highPriorityGesture( @@ -207,8 +211,6 @@ struct TimelineView: View { } ) #endif - .modifier(ControlBackgroundModifier()) - .clipShape(RoundedRectangle(cornerRadius: 4)) .font(.system(size: playerControlsLayout.timeFontSize).monospacedDigit()) .zIndex(2) } @@ -369,14 +371,11 @@ struct TimelineView_Previews: PreviewProvider { let playerModel = PlayerModel() playerModel.currentItem = .init(Video.fixture) let playerTimeModel = PlayerTimeModel.shared - playerTimeModel.player = playerModel playerTimeModel.currentTime = .secondsInDefaultTimescale(33) playerTimeModel.duration = .secondsInDefaultTimescale(100) return VStack(spacing: 40) { TimelineView() } - .environmentObject(playerModel) - .environmentObject(PlayerControlsModel()) .padding() } } diff --git a/Shared/Player/Controls/VideoDetailsOverlay.swift b/Shared/Player/Controls/VideoDetailsOverlay.swift index 7a2c423d..ee7c8661 100644 --- a/Shared/Player/Controls/VideoDetailsOverlay.swift +++ b/Shared/Player/Controls/VideoDetailsOverlay.swift @@ -2,7 +2,7 @@ import Defaults import SwiftUI struct VideoDetailsOverlay: View { - @EnvironmentObject private var controls + @ObservedObject private var controls = PlayerControlsModel.shared @State private var detailsPage = VideoDetails.DetailsPage.queue diff --git a/Shared/Player/PlayerBackendView.swift b/Shared/Player/PlayerBackendView.swift index 65945775..70691f66 100644 --- a/Shared/Player/PlayerBackendView.swift +++ b/Shared/Player/PlayerBackendView.swift @@ -4,8 +4,7 @@ struct PlayerBackendView: View { #if os(iOS) @Environment(\.verticalSizeClass) private var verticalSizeClass #endif - @EnvironmentObject private var player - @EnvironmentObject private var thumbnails + @ObservedObject private var player = PlayerModel.shared var body: some View { ZStack(alignment: .top) { @@ -29,7 +28,7 @@ struct PlayerBackendView: View { #if !os(tvOS) PlayerGestures() - PlayerControls(player: player, thumbnails: thumbnails) + PlayerControls() #if os(iOS) .padding(.top, controlsTopPadding) .padding(.bottom, controlsBottomPadding) diff --git a/Shared/Player/PlayerGestures.swift b/Shared/Player/PlayerGestures.swift index d575ced3..6a3d73c9 100644 --- a/Shared/Player/PlayerGestures.swift +++ b/Shared/Player/PlayerGestures.swift @@ -1,8 +1,8 @@ import SwiftUI struct PlayerGestures: View { - @EnvironmentObject private var player - @EnvironmentObject private var model + private var player = PlayerModel.shared + @ObservedObject private var model = PlayerControlsModel.shared var body: some View { HStack(spacing: 0) { diff --git a/Shared/Player/PlayerLayerView.swift b/Shared/Player/PlayerLayerView.swift index 99bf5f4a..ff2df0a9 100644 --- a/Shared/Player/PlayerLayerView.swift +++ b/Shared/Player/PlayerLayerView.swift @@ -8,7 +8,7 @@ import Foundation #if os(macOS) final class PlayerLayerView: NSView { - var player: PlayerModel! { didSet { + var player = PlayerModel.shared { didSet { wantsLayer = true }} @@ -26,7 +26,7 @@ import Foundation } #else final class PlayerLayerView: UIView { - var player: PlayerModel! + var player: PlayerModel { .shared } override init(frame: CGRect) { super.init(frame: frame) diff --git a/Shared/Player/PlayerQueueRow.swift b/Shared/Player/PlayerQueueRow.swift index f68e1759..e7c2e6bb 100644 --- a/Shared/Player/PlayerQueueRow.swift +++ b/Shared/Player/PlayerQueueRow.swift @@ -9,7 +9,7 @@ struct PlayerQueueRow: View { var autoplay = false @Binding var fullScreen: Bool - @EnvironmentObject private var player + private var player = PlayerModel.shared @Default(.closePiPOnNavigation) var closePiPOnNavigation diff --git a/Shared/Player/RelatedView.swift b/Shared/Player/RelatedView.swift index 11f454ce..d923f3d5 100644 --- a/Shared/Player/RelatedView.swift +++ b/Shared/Player/RelatedView.swift @@ -2,10 +2,7 @@ import Defaults import SwiftUI struct RelatedView: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists + @ObservedObject private var player = PlayerModel.shared var body: some View { List { diff --git a/Shared/Player/StreamControl.swift b/Shared/Player/StreamControl.swift index e1a400ff..c4f7bb1c 100644 --- a/Shared/Player/StreamControl.swift +++ b/Shared/Player/StreamControl.swift @@ -9,7 +9,7 @@ struct StreamControl: View { } #endif - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared var body: some View { Group { diff --git a/Shared/Player/Video Details/ChapterView.swift b/Shared/Player/Video Details/ChapterView.swift index 3767b38f..826dfd86 100644 --- a/Shared/Player/Video Details/ChapterView.swift +++ b/Shared/Player/Video Details/ChapterView.swift @@ -5,7 +5,7 @@ import SwiftUI struct ChapterView: View { var chapter: Chapter - @EnvironmentObject private var player + var player = PlayerModel.shared var body: some View { Button { diff --git a/Shared/Player/Video Details/ChaptersView.swift b/Shared/Player/Video Details/ChaptersView.swift index 5f057898..372d4a9e 100644 --- a/Shared/Player/Video Details/ChaptersView.swift +++ b/Shared/Player/Video Details/ChaptersView.swift @@ -3,7 +3,7 @@ import SDWebImageSwiftUI import SwiftUI struct ChaptersView: View { - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared var body: some View { if let chapters = player.currentVideo?.chapters, !chapters.isEmpty { diff --git a/Shared/Player/Video Details/CommentView.swift b/Shared/Player/Video Details/CommentView.swift index 2314acd3..ecf7d7d0 100644 --- a/Shared/Player/Video Details/CommentView.swift +++ b/Shared/Player/Video Details/CommentView.swift @@ -14,11 +14,8 @@ struct CommentView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var comments - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions + @ObservedObject private var comments = CommentsModel.shared + var subscriptions = SubscriptionsModel.shared var body: some View { VStack(alignment: .leading) { @@ -252,11 +249,8 @@ struct CommentView: View { } private func openChannelAction() { - NavigationModel.openChannel( + NavigationModel.shared.openChannel( comment.channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } @@ -269,7 +263,6 @@ struct CommentView_Previews: PreviewProvider { static var previews: some View { CommentView(comment: fixture, repliesID: .constant(fixture.id)) - .environmentObject(SubscriptionsModel()) .padding(5) } } diff --git a/Shared/Player/Video Details/CommentsView.swift b/Shared/Player/Video Details/CommentsView.swift index 99a129f1..6764f764 100644 --- a/Shared/Player/Video Details/CommentsView.swift +++ b/Shared/Player/Video Details/CommentsView.swift @@ -4,7 +4,7 @@ struct CommentsView: View { var embedInScrollView = false @State private var repliesID: Comment.ID? - @EnvironmentObject private var comments + @ObservedObject private var comments = CommentsModel.shared var body: some View { Group { diff --git a/Shared/Player/Video Details/InspectorView.swift b/Shared/Player/Video Details/InspectorView.swift index 41df46b3..c1bf0202 100644 --- a/Shared/Player/Video Details/InspectorView.swift +++ b/Shared/Player/Video Details/InspectorView.swift @@ -3,7 +3,7 @@ import SwiftUI struct InspectorView: View { var video: Video? - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared var body: some View { ScrollView { diff --git a/Shared/Player/Video Details/PlayerQueueView.swift b/Shared/Player/Video Details/PlayerQueueView.swift index 9d2cd6db..ae06d2f0 100644 --- a/Shared/Player/Video Details/PlayerQueueView.swift +++ b/Shared/Player/Video Details/PlayerQueueView.swift @@ -9,10 +9,7 @@ struct PlayerQueueView: View { @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) var watches: FetchedResults - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var playlists - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared @Default(.saveHistory) private var saveHistory diff --git a/Shared/Player/Video Details/VideoActions.swift b/Shared/Player/Video Details/VideoActions.swift index c8ce6572..d2530a9b 100644 --- a/Shared/Player/Video Details/VideoActions.swift +++ b/Shared/Player/Video Details/VideoActions.swift @@ -2,10 +2,10 @@ import Defaults import SwiftUI struct VideoActions: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var subscriptions - @EnvironmentObject private var player + @ObservedObject private var accounts = AccountsModel.shared + var navigation = NavigationModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared + @ObservedObject private var player = PlayerModel.shared var video: Video? diff --git a/Shared/Player/Video Details/VideoDescription.swift b/Shared/Player/Video Details/VideoDescription.swift index c098517a..f6609db0 100644 --- a/Shared/Player/Video Details/VideoDescription.swift +++ b/Shared/Player/Video Details/VideoDescription.swift @@ -6,10 +6,7 @@ import Foundation import SwiftUI struct VideoDescription: View { - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var search + private var search: SearchModel { .shared } @Default(.showKeywords) private var showKeywords var video: Video @@ -56,7 +53,7 @@ struct VideoDescription: View { HStack { ForEach(video.keywords, id: \.self) { keyword in Button { - NavigationModel.openSearchQuery(keyword, player: player, recents: recents, navigation: navigation, search: search) + NavigationModel.shared.openSearchQuery(keyword) } label: { HStack(alignment: .center, spacing: 0) { Text("#") @@ -96,7 +93,8 @@ struct VideoDescription: View { @State private var label = ActiveLabel() @Environment(\.openURL) private var openURL - @EnvironmentObject private var player + + var player = PlayerModel.shared func makeUIView(context _: Context) -> some UIView { customizeLabel() diff --git a/Shared/Player/Video Details/VideoDetails.swift b/Shared/Player/Video Details/VideoDetails.swift index 25df321b..a8bb9f1a 100644 --- a/Shared/Player/Video Details/VideoDetails.swift +++ b/Shared/Player/Video Details/VideoDetails.swift @@ -23,12 +23,9 @@ struct VideoDetails: View { @Environment(\.colorScheme) private var colorScheme - @EnvironmentObject private var accounts - @EnvironmentObject private var comments - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions + @ObservedObject private var accounts = AccountsModel.shared + let comments = CommentsModel.shared + @ObservedObject private var player = PlayerModel.shared @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.detailsToolbarPosition) private var detailsToolbarPosition @@ -148,6 +145,7 @@ struct VideoDetails: View { } } .contentShape(Rectangle()) + .frame(maxHeight: .infinity) } @State private var detailsSize = CGSize.zero diff --git a/Shared/Player/Video Details/VideoDetailsToolbar.swift b/Shared/Player/Video Details/VideoDetailsToolbar.swift index 59e4c958..cef209e8 100644 --- a/Shared/Player/Video Details/VideoDetailsToolbar.swift +++ b/Shared/Player/Video Details/VideoDetailsToolbar.swift @@ -13,7 +13,7 @@ struct VideoDetailsToolbar: View { @State private var startedToolPosition: CGRect = .zero @State private var opacity = 1.0 - @EnvironmentObject private var player + @ObservedObject private var player = PlayerModel.shared @Default(.playerDetailsPageButtonLabelStyle) private var playerDetailsPageButtonLabelStyle var body: some View { diff --git a/Shared/Player/VideoPlayerView.swift b/Shared/Player/VideoPlayerView.swift index 9223d517..b6fada2d 100644 --- a/Shared/Player/VideoPlayerView.swift +++ b/Shared/Player/VideoPlayerView.swift @@ -57,13 +57,10 @@ struct VideoPlayerView: View { @State internal var orientationNotification: Any? #endif - @EnvironmentObject internal var player + internal var player: PlayerModel! = PlayerModel.shared + #if os(macOS) - @EnvironmentObject internal var navigation - @EnvironmentObject internal var search - #endif - #if os(tvOS) - @EnvironmentObject private var thumbnails + @ObservedObject private var navigation = NavigationModel.shared #endif @Default(.horizontalPlayerGestureEnabled) var horizontalPlayerGestureEnabled @@ -482,7 +479,7 @@ struct VideoPlayerView: View { #if os(tvOS) var tvControls: some View { - TVControls(player: player, thumbnails: thumbnails) + TVControls() } #endif } diff --git a/Shared/Playlists/AddToPlaylistView.swift b/Shared/Playlists/AddToPlaylistView.swift index de0a2002..e0d0d14c 100644 --- a/Shared/Playlists/AddToPlaylistView.swift +++ b/Shared/Playlists/AddToPlaylistView.swift @@ -13,8 +13,7 @@ struct AddToPlaylistView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.presentationMode) private var presentationMode - @EnvironmentObject private var navigation - @EnvironmentObject private var model + @ObservedObject private var model = PlaylistsModel.shared var body: some View { Group { @@ -164,7 +163,7 @@ struct AddToPlaylistView: View { Defaults[.lastUsedPlaylistID] = id - model.addVideo(playlistID: id, videoID: video.videoID, navigation: navigation) + model.addVideo(playlistID: id, videoID: video.videoID) presentationMode.wrappedValue.dismiss() } diff --git a/Shared/Playlists/PlaylistFormView.swift b/Shared/Playlists/PlaylistFormView.swift index 8b6ae8ac..7050e9da 100644 --- a/Shared/Playlists/PlaylistFormView.swift +++ b/Shared/Playlists/PlaylistFormView.swift @@ -16,8 +16,8 @@ struct PlaylistFormView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.presentationMode) private var presentationMode - @EnvironmentObject private var accounts - @EnvironmentObject private var playlists + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var playlists = PlaylistsModel.shared var editing: Bool { playlist != nil diff --git a/Shared/Playlists/PlaylistsView.swift b/Shared/Playlists/PlaylistsView.swift index 173f1ba7..1ba6df60 100644 --- a/Shared/Playlists/PlaylistsView.swift +++ b/Shared/Playlists/PlaylistsView.swift @@ -14,9 +14,9 @@ struct PlaylistsView: View { @StateObject private var channelPlaylist = Store() @StateObject private var userPlaylist = Store() - @EnvironmentObject private var accounts - @EnvironmentObject private var player - @EnvironmentObject private var model + @ObservedObject private var accounts = AccountsModel.shared + private var player = PlayerModel.shared + @ObservedObject private var model = PlaylistsModel.shared @Namespace private var focusNamespace @@ -26,7 +26,7 @@ struct PlaylistsView: View { if videos.isEmpty { videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? [] - if !player.accounts.app.userPlaylistsEndpointIncludesVideos { + if !accounts.app.userPlaylistsEndpointIncludesVideos { var i = 0 for index in videos.indices { @@ -44,9 +44,9 @@ struct PlaylistsView: View { private var resource: Resource? { 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) } else { resource?.addObserver(userPlaylist) @@ -150,11 +150,9 @@ struct PlaylistsView: View { #if os(tvOS) .fullScreenCover(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { PlaylistFormView(playlist: $createdPlaylist) - .environmentObject(accounts) } .fullScreenCover(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { PlaylistFormView(playlist: $editedPlaylist) - .environmentObject(accounts) } .focusScope(focusNamespace) #else @@ -162,14 +160,12 @@ struct PlaylistsView: View { EmptyView() .sheet(isPresented: $showingNewPlaylist, onDismiss: selectCreatedPlaylist) { PlaylistFormView(playlist: $createdPlaylist) - .environmentObject(accounts) } ) .background( EmptyView() .sheet(isPresented: $showingEditPlaylist, onDismiss: selectEditedPlaylist) { PlaylistFormView(playlist: $editedPlaylist) - .environmentObject(accounts) } ) #endif diff --git a/Shared/Search/SearchField.swift b/Shared/Search/SearchField.swift index 4a3622fd..4ba64491 100644 --- a/Shared/Search/SearchField.swift +++ b/Shared/Search/SearchField.swift @@ -4,9 +4,8 @@ import SwiftUI struct SearchTextField: View { @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var navigation - @EnvironmentObject private var recents - @EnvironmentObject private var state + private var navigation = NavigationModel.shared + @ObservedObject private var state = SearchModel.shared @Binding var favoriteItem: FavoriteItem? @@ -34,7 +33,7 @@ struct SearchTextField: View { query.query = state.queryText navigation.hideKeyboard() } - recents.addQuery(state.queryText, navigation: navigation) + RecentsModel.shared.addQuery(state.queryText) } .disableAutocorrection(true) #if os(macOS) diff --git a/Shared/Search/SearchSuggestions.swift b/Shared/Search/SearchSuggestions.swift index be7be220..f885e1d3 100644 --- a/Shared/Search/SearchSuggestions.swift +++ b/Shared/Search/SearchSuggestions.swift @@ -1,9 +1,7 @@ import SwiftUI struct SearchSuggestions: View { - @EnvironmentObject private var navigation - @EnvironmentObject private var recents - @EnvironmentObject private var state + @ObservedObject private var state = SearchModel.shared var body: some View { List { @@ -80,10 +78,10 @@ struct SearchSuggestions: View { state.changeQuery { query in query.query = queryText - navigation.hideKeyboard() + NavigationModel.shared.hideKeyboard() } - recents.addQuery(queryText, navigation: navigation) + RecentsModel.shared.addQuery(queryText) } private var visibleSuggestions: [String] { diff --git a/Shared/Search/SearchView.swift b/Shared/Search/SearchView.swift index 846f7b5a..234ea64d 100644 --- a/Shared/Search/SearchView.swift +++ b/Shared/Search/SearchView.swift @@ -14,17 +14,15 @@ struct SearchView: View { #if os(tvOS) @State private var searchDebounce = Debounce() @State private var recentsDebounce = Debounce() + private var recents = RecentsModel.shared #endif @State private var favoriteItem: FavoriteItem? @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var state + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var state = SearchModel.shared private var favorites = FavoritesModel.shared @Default(.recentlyOpened) private var recentlyOpened @@ -313,20 +311,17 @@ struct SearchView: View { case .query: state.queryText = item.title state.changeQuery { query in query.query = item.title } - navigation.hideKeyboard() + NavigationModel.shared.hideKeyboard() updateFavoriteItem() - recents.add(item) + RecentsModel.shared.add(item) case .channel: guard let channel = item.channel else { return } - NavigationModel.openChannel( + NavigationModel.shared.openChannel( channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) case .playlist: @@ -334,11 +329,8 @@ struct SearchView: View { return } - NavigationModel.openChannelPlaylist( + NavigationModel.shared.openChannelPlaylist( playlist, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } @@ -359,7 +351,7 @@ struct SearchView: View { private func removeButton(_ item: RecentItem) -> some View { Button { - recents.close(item) + RecentsModel.shared.close(item) recentsChanged.toggle() } label: { Label("Remove", systemImage: "trash") @@ -368,12 +360,12 @@ struct SearchView: View { private var clearHistoryButton: some View { Button { - navigation.presentAlert( + NavigationModel.shared.presentAlert( Alert( title: Text("Are you sure you want to clear search history?"), message: Text("This cannot be reverted"), primaryButton: .destructive(Text("Clear")) { - recents.clear() + RecentsModel.shared.clear() recentsChanged.toggle() }, secondaryButton: .cancel() diff --git a/Shared/Settings/AccountsNavigationLink.swift b/Shared/Settings/AccountsNavigationLink.swift index 9290f284..044add7b 100644 --- a/Shared/Settings/AccountsNavigationLink.swift +++ b/Shared/Settings/AccountsNavigationLink.swift @@ -1,7 +1,7 @@ import SwiftUI struct AccountsNavigationLink: View { - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared var instance: Instance var body: some View { @@ -30,6 +30,6 @@ struct AccountsNavigationLink: View { if accounts.current?.instance == instance { accounts.setCurrent(nil) } - InstancesModel.remove(instance) + InstancesModel.shared.remove(instance) } } diff --git a/Shared/Settings/AdvancedSettings.swift b/Shared/Settings/AdvancedSettings.swift index 88db4b95..99a6b96c 100644 --- a/Shared/Settings/AdvancedSettings.swift +++ b/Shared/Settings/AdvancedSettings.swift @@ -9,11 +9,6 @@ struct AdvancedSettings: View { @Default(.countryOfPublicInstances) private var countryOfPublicInstances @Default(.instances) private var instances - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var settings - @State private var countries = [String]() @State private var filesToShare = [MPVClient.logFile] @State private var presentingInstanceForm = false @@ -39,7 +34,7 @@ struct AdvancedSettings: View { #endif } .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) { InstanceForm(savedInstanceID: $savedFormInstanceID) diff --git a/Shared/Settings/BrowsingSettings.swift b/Shared/Settings/BrowsingSettings.swift index 819b02b2..a62f8d24 100644 --- a/Shared/Settings/BrowsingSettings.swift +++ b/Shared/Settings/BrowsingSettings.swift @@ -22,7 +22,7 @@ struct BrowsingSettings: View { @Default(.homeHistoryItems) private var homeHistoryItems @Default(.visibleSections) private var visibleSections - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared @State private var homeHistoryItemsText = "" #if os(iOS) diff --git a/Shared/Settings/EditFavorites.swift b/Shared/Settings/EditFavorites.swift index c316c4dd..c3b64f5a 100644 --- a/Shared/Settings/EditFavorites.swift +++ b/Shared/Settings/EditFavorites.swift @@ -2,8 +2,7 @@ import Defaults import SwiftUI struct EditFavorites: View { - @EnvironmentObject private var playlistsModel - + private var playlistsModel = PlaylistsModel.shared private var model = FavoritesModel.shared @Default(.favorites) private var favorites diff --git a/Shared/Settings/HistorySettings.swift b/Shared/Settings/HistorySettings.swift index fae004a9..869233d0 100644 --- a/Shared/Settings/HistorySettings.swift +++ b/Shared/Settings/HistorySettings.swift @@ -4,8 +4,8 @@ import SwiftUI struct HistorySettings: View { static let watchedThresholds = [50, 60, 70, 80, 90, 95, 100] - @EnvironmentObject private var player - @EnvironmentObject private var settings + private var player = PlayerModel.shared + private var settings = SettingsModel.shared @Default(.saveRecents) private var saveRecents @Default(.saveLastPlayed) private var saveLastPlayed diff --git a/Shared/Settings/InstanceForm.swift b/Shared/Settings/InstanceForm.swift index a7d97825..2cb08e49 100644 --- a/Shared/Settings/InstanceForm.swift +++ b/Shared/Settings/InstanceForm.swift @@ -16,7 +16,7 @@ struct InstanceForm: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.presentationMode) private var presentationMode - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared var body: some View { VStack(alignment: .leading) { @@ -140,7 +140,7 @@ struct InstanceForm: View { 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 if accounts.isEmpty { diff --git a/Shared/Settings/InstanceSettings.swift b/Shared/Settings/InstanceSettings.swift index 1d9f6ded..ff5054e6 100644 --- a/Shared/Settings/InstanceSettings.swift +++ b/Shared/Settings/InstanceSettings.swift @@ -13,7 +13,7 @@ struct InstanceSettings: View { List { Section(header: Text("Accounts".localized())) { 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) Button(account.description) {} .contextMenu { @@ -70,7 +70,7 @@ struct InstanceSettings: View { frontendURL = instance.frontendURL ?? "" } .onChange(of: frontendURL) { newValue in - InstancesModel.setFrontendURL(instance, newValue) + InstancesModel.shared.setFrontendURL(instance, newValue) } .labelsHidden() .autocapitalization(.none) @@ -84,7 +84,7 @@ struct InstanceSettings: View { proxiesVideos = instance.proxiesVideos } .onChange(of: proxiesVideos) { newValue in - InstancesModel.setProxiesVideos(instance, newValue) + InstancesModel.shared.setProxiesVideos(instance, newValue) } } } diff --git a/Shared/Settings/LocationsSettings.swift b/Shared/Settings/LocationsSettings.swift index 79f7db6f..fcf1e55b 100644 --- a/Shared/Settings/LocationsSettings.swift +++ b/Shared/Settings/LocationsSettings.swift @@ -6,9 +6,8 @@ struct LocationsSettings: View { @State private var presentingInstanceForm = false @State private var savedFormInstanceID: Instance.ID? - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var model + @ObservedObject private var accounts = AccountsModel.shared + private var model = SettingsModel.shared @Default(.countryOfPublicInstances) private var countryOfPublicInstances @Default(.instances) private var instances @@ -30,7 +29,7 @@ struct LocationsSettings: View { } .onAppear(perform: loadCountries) .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 countryOfPublicInstances = nil @@ -74,7 +73,7 @@ struct LocationsSettings: View { .disabled(countries.isEmpty) Button { - InstancesManifest.shared.changePublicAccount(accounts, settings: model) + InstancesManifest.shared.changePublicAccount() } label: { if let account = accounts.current, account.isPublic { Text("Switch to other public location") @@ -89,7 +88,6 @@ struct LocationsSettings: View { Section(header: SettingsHeader(text: "Custom Locations".localized())) { #if os(macOS) InstancesSettings() - .environmentObject(model) #else ForEach(instances) { instance in AccountsNavigationLink(instance: instance) @@ -137,8 +135,5 @@ struct LocationsSettings: View { struct LocationsSettings_Previews: PreviewProvider { static var previews: some View { LocationsSettings() - .environmentObject(AccountsModel()) - .environmentObject(NavigationModel()) - .environmentObject(SettingsModel()) } } diff --git a/Shared/Settings/PlayerSettings.swift b/Shared/Settings/PlayerSettings.swift index 6d4759bc..b2347130 100644 --- a/Shared/Settings/PlayerSettings.swift +++ b/Shared/Settings/PlayerSettings.swift @@ -35,8 +35,8 @@ struct PlayerSettings: View { @Default(.enableReturnYouTubeDislike) private var enableReturnYouTubeDislike @Default(.systemControlsCommands) private var systemControlsCommands - @EnvironmentObject private var accounts - @EnvironmentObject private var player + @ObservedObject private var accounts = AccountsModel.shared + private var player = PlayerModel.shared #if os(iOS) private var idiom: UIUserInterfaceIdiom { diff --git a/Shared/Settings/QualitySettings.swift b/Shared/Settings/QualitySettings.swift index 08ff73a6..afe8ae8d 100644 --- a/Shared/Settings/QualitySettings.swift +++ b/Shared/Settings/QualitySettings.swift @@ -5,7 +5,7 @@ struct QualitySettings: View { @State private var presentingProfileForm = false @State private var editedProfileID: QualityProfile.ID? - @EnvironmentObject private var settings + @ObservedObject private var settings = SettingsModel.shared @Default(.qualityProfiles) private var qualityProfiles @Default(.batteryCellularProfile) private var batteryCellularProfile diff --git a/Shared/Settings/SettingsView.swift b/Shared/Settings/SettingsView.swift index f3a43cef..8e0a01b0 100644 --- a/Shared/Settings/SettingsView.swift +++ b/Shared/Settings/SettingsView.swift @@ -19,15 +19,13 @@ struct SettingsView: View { @Environment(\.presentationMode) private var presentationMode #endif - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var model + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var model = SettingsModel.shared @Default(.instances) private var instances var body: some View { settings - .environmentObject(model) .alert(isPresented: $model.presentingAlert) { model.alert } } diff --git a/Shared/Trending/TrendingView.swift b/Shared/Trending/TrendingView.swift index c5b740d9..bd97ea45 100644 --- a/Shared/Trending/TrendingView.swift +++ b/Shared/Trending/TrendingView.swift @@ -13,7 +13,7 @@ struct TrendingView: View { @State private var favoriteItem: FavoriteItem? - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared var trending: [ContentItem] { ContentItem.array(of: store.collection) diff --git a/Shared/Videos/VideoCell.swift b/Shared/Videos/VideoCell.swift index c7b3ab31..b8718198 100644 --- a/Shared/Videos/VideoCell.swift +++ b/Shared/Videos/VideoCell.swift @@ -14,9 +14,7 @@ struct VideoCell: View { @Environment(\.verticalSizeClass) private var verticalSizeClass #endif - @EnvironmentObject private var accounts - @EnvironmentObject private var recents - @EnvironmentObject private var thumbnails + @ObservedObject var thumbnails = ThumbnailsModel.shared @Default(.channelOnThumbnail) private var channelOnThumbnail @Default(.timeOnThumbnail) private var timeOnThumbnail @@ -53,7 +51,6 @@ struct VideoCell: View { .contentShape(RoundedRectangle(cornerRadius: thumbnailRoundingCornerRadius)) .contextMenu { VideoContextMenuView(video: video) - .environmentObject(accounts) } } @@ -310,11 +307,8 @@ struct VideoCell: View { return } - NavigationModel.openChannel( + NavigationModel.shared.openChannel( video.channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } label: { diff --git a/Shared/Views/ChannelCell.swift b/Shared/Views/ChannelCell.swift index 6b634a70..abacc5ae 100644 --- a/Shared/Views/ChannelCell.swift +++ b/Shared/Views/ChannelCell.swift @@ -7,17 +7,10 @@ struct ChannelCell: View { @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - var body: some View { Button { - NavigationModel.openChannel( + NavigationModel.shared.openChannel( channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } label: { diff --git a/Shared/Views/ChannelPlaylistCell.swift b/Shared/Views/ChannelPlaylistCell.swift index 45f9de17..395f8d84 100644 --- a/Shared/Views/ChannelPlaylistCell.swift +++ b/Shared/Views/ChannelPlaylistCell.swift @@ -6,13 +6,12 @@ struct ChannelPlaylistCell: View { @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var navigation - @EnvironmentObject private var recents + var navigation = NavigationModel.shared var body: some View { Button { let recent = RecentItem(from: playlist) - recents.add(recent) + RecentsModel.shared.add(recent) navigation.presentingPlaylist = true if navigationStyle == .sidebar { diff --git a/Shared/Views/ChannelPlaylistView.swift b/Shared/Views/ChannelPlaylistView.swift index 187f64c7..cd7e0d79 100644 --- a/Shared/Views/ChannelPlaylistView.swift +++ b/Shared/Views/ChannelPlaylistView.swift @@ -12,10 +12,9 @@ struct ChannelPlaylistView: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents + @ObservedObject private var accounts = AccountsModel.shared + var player = PlayerModel.shared + @ObservedObject private var recents = RecentsModel.shared private var items: [ContentItem] { ContentItem.array(of: store.item?.videos ?? []) @@ -89,7 +88,7 @@ struct ChannelPlaylistView: View { if navigationStyle == .tab { Button("Done") { withAnimation(Constants.overlayAnimation) { - navigation.presentingPlaylist = false + NavigationModel.shared.presentingPlaylist = false } } } diff --git a/Shared/Views/ChannelVideosView.swift b/Shared/Views/ChannelVideosView.swift index 123dd4d4..4911afbe 100644 --- a/Shared/Views/ChannelVideosView.swift +++ b/Shared/Views/ChannelVideosView.swift @@ -15,14 +15,12 @@ struct ChannelVideosView: View { #if os(iOS) @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @EnvironmentObject private var player #endif - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions - + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + @ObservedObject private var recents = RecentsModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared @Namespace private var focusNamespace var presentedChannel: Channel? { diff --git a/Shared/Views/ControlsBar.swift b/Shared/Views/ControlsBar.swift index b0efff3f..43a2668e 100644 --- a/Shared/Views/ControlsBar.swift +++ b/Shared/Views/ControlsBar.swift @@ -10,12 +10,11 @@ struct ControlsBar: View { @Environment(\.navigationStyle) private var navigationStyle - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var model - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions + @ObservedObject private var accounts = AccountsModel.shared + var navigation = NavigationModel.shared + @ObservedObject private var model = PlayerModel.shared + @ObservedObject private var playlists = PlaylistsModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared @ObservedObject private var controls = PlayerControlsModel.shared @@ -139,11 +138,8 @@ struct ControlsBar: View { HStack(spacing: 8) { Button { if let video = model.currentVideo, !video.isLocal { - NavigationModel.openChannel( + navigation.openChannel( video.channel, - player: model, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } @@ -179,7 +175,7 @@ struct ControlsBar: View { if let playlist = playlists.lastUsed, let video = model.currentVideo { Button { - playlists.addVideo(playlistID: playlist.id, videoID: video.videoID, navigation: navigation) + playlists.addVideo(playlistID: playlist.id, videoID: video.videoID) } label: { Label("Add to \(playlist.title)", systemImage: "text.badge.star") } @@ -194,11 +190,8 @@ struct ControlsBar: View { Section { if !video.isLocal { Button { - NavigationModel.openChannel( + navigation.openChannel( video.channel, - player: model, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } label: { diff --git a/Shared/Views/MPVPlayerView.swift b/Shared/Views/MPVPlayerView.swift index 9d93b3ff..04287ef4 100644 --- a/Shared/Views/MPVPlayerView.swift +++ b/Shared/Views/MPVPlayerView.swift @@ -2,10 +2,8 @@ import SwiftUI #if !os(macOS) struct MPVPlayerView: UIViewControllerRepresentable { - @EnvironmentObject private var player - func makeUIViewController(context _: Context) -> some UIViewController { - player.mpvController + PlayerModel.shared.mpvController } func updateUIViewController(_: UIViewControllerType, context _: Context) {} @@ -15,10 +13,8 @@ import SwiftUI @State private var client = MPVClient() @State private var layer = VideoLayer() - @EnvironmentObject private var player - func makeNSView(context _: Context) -> some NSView { - player.mpvBackend.client = client + PlayerModel.shared.mpvBackend.client = client let view = MPVOGLView() diff --git a/Shared/Views/OpenSettingsButton.swift b/Shared/Views/OpenSettingsButton.swift index 4607de33..4bb3f98a 100644 --- a/Shared/Views/OpenSettingsButton.swift +++ b/Shared/Views/OpenSettingsButton.swift @@ -4,7 +4,7 @@ struct OpenSettingsButton: View { @Environment(\.presentationMode) private var presentationMode #if !os(macOS) - @EnvironmentObject private var navigation + private var navigation: NavigationModel { .shared } #endif var body: some View { diff --git a/Shared/Views/OpenVideosView.swift b/Shared/Views/OpenVideosView.swift index 29902c76..53e94d79 100644 --- a/Shared/Views/OpenVideosView.swift +++ b/Shared/Views/OpenVideosView.swift @@ -6,11 +6,7 @@ struct OpenVideosView: View { @State private var playbackMode = OpenVideosModel.PlaybackMode.playNow @State private var removeQueueItems = false - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var search + @ObservedObject private var navigation = NavigationModel.shared @Environment(\.openURL) private var openURL @Environment(\.presentationMode) private var presentationMode @@ -132,8 +128,8 @@ struct OpenVideosView: View { openURLs(urlsToOpen) } catch { - NavigationModel.shared.alert = Alert(title: Text("Could not open Files")) - NavigationModel.shared.presentingAlertInOpenVideos = true + navigation.alert = Alert(title: Text("Could not open Files")) + navigation.presentingAlertInOpenVideos = true } presentationMode.wrappedValue.dismiss() diff --git a/Shared/Views/PlaylistVideosView.swift b/Shared/Views/PlaylistVideosView.swift index 202f2bb0..5696b0a9 100644 --- a/Shared/Views/PlaylistVideosView.swift +++ b/Shared/Views/PlaylistVideosView.swift @@ -4,8 +4,9 @@ import SwiftUI struct PlaylistVideosView: View { let playlist: Playlist - @EnvironmentObject private var player - @EnvironmentObject private var model + @ObservedObject private var accounts = AccountsModel.shared + var player = PlayerModel.shared + @ObservedObject private var model = PlaylistsModel.shared @StateObject private var channelPlaylist = Store() @StateObject private var userPlaylist = Store() @@ -15,7 +16,7 @@ struct PlaylistVideosView: View { if videos.isEmpty { videos = userPlaylist.item?.videos ?? channelPlaylist.item?.videos ?? [] - if !player.accounts.app.userPlaylistsEndpointIncludesVideos { + if !accounts.app.userPlaylistsEndpointIncludesVideos { var i = 0 for index in videos.indices { @@ -31,9 +32,9 @@ struct PlaylistVideosView: View { } 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) } else { resource?.addObserver(userPlaylist) diff --git a/Shared/Views/PopularView.swift b/Shared/Views/PopularView.swift index bb0f0a34..bbeb4a23 100644 --- a/Shared/Views/PopularView.swift +++ b/Shared/Views/PopularView.swift @@ -4,7 +4,7 @@ import SwiftUI struct PopularView: View { @StateObject private var store = Store<[Video]>() - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared var resource: Resource? { accounts.api.popular diff --git a/Shared/Views/ShareButton.swift b/Shared/Views/ShareButton.swift index 62d4304b..cd96cb04 100644 --- a/Shared/Views/ShareButton.swift +++ b/Shared/Views/ShareButton.swift @@ -3,9 +3,9 @@ import SwiftUI struct ShareButton: View { let contentItem: ContentItem - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player + @ObservedObject private var accounts = AccountsModel.shared + private var navigation: NavigationModel { .shared } + @ObservedObject private var player = PlayerModel.shared let label: LabelView? diff --git a/Shared/Views/SignInRequiredView.swift b/Shared/Views/SignInRequiredView.swift index 6b414b89..8f153e8f 100644 --- a/Shared/Views/SignInRequiredView.swift +++ b/Shared/Views/SignInRequiredView.swift @@ -5,7 +5,7 @@ struct SignInRequiredView: View { let title: String let content: Content - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared @Default(.instances) private var instances diff --git a/Shared/Views/SubscriptionsView.swift b/Shared/Views/SubscriptionsView.swift index 91ea419c..b0661479 100644 --- a/Shared/Views/SubscriptionsView.swift +++ b/Shared/Views/SubscriptionsView.swift @@ -4,7 +4,7 @@ import SwiftUI struct SubscriptionsView: View { @StateObject private var store = Store<[Video]>() - @EnvironmentObject private var accounts + @ObservedObject private var accounts = AccountsModel.shared var feed: Resource? { accounts.api.feed diff --git a/Shared/Views/VideoContextMenuView.swift b/Shared/Views/VideoContextMenuView.swift index 1c2edacd..f9d36a3f 100644 --- a/Shared/Views/VideoContextMenuView.swift +++ b/Shared/Views/VideoContextMenuView.swift @@ -11,12 +11,11 @@ struct VideoContextMenuView: View { @Environment(\.navigationStyle) private var navigationStyle @Environment(\.currentPlaylistID) private var playlistID - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var playlists - @EnvironmentObject private var recents - @EnvironmentObject private var subscriptions + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + @ObservedObject private var player = PlayerModel.shared + @ObservedObject private var playlists = PlaylistsModel.shared + @ObservedObject private var subscriptions = SubscriptionsModel.shared @FetchRequest private var watchRequest: FetchedResults @@ -261,11 +260,8 @@ struct VideoContextMenuView: View { private var openChannelButton: some View { Button { - NavigationModel.openChannel( + NavigationModel.shared.openChannel( video.channel, - player: player, - recents: recents, - navigation: navigation, navigationStyle: navigationStyle ) } label: { @@ -308,7 +304,7 @@ struct VideoContextMenuView: View { @ViewBuilder private var addToLastPlaylistButton: some View { if let playlist = playlists.lastUsed { Button { - playlists.addVideo(playlistID: playlist.id, videoID: video.videoID, navigation: navigation) + playlists.addVideo(playlistID: playlist.id, videoID: video.videoID) } label: { Label("Add to \(playlist.title)", systemImage: "text.badge.star") } diff --git a/Shared/Views/WelcomeScreen.swift b/Shared/Views/WelcomeScreen.swift index df6adadd..734658cd 100644 --- a/Shared/Views/WelcomeScreen.swift +++ b/Shared/Views/WelcomeScreen.swift @@ -5,7 +5,6 @@ import SwiftUI struct WelcomeScreen: View { @Environment(\.presentationMode) private var presentationMode - @EnvironmentObject private var accounts @State private var store = [ManifestedInstance]() var body: some View { @@ -25,7 +24,7 @@ struct WelcomeScreen: View { ForEach(countries, id: \.self) { country in Button { Defaults[.countryOfPublicInstances] = country - InstancesManifest.shared.setPublicAccount(country, accounts: accounts) + InstancesManifest.shared.setPublicAccount(country) presentationMode.wrappedValue.dismiss() } label: { diff --git a/Shared/YatteeApp.swift b/Shared/YatteeApp.swift index ace592e7..a1c86a79 100644 --- a/Shared/YatteeApp.swift +++ b/Shared/YatteeApp.swift @@ -32,19 +32,16 @@ struct YatteeApp: App { @State private var configured = false - @StateObject private var accounts = AccountsModel() - @StateObject private var comments = CommentsModel() - @StateObject private var instances = InstancesModel() - @StateObject private var menu = MenuModel() - @StateObject private var navigation = NavigationModel() - @StateObject private var networkState = NetworkStateModel() - @StateObject private var player = PlayerModel() - @StateObject private var playlists = PlaylistsModel() - @StateObject private var recents = RecentsModel() - @StateObject private var search = SearchModel() - @StateObject private var settings = SettingsModel() - @StateObject private var subscriptions = SubscriptionsModel() - @StateObject private var thumbnails = ThumbnailsModel() + @StateObject private var comments = CommentsModel.shared + @StateObject private var instances = InstancesModel.shared + @StateObject private var menu = MenuModel.shared + @StateObject private var networkState = NetworkStateModel.shared + @StateObject private var player = PlayerModel.shared + @StateObject private var playlists = PlaylistsModel.shared + @StateObject private var recents = RecentsModel.shared + @StateObject private var settings = SettingsModel.shared + @StateObject private var subscriptions = SubscriptionsModel.shared + @StateObject private var thumbnails = ThumbnailsModel.shared let persistenceController = PersistenceController.shared @@ -55,20 +52,6 @@ struct YatteeApp: App { ContentView() .onAppear(perform: configure) .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) .background( HostingWindowFinder { window in @@ -100,7 +83,7 @@ struct YatteeApp: App { CommandGroup(replacing: .newItem, addition: {}) - MenuCommands(model: Binding(get: { menu }, set: { _ in })) + MenuCommands(model: Binding(get: { MenuModel.shared }, set: { _ in })) } #endif @@ -127,18 +110,7 @@ struct YatteeApp: App { .onDisappear { player.presentingPlayer = false } .environment(\.managedObjectContext, persistenceController.container.viewContext) .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(matching: Set(["player", "*"])) @@ -146,12 +118,6 @@ struct YatteeApp: App { Settings { SettingsView() .environment(\.managedObjectContext, persistenceController.container.viewContext) - .environmentObject(accounts) - .environmentObject(instances) - .environmentObject(navigation) - .environmentObject(player) - .environmentObject(playerControls) - .environmentObject(settings) } #endif } @@ -171,39 +137,14 @@ struct YatteeApp: App { migrateAccounts() if !Defaults[.lastAccountIsPublic] { - accounts.configureAccount() + AccountsModel.shared.configureAccount() } 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 - 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 { + if !AccountsModel.shared.current.isNil { player.restoreQueue() } @@ -219,9 +160,7 @@ struct YatteeApp: App { } #endif - navigation.tabSelection = section ?? .search - - NavigationModel.shared = navigation + NavigationModel.shared.tabSelection = section ?? .search subscriptions.load() playlists.load() diff --git a/macOS/AppleAVPlayerView.swift b/macOS/AppleAVPlayerView.swift index 800cc379..da1bb45c 100644 --- a/macOS/AppleAVPlayerView.swift +++ b/macOS/AppleAVPlayerView.swift @@ -2,14 +2,8 @@ import Defaults import SwiftUI struct AppleAVPlayerView: NSViewRepresentable { - @EnvironmentObject private var player - func makeNSView(context _: Context) -> some NSView { - let playerLayerView = PlayerLayerView(frame: .zero) - - playerLayerView.player = player - - return playerLayerView + PlayerLayerView(frame: .zero) } func updateNSView(_: NSViewType, context _: Context) {} diff --git a/macOS/InstancesSettings.swift b/macOS/InstancesSettings.swift index 8e8ce08f..1a1c247b 100644 --- a/macOS/InstancesSettings.swift +++ b/macOS/InstancesSettings.swift @@ -13,8 +13,8 @@ struct InstancesSettings: View { @State private var proxiesVideos = false @Environment(\.colorScheme) private var colorScheme - @EnvironmentObject private var accounts - @EnvironmentObject private var settings + @ObservedObject private var accounts = AccountsModel.shared + private var settings = SettingsModel.shared @Default(.instances) private var instances @@ -86,7 +86,7 @@ struct InstancesSettings: View { frontendURL = selectedInstanceFrontendURL } .onChange(of: frontendURL) { newValue in - InstancesModel.setFrontendURL(selectedInstance, newValue) + InstancesModel.shared.setFrontendURL(selectedInstance, newValue) } .labelsHidden() @@ -101,7 +101,7 @@ struct InstancesSettings: View { proxiesVideos = selectedInstance.proxiesVideos } .onChange(of: proxiesVideos) { newValue in - InstancesModel.setProxiesVideos(selectedInstance, newValue) + InstancesModel.shared.setProxiesVideos(selectedInstance, newValue) } } @@ -134,7 +134,7 @@ struct InstancesSettings: View { accounts.setCurrent(nil) } - InstancesModel.remove(selectedInstance!) + InstancesModel.shared.remove(selectedInstance!) selectedInstanceID = instances.last?.id }, secondaryButton: .cancel() @@ -170,7 +170,7 @@ struct InstancesSettings: View { } var selectedInstance: Instance! { - InstancesModel.find(selectedInstanceID) + InstancesModel.shared.find(selectedInstanceID) } var selectedInstanceFrontendURL: String { @@ -182,7 +182,7 @@ struct InstancesSettings: View { return [] } - return InstancesModel.accounts(selectedInstanceID) + return InstancesModel.shared.accounts(selectedInstanceID) } private var proxiesVideosToggle: some View { diff --git a/tvOS/AccountSelectionView.swift b/tvOS/AccountSelectionView.swift index e3138049..60237313 100644 --- a/tvOS/AccountSelectionView.swift +++ b/tvOS/AccountSelectionView.swift @@ -5,7 +5,7 @@ import SwiftUI struct AccountSelectionView: View { var showHeader = true - @EnvironmentObject private var accountsModel + @ObservedObject private var accountsModel = AccountsModel.shared @Default(.accounts) private var accounts @Default(.instances) private var instances diff --git a/tvOS/NowPlayingView.swift b/tvOS/NowPlayingView.swift index 325bd021..46adfcfc 100644 --- a/tvOS/NowPlayingView.swift +++ b/tvOS/NowPlayingView.swift @@ -15,9 +15,8 @@ struct NowPlayingView: View { @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) var watches: FetchedResults - @EnvironmentObject private var comments - @EnvironmentObject private var player - @EnvironmentObject private var recents + @ObservedObject private var comments = CommentsModel.shared + @ObservedObject private var player = PlayerModel.shared @Default(.saveHistory) private var saveHistory diff --git a/tvOS/TVNavigationView.swift b/tvOS/TVNavigationView.swift index b2ebbcc1..02f91d89 100644 --- a/tvOS/TVNavigationView.swift +++ b/tvOS/TVNavigationView.swift @@ -2,11 +2,10 @@ import Defaults import SwiftUI struct TVNavigationView: View { - @EnvironmentObject private var accounts - @EnvironmentObject private var navigation - @EnvironmentObject private var player - @EnvironmentObject private var recents - @EnvironmentObject private var settings + @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var navigation = NavigationModel.shared + @ObservedObject private var player = PlayerModel.shared + @ObservedObject private var recents = RecentsModel.shared @Default(.visibleSections) private var visibleSections