mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Replace environment objects with observed objects
This commit is contained in:
@@ -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)
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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() {
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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<T: ObservableObject>(_ model: T?) {
|
||||
guard !model.isNil else {
|
||||
return
|
||||
|
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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?
|
||||
|
@@ -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 {
|
||||
|
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 }
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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)"
|
||||
)
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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"))
|
||||
|
||||
|
@@ -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() }
|
||||
}
|
||||
|
@@ -2,6 +2,8 @@ import Defaults
|
||||
import Foundation
|
||||
|
||||
final class ThumbnailsModel: ObservableObject {
|
||||
static var shared = ThumbnailsModel()
|
||||
|
||||
@Published var unloadable = Set<URL>()
|
||||
|
||||
func insertUnloadable(_ url: URL) {
|
||||
|
Reference in New Issue
Block a user