swiftformat
This commit is contained in:
parent
961e639970
commit
4298062ca3
61
.swiftformat
61
.swiftformat
|
@ -1,18 +1,49 @@
|
||||||
# version: 0.47.5
|
# version: 0.47.5
|
||||||
|
|
||||||
--indent 4 #indent
|
--swiftversion 5.5
|
||||||
--self init-only # redundantSelf
|
|
||||||
--semicolons never # semicolons
|
--indent tab
|
||||||
--stripunusedargs closure-only # unusedArguments
|
--tabwidth 4
|
||||||
--maxwidth 140 #wrap
|
--xcodeindentation enabled
|
||||||
--assetliterals visual-width #wrap
|
--self init-only
|
||||||
--wraparguments after-first # wrapArguments
|
--semicolons never
|
||||||
--wrapparameters after-first # wrapArguments
|
--stripunusedargs closure-only
|
||||||
--wrapcollections before-first # wrapArguments
|
--maxwidth 140
|
||||||
--wrapconditions after-first # wrapArguments
|
--assetliterals visual-width
|
||||||
--funcattributes prev-line # wrapAttributes
|
--wraparguments after-first
|
||||||
--typeattributes prev-line # wrapAttributes
|
--wrapparameters after-first
|
||||||
--varattributes prev-line # wrapAttributes
|
--wrapcollections before-first
|
||||||
|
--wrapconditions after-first
|
||||||
|
--funcattributes prev-line
|
||||||
|
--typeattributes prev-line
|
||||||
|
--varattributes prev-line
|
||||||
|
--trailingclosures
|
||||||
|
--shortoptionals "always"
|
||||||
|
|
||||||
|
--enable isEmpty, \
|
||||||
|
leadingDelimiters, \
|
||||||
|
wrapEnumCases, \
|
||||||
|
typeSugar, \
|
||||||
|
void, \
|
||||||
|
trailingSpace, \
|
||||||
|
spaceInsideParens, \
|
||||||
|
spaceInsideGenerics, \
|
||||||
|
spaceInsideComments, \
|
||||||
|
spaceInsideBrackets, \
|
||||||
|
spaceInsideBraces, \
|
||||||
|
blankLinesAroundMark, \
|
||||||
|
redundantLet, \
|
||||||
|
redundantInit, \
|
||||||
|
blankLinesAroundMark
|
||||||
|
|
||||||
|
--disable strongOutlets, \
|
||||||
|
yodaConditions, \
|
||||||
|
blankLinesAtStartOfScope,\
|
||||||
|
andOperator, \
|
||||||
|
redundantFileprivate, \
|
||||||
|
redundantSelf
|
||||||
|
|
||||||
|
--exclude Pods
|
||||||
|
|
||||||
|
--header "\nSwiftfin is subject to the terms of the Mozilla Public\nLicense, v2.0. If a copy of the MPL was not distributed with this\nfile, you can obtain one at https://mozilla.org/MPL/2.0/.\n\nCopyright (c) {year} Jellyfin & Jellyfin Contributors\n"
|
||||||
|
|
||||||
--enable isEmpty
|
|
||||||
--disable strongOutlets,yodaConditions
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,9 +14,11 @@ final class BasicAppSettingsCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \BasicAppSettingsCoordinator.start)
|
let stack = NavigationStack(initial: \BasicAppSettingsCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
BasicAppSettingsView(viewModel: BasicAppSettingsViewModel())
|
BasicAppSettingsView(viewModel: BasicAppSettingsViewModel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,14 +14,17 @@ final class ConnectToServerCoodinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \ConnectToServerCoodinator.start)
|
let stack = NavigationStack(initial: \ConnectToServerCoodinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var userSignIn = makeUserSignIn
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var userSignIn = makeUserSignIn
|
||||||
|
|
||||||
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
|
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
|
||||||
return UserSignInCoordinator(viewModel: .init(server: server))
|
UserSignInCoordinator(viewModel: .init(server: server))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
ConnectToServerView(viewModel: ConnectToServerViewModel())
|
ConnectToServerView(viewModel: ConnectToServerViewModel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -17,9 +16,11 @@ final class FilterCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \FilterCoordinator.start)
|
let stack = NavigationStack(initial: \FilterCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
@Binding var filters: LibraryFilters
|
@Binding
|
||||||
|
var filters: LibraryFilters
|
||||||
var enabledFilterType: [FilterType]
|
var enabledFilterType: [FilterType]
|
||||||
var parentId: String = ""
|
var parentId: String = ""
|
||||||
|
|
||||||
|
@ -29,7 +30,8 @@ final class FilterCoordinator: NavigationCoordinatable {
|
||||||
self.parentId = parentId
|
self.parentId = parentId
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
LibraryFilterView(filters: $filters, enabledFilterType: enabledFilterType, parentId: parentId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -16,12 +15,18 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \HomeCoordinator.start)
|
let stack = NavigationStack(initial: \HomeCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.modal) var settings = makeSettings
|
var start = makeStart
|
||||||
@Route(.push) var library = makeLibrary
|
@Route(.modal)
|
||||||
@Route(.push) var item = makeItem
|
var settings = makeSettings
|
||||||
@Route(.modal) var modalItem = makeModalItem
|
@Route(.push)
|
||||||
@Route(.modal) var modalLibrary = makeModalLibrary
|
var library = makeLibrary
|
||||||
|
@Route(.push)
|
||||||
|
var item = makeItem
|
||||||
|
@Route(.modal)
|
||||||
|
var modalItem = makeModalItem
|
||||||
|
@Route(.modal)
|
||||||
|
var modalLibrary = makeModalLibrary
|
||||||
|
|
||||||
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
||||||
NavigationViewCoordinator(SettingsCoordinator())
|
NavigationViewCoordinator(SettingsCoordinator())
|
||||||
|
@ -36,14 +41,15 @@ final class HomeCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
return NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
|
func makeModalLibrary(params: LibraryCoordinatorParams) -> NavigationViewCoordinator<LibraryCoordinator> {
|
||||||
return NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
|
NavigationViewCoordinator(LibraryCoordinator(viewModel: params.viewModel, title: params.title))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
HomeView()
|
HomeView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -16,11 +15,16 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \ItemCoordinator.start)
|
let stack = NavigationStack(initial: \ItemCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var item = makeItem
|
var start = makeStart
|
||||||
@Route(.push) var library = makeLibrary
|
@Route(.push)
|
||||||
@Route(.modal) var itemOverview = makeItemOverview
|
var item = makeItem
|
||||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
@Route(.modal)
|
||||||
|
var itemOverview = makeItemOverview
|
||||||
|
@Route(.fullScreen)
|
||||||
|
var videoPlayer = makeVideoPlayer
|
||||||
|
|
||||||
let itemDto: BaseItemDto
|
let itemDto: BaseItemDto
|
||||||
|
|
||||||
|
@ -44,7 +48,8 @@ final class ItemCoordinator: NavigationCoordinatable {
|
||||||
NavigationViewCoordinator(VideoPlayerCoordinator(viewModel: viewModel))
|
NavigationViewCoordinator(VideoPlayerCoordinator(viewModel: viewModel))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
ItemNavigationView(item: itemDto)
|
ItemNavigationView(item: itemDto)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
final class ItemOverviewCoordinator: NavigationCoordinatable {
|
final class ItemOverviewCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \ItemOverviewCoordinator.start)
|
let stack = NavigationStack(initial: \ItemOverviewCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
let item: BaseItemDto
|
let item: BaseItemDto
|
||||||
|
|
||||||
|
@ -23,7 +23,8 @@ final class ItemOverviewCoordinator: NavigationCoordinatable {
|
||||||
self.item = item
|
self.item = item
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
#if os(tvOS)
|
#if os(tvOS)
|
||||||
EmptyView()
|
EmptyView()
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -18,11 +17,16 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
let stack = NavigationStack(initial: \LibraryCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var search = makeSearch
|
var start = makeStart
|
||||||
@Route(.modal) var filter = makeFilter
|
@Route(.push)
|
||||||
@Route(.push) var item = makeItem
|
var search = makeSearch
|
||||||
@Route(.modal) var modalItem = makeModalItem
|
@Route(.modal)
|
||||||
|
var filter = makeFilter
|
||||||
|
@Route(.push)
|
||||||
|
var item = makeItem
|
||||||
|
@Route(.modal)
|
||||||
|
var modalItem = makeModalItem
|
||||||
|
|
||||||
let viewModel: LibraryViewModel
|
let viewModel: LibraryViewModel
|
||||||
let title: String
|
let title: String
|
||||||
|
@ -32,7 +36,8 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
LibraryView(viewModel: self.viewModel, title: title)
|
LibraryView(viewModel: self.viewModel, title: title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +56,6 @@ final class LibraryCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
return NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,9 +14,12 @@ final class LibraryListCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LibraryListCoordinator.start)
|
let stack = NavigationStack(initial: \LibraryListCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var search = makeSearch
|
var start = makeStart
|
||||||
@Route(.push) var library = makeLibrary
|
@Route(.push)
|
||||||
|
var search = makeSearch
|
||||||
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
|
||||||
let viewModel: LibraryListViewModel
|
let viewModel: LibraryListViewModel
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -15,12 +14,15 @@ import SwiftUI
|
||||||
final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
||||||
let stack = NavigationStack(initial: \LiveTVChannelsCoordinator.start)
|
let stack = NavigationStack(initial: \LiveTVChannelsCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.modal) var modalItem = makeModalItem
|
var start = makeStart
|
||||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
@Route(.modal)
|
||||||
|
var modalItem = makeModalItem
|
||||||
|
@Route(.fullScreen)
|
||||||
|
var videoPlayer = makeVideoPlayer
|
||||||
|
|
||||||
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
func makeModalItem(item: BaseItemDto) -> NavigationViewCoordinator<ItemCoordinator> {
|
||||||
return NavigationViewCoordinator(ItemCoordinator(item: item))
|
NavigationViewCoordinator(ItemCoordinator(item: item))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
||||||
|
@ -37,7 +39,8 @@ final class LiveTVChannelsCoordinator: NavigationCoordinatable {
|
||||||
final class EmptyViewCoordinator: NavigationCoordinatable {
|
final class EmptyViewCoordinator: NavigationCoordinatable {
|
||||||
let stack = NavigationStack(initial: \EmptyViewCoordinator.start)
|
let stack = NavigationStack(initial: \EmptyViewCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func makeStart() -> some View {
|
func makeStart() -> some View {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -16,8 +15,10 @@ final class LiveTVProgramsCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \LiveTVProgramsCoordinator.start)
|
let stack = NavigationStack(initial: \LiveTVProgramsCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.fullScreen) var videoPlayer = makeVideoPlayer
|
var start = makeStart
|
||||||
|
@Route(.fullScreen)
|
||||||
|
var videoPlayer = makeVideoPlayer
|
||||||
|
|
||||||
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
func makeVideoPlayer(item: BaseItemDto) -> NavigationViewCoordinator<EmptyViewCoordinator> {
|
||||||
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
|
// NavigationViewCoordinator(VideoPlayerCoordinator(item: item))
|
||||||
|
|
|
@ -1,32 +1,35 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
final class LiveTVTabCoordinator: TabCoordinatable {
|
final class LiveTVTabCoordinator: TabCoordinatable {
|
||||||
var child = TabChild(startingItems: [
|
var child = TabChild(startingItems: [
|
||||||
\LiveTVTabCoordinator.programs,
|
\LiveTVTabCoordinator.programs,
|
||||||
\LiveTVTabCoordinator.channels,
|
\LiveTVTabCoordinator.channels,
|
||||||
\LiveTVTabCoordinator.home
|
\LiveTVTabCoordinator.home,
|
||||||
])
|
])
|
||||||
|
|
||||||
@Route(tabItem: makeProgramsTab) var programs = makePrograms
|
@Route(tabItem: makeProgramsTab)
|
||||||
@Route(tabItem: makeChannelsTab) var channels = makeChannels
|
var programs = makePrograms
|
||||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
@Route(tabItem: makeChannelsTab)
|
||||||
|
var channels = makeChannels
|
||||||
|
@Route(tabItem: makeHomeTab)
|
||||||
|
var home = makeHome
|
||||||
|
|
||||||
func makePrograms() -> NavigationViewCoordinator<LiveTVProgramsCoordinator> {
|
func makePrograms() -> NavigationViewCoordinator<LiveTVProgramsCoordinator> {
|
||||||
return NavigationViewCoordinator(LiveTVProgramsCoordinator())
|
NavigationViewCoordinator(LiveTVProgramsCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeProgramsTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeProgramsTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "tv")
|
Image(systemName: "tv")
|
||||||
Text("Programs")
|
Text("Programs")
|
||||||
|
@ -34,10 +37,11 @@ final class LiveTVTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeChannels() -> NavigationViewCoordinator<LiveTVChannelsCoordinator> {
|
func makeChannels() -> NavigationViewCoordinator<LiveTVChannelsCoordinator> {
|
||||||
return NavigationViewCoordinator(LiveTVChannelsCoordinator())
|
NavigationViewCoordinator(LiveTVChannelsCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeChannelsTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeChannelsTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "square.grid.3x3")
|
Image(systemName: "square.grid.3x3")
|
||||||
Text("Channels")
|
Text("Channels")
|
||||||
|
@ -45,10 +49,11 @@ final class LiveTVTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeHome() -> LiveTVHomeView {
|
func makeHome() -> LiveTVHomeView {
|
||||||
return LiveTVHomeView()
|
LiveTVHomeView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeHomeTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "house")
|
Image(systemName: "house")
|
||||||
Text("Home")
|
Text("Home")
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
@ -18,8 +17,10 @@ import WidgetKit
|
||||||
final class MainCoordinator: NavigationCoordinatable {
|
final class MainCoordinator: NavigationCoordinatable {
|
||||||
var stack: NavigationStack<MainCoordinator>
|
var stack: NavigationStack<MainCoordinator>
|
||||||
|
|
||||||
@Root var mainTab = makeMainTab
|
@Root
|
||||||
@Root var serverList = makeServerList
|
var mainTab = makeMainTab
|
||||||
|
@Root
|
||||||
|
var serverList = makeServerList
|
||||||
|
|
||||||
private var cancellables = Set<AnyCancellable>()
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
@ -48,7 +49,8 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
nc.addObserver(self, selector: #selector(didLogIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
|
nc.addObserver(self, selector: #selector(didLogIn), name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
|
||||||
nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
||||||
nc.addObserver(self, selector: #selector(processDeepLink), name: SwiftfinNotificationCenter.Keys.processDeepLink, object: nil)
|
nc.addObserver(self, selector: #selector(processDeepLink), name: SwiftfinNotificationCenter.Keys.processDeepLink, object: nil)
|
||||||
nc.addObserver(self, selector: #selector(didChangeServerCurrentURI), name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: nil)
|
nc.addObserver(self, selector: #selector(didChangeServerCurrentURI),
|
||||||
|
name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: nil)
|
||||||
|
|
||||||
Defaults.publisher(.appAppearance)
|
Defaults.publisher(.appAppearance)
|
||||||
.sink { _ in
|
.sink { _ in
|
||||||
|
@ -57,17 +59,20 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didLogIn() {
|
@objc
|
||||||
|
func didLogIn() {
|
||||||
LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.")
|
LogManager.shared.log.info("Received `didSignIn` from SwiftfinNotificationCenter.")
|
||||||
root(\.mainTab)
|
root(\.mainTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didLogOut() {
|
@objc
|
||||||
|
func didLogOut() {
|
||||||
LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.")
|
LogManager.shared.log.info("Received `didSignOut` from SwiftfinNotificationCenter.")
|
||||||
root(\.serverList)
|
root(\.serverList)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func processDeepLink(_ notification: Notification) {
|
@objc
|
||||||
|
func processDeepLink(_ notification: Notification) {
|
||||||
guard let deepLink = notification.object as? DeepLink else { return }
|
guard let deepLink = notification.object as? DeepLink else { return }
|
||||||
if let coordinator = hasRoot(\.mainTab) {
|
if let coordinator = hasRoot(\.mainTab) {
|
||||||
switch deepLink {
|
switch deepLink {
|
||||||
|
@ -80,8 +85,10 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didChangeServerCurrentURI(_ notification: Notification) {
|
@objc
|
||||||
guard let newCurrentServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new current login state server") }
|
func didChangeServerCurrentURI(_ notification: Notification) {
|
||||||
|
guard let newCurrentServerState = notification.object as? SwiftfinStore.State.Server
|
||||||
|
else { fatalError("Need to have new current login state server") }
|
||||||
guard SessionManager.main.currentLogin != nil else { return }
|
guard SessionManager.main.currentLogin != nil else { return }
|
||||||
if newCurrentServerState.id == SessionManager.main.currentLogin.server.id {
|
if newCurrentServerState.id == SessionManager.main.currentLogin.server.id {
|
||||||
SessionManager.main.loginUser(server: newCurrentServerState, user: SessionManager.main.currentLogin.user)
|
SessionManager.main.loginUser(server: newCurrentServerState, user: SessionManager.main.currentLogin.user)
|
||||||
|
|
|
@ -1,44 +1,48 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
final class MainTabCoordinator: TabCoordinatable {
|
final class MainTabCoordinator: TabCoordinatable {
|
||||||
var child = TabChild(startingItems: [
|
var child = TabChild(startingItems: [
|
||||||
\MainTabCoordinator.home,
|
\MainTabCoordinator.home,
|
||||||
\MainTabCoordinator.allMedia
|
\MainTabCoordinator.allMedia,
|
||||||
])
|
])
|
||||||
|
|
||||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
@Route(tabItem: makeHomeTab)
|
||||||
@Route(tabItem: makeAllMediaTab) var allMedia = makeAllMedia
|
var home = makeHome
|
||||||
|
@Route(tabItem: makeAllMediaTab)
|
||||||
|
var allMedia = makeAllMedia
|
||||||
|
|
||||||
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
||||||
return NavigationViewCoordinator(HomeCoordinator())
|
NavigationViewCoordinator(HomeCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeHomeTab(isActive: Bool) -> some View {
|
||||||
Image(systemName: "house")
|
Image(systemName: "house")
|
||||||
L10n.home.text
|
L10n.home.text
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAllMedia() -> NavigationViewCoordinator<LibraryListCoordinator> {
|
func makeAllMedia() -> NavigationViewCoordinator<LibraryListCoordinator> {
|
||||||
return NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
|
NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeAllMediaTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeAllMediaTab(isActive: Bool) -> some View {
|
||||||
Image(systemName: "folder")
|
Image(systemName: "folder")
|
||||||
L10n.allMedia.text
|
L10n.allMedia.text
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func customize(_ view: AnyView) -> some View {
|
@ViewBuilder
|
||||||
|
func customize(_ view: AnyView) -> some View {
|
||||||
view.onAppear {
|
view.onAppear {
|
||||||
AppURLHandler.shared.appURLState = .allowed
|
AppURLHandler.shared.appURLState = .allowed
|
||||||
// TODO: todo
|
// TODO: todo
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Nuke
|
import Nuke
|
||||||
|
@ -15,9 +14,12 @@ import SwiftUI
|
||||||
final class MainCoordinator: NavigationCoordinatable {
|
final class MainCoordinator: NavigationCoordinatable {
|
||||||
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
|
var stack = NavigationStack<MainCoordinator>(initial: \MainCoordinator.mainTab)
|
||||||
|
|
||||||
@Root var mainTab = makeMainTab
|
@Root
|
||||||
@Root var serverList = makeServerList
|
var mainTab = makeMainTab
|
||||||
@Root var liveTV = makeLiveTV
|
@Root
|
||||||
|
var serverList = makeServerList
|
||||||
|
@Root
|
||||||
|
var liveTV = makeLiveTV
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func customize(_ view: AnyView) -> some View {
|
func customize(_ view: AnyView) -> some View {
|
||||||
|
@ -43,12 +45,14 @@ final class MainCoordinator: NavigationCoordinatable {
|
||||||
nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
nc.addObserver(self, selector: #selector(didLogOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didLogIn() {
|
@objc
|
||||||
|
func didLogIn() {
|
||||||
LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.")
|
LogManager.shared.log.info("Received `didSignIn` from NSNotificationCenter.")
|
||||||
root(\.mainTab)
|
root(\.mainTab)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didLogOut() {
|
@objc
|
||||||
|
func didLogOut() {
|
||||||
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
|
LogManager.shared.log.info("Received `didSignOut` from NSNotificationCenter.")
|
||||||
root(\.serverList)
|
root(\.serverList)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
final class MainTabCoordinator: TabCoordinatable {
|
final class MainTabCoordinator: TabCoordinatable {
|
||||||
var child = TabChild(startingItems: [
|
var child = TabChild(startingItems: [
|
||||||
|
@ -17,20 +16,26 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
\MainTabCoordinator.tv,
|
\MainTabCoordinator.tv,
|
||||||
\MainTabCoordinator.movies,
|
\MainTabCoordinator.movies,
|
||||||
\MainTabCoordinator.other,
|
\MainTabCoordinator.other,
|
||||||
\MainTabCoordinator.settings
|
\MainTabCoordinator.settings,
|
||||||
])
|
])
|
||||||
|
|
||||||
@Route(tabItem: makeHomeTab) var home = makeHome
|
@Route(tabItem: makeHomeTab)
|
||||||
@Route(tabItem: makeTvTab) var tv = makeTv
|
var home = makeHome
|
||||||
@Route(tabItem: makeMoviesTab) var movies = makeMovies
|
@Route(tabItem: makeTvTab)
|
||||||
@Route(tabItem: makeOtherTab) var other = makeOther
|
var tv = makeTv
|
||||||
@Route(tabItem: makeSettingsTab) var settings = makeSettings
|
@Route(tabItem: makeMoviesTab)
|
||||||
|
var movies = makeMovies
|
||||||
|
@Route(tabItem: makeOtherTab)
|
||||||
|
var other = makeOther
|
||||||
|
@Route(tabItem: makeSettingsTab)
|
||||||
|
var settings = makeSettings
|
||||||
|
|
||||||
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
func makeHome() -> NavigationViewCoordinator<HomeCoordinator> {
|
||||||
return NavigationViewCoordinator(HomeCoordinator())
|
NavigationViewCoordinator(HomeCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeHomeTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeHomeTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "house")
|
Image(systemName: "house")
|
||||||
L10n.home.text
|
L10n.home.text
|
||||||
|
@ -38,10 +43,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeTv() -> NavigationViewCoordinator<TVLibrariesCoordinator> {
|
func makeTv() -> NavigationViewCoordinator<TVLibrariesCoordinator> {
|
||||||
return NavigationViewCoordinator(TVLibrariesCoordinator(viewModel: TVLibrariesViewModel(), title: "TV Shows"))
|
NavigationViewCoordinator(TVLibrariesCoordinator(viewModel: TVLibrariesViewModel(), title: "TV Shows"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeTvTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeTvTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "tv")
|
Image(systemName: "tv")
|
||||||
Text("TV Shows")
|
Text("TV Shows")
|
||||||
|
@ -49,10 +55,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeMovies() -> NavigationViewCoordinator<MovieLibrariesCoordinator> {
|
func makeMovies() -> NavigationViewCoordinator<MovieLibrariesCoordinator> {
|
||||||
return NavigationViewCoordinator(MovieLibrariesCoordinator(viewModel: MovieLibrariesViewModel(), title: "Movies"))
|
NavigationViewCoordinator(MovieLibrariesCoordinator(viewModel: MovieLibrariesViewModel(), title: "Movies"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeMoviesTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeMoviesTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "film")
|
Image(systemName: "film")
|
||||||
Text("Movies")
|
Text("Movies")
|
||||||
|
@ -60,10 +67,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeOther() -> NavigationViewCoordinator<LibraryListCoordinator> {
|
func makeOther() -> NavigationViewCoordinator<LibraryListCoordinator> {
|
||||||
return NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
|
NavigationViewCoordinator(LibraryListCoordinator(viewModel: LibraryListViewModel()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeOtherTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeOtherTab(isActive: Bool) -> some View {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: "folder")
|
Image(systemName: "folder")
|
||||||
Text("Other")
|
Text("Other")
|
||||||
|
@ -71,10 +79,11 @@ final class MainTabCoordinator: TabCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
func makeSettings() -> NavigationViewCoordinator<SettingsCoordinator> {
|
||||||
return NavigationViewCoordinator(SettingsCoordinator())
|
NavigationViewCoordinator(SettingsCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeSettingsTab(isActive: Bool) -> some View {
|
@ViewBuilder
|
||||||
|
func makeSettingsTab(isActive: Bool) -> some View {
|
||||||
Image(systemName: "gearshape.fill")
|
Image(systemName: "gearshape.fill")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -16,8 +15,10 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start)
|
let stack = NavigationStack(initial: \MovieLibrariesCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var library = makeLibrary
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
|
||||||
let viewModel: MovieLibrariesViewModel
|
let viewModel: MovieLibrariesViewModel
|
||||||
let title: String
|
let title: String
|
||||||
|
@ -27,7 +28,8 @@ final class MovieLibrariesCoordinator: NavigationCoordinatable {
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
MovieLibrariesView(viewModel: self.viewModel, title: title)
|
MovieLibrariesView(viewModel: self.viewModel, title: title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,24 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
import JellyfinAPI
|
||||||
import Stinsen
|
import Stinsen
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import JellyfinAPI
|
|
||||||
|
|
||||||
final class SearchCoordinator: NavigationCoordinatable {
|
final class SearchCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \SearchCoordinator.start)
|
let stack = NavigationStack(initial: \SearchCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var item = makeItem
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var item = makeItem
|
||||||
|
|
||||||
let viewModel: LibrarySearchViewModel
|
let viewModel: LibrarySearchViewModel
|
||||||
|
|
||||||
|
@ -29,7 +30,8 @@ final class SearchCoordinator: NavigationCoordinatable {
|
||||||
ItemCoordinator(item: item)
|
ItemCoordinator(item: item)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
LibrarySearchView(viewModel: self.viewModel)
|
LibrarySearchView(viewModel: self.viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,7 +14,8 @@ final class ServerDetailCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \ServerDetailCoordinator.start)
|
let stack = NavigationStack(initial: \ServerDetailCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
let viewModel: ServerDetailViewModel
|
let viewModel: ServerDetailViewModel
|
||||||
|
|
||||||
|
@ -23,7 +23,8 @@ final class ServerDetailCoordinator: NavigationCoordinatable {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
ServerDetailView(viewModel: viewModel)
|
ServerDetailView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,10 +14,14 @@ final class ServerListCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \ServerListCoordinator.start)
|
let stack = NavigationStack(initial: \ServerListCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var connectToServer = makeConnectToServer
|
var start = makeStart
|
||||||
@Route(.push) var userList = makeUserList
|
@Route(.push)
|
||||||
@Route(.modal) var basicAppSettings = makeBasicAppSettings
|
var connectToServer = makeConnectToServer
|
||||||
|
@Route(.push)
|
||||||
|
var userList = makeUserList
|
||||||
|
@Route(.modal)
|
||||||
|
var basicAppSettings = makeBasicAppSettings
|
||||||
|
|
||||||
func makeConnectToServer() -> ConnectToServerCoodinator {
|
func makeConnectToServer() -> ConnectToServerCoodinator {
|
||||||
ConnectToServerCoodinator()
|
ConnectToServerCoodinator()
|
||||||
|
@ -32,7 +35,8 @@ final class ServerListCoordinator: NavigationCoordinatable {
|
||||||
NavigationViewCoordinator(BasicAppSettingsCoordinator())
|
NavigationViewCoordinator(BasicAppSettingsCoordinator())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
ServerListView(viewModel: ServerListViewModel())
|
ServerListView(viewModel: ServerListViewModel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,25 +14,33 @@ final class SettingsCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \SettingsCoordinator.start)
|
let stack = NavigationStack(initial: \SettingsCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var serverDetail = makeServerDetail
|
var start = makeStart
|
||||||
@Route(.push) var overlaySettings = makeOverlaySettings
|
@Route(.push)
|
||||||
@Route(.push) var experimentalSettings = makeExperimentalSettings
|
var serverDetail = makeServerDetail
|
||||||
|
@Route(.push)
|
||||||
|
var overlaySettings = makeOverlaySettings
|
||||||
|
@Route(.push)
|
||||||
|
var experimentalSettings = makeExperimentalSettings
|
||||||
|
|
||||||
@ViewBuilder func makeServerDetail() -> some View {
|
@ViewBuilder
|
||||||
|
func makeServerDetail() -> some View {
|
||||||
let viewModel = ServerDetailViewModel(server: SessionManager.main.currentLogin.server)
|
let viewModel = ServerDetailViewModel(server: SessionManager.main.currentLogin.server)
|
||||||
ServerDetailView(viewModel: viewModel)
|
ServerDetailView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeOverlaySettings() -> some View {
|
@ViewBuilder
|
||||||
|
func makeOverlaySettings() -> some View {
|
||||||
OverlaySettingsView()
|
OverlaySettingsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeExperimentalSettings() -> some View {
|
@ViewBuilder
|
||||||
|
func makeExperimentalSettings() -> some View {
|
||||||
ExperimentalSettingsView()
|
ExperimentalSettingsView()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
let viewModel = SettingsViewModel(server: SessionManager.main.currentLogin.server, user: SessionManager.main.currentLogin.user)
|
let viewModel = SettingsViewModel(server: SessionManager.main.currentLogin.server, user: SessionManager.main.currentLogin.user)
|
||||||
SettingsView(viewModel: viewModel)
|
SettingsView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -16,8 +15,10 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \TVLibrariesCoordinator.start)
|
let stack = NavigationStack(initial: \TVLibrariesCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var library = makeLibrary
|
var start = makeStart
|
||||||
|
@Route(.push)
|
||||||
|
var library = makeLibrary
|
||||||
|
|
||||||
let viewModel: TVLibrariesViewModel
|
let viewModel: TVLibrariesViewModel
|
||||||
let title: String
|
let title: String
|
||||||
|
@ -27,7 +28,8 @@ final class TVLibrariesCoordinator: NavigationCoordinatable {
|
||||||
self.title = title
|
self.title = title
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
TVLibrariesView(viewModel: self.viewModel, title: title)
|
TVLibrariesView(viewModel: self.viewModel, title: title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,9 +14,12 @@ final class UserListCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \UserListCoordinator.start)
|
let stack = NavigationStack(initial: \UserListCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
@Route(.push) var userSignIn = makeUserSignIn
|
var start = makeStart
|
||||||
@Route(.push) var serverDetail = makeServerDetail
|
@Route(.push)
|
||||||
|
var userSignIn = makeUserSignIn
|
||||||
|
@Route(.push)
|
||||||
|
var serverDetail = makeServerDetail
|
||||||
|
|
||||||
let viewModel: UserListViewModel
|
let viewModel: UserListViewModel
|
||||||
|
|
||||||
|
@ -26,14 +28,15 @@ final class UserListCoordinator: NavigationCoordinatable {
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
|
func makeUserSignIn(server: SwiftfinStore.State.Server) -> UserSignInCoordinator {
|
||||||
return UserSignInCoordinator(viewModel: .init(server: server))
|
UserSignInCoordinator(viewModel: .init(server: server))
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeServerDetail(server: SwiftfinStore.State.Server) -> ServerDetailCoordinator {
|
func makeServerDetail(server: SwiftfinStore.State.Server) -> ServerDetailCoordinator {
|
||||||
return ServerDetailCoordinator(viewModel: .init(server: server))
|
ServerDetailCoordinator(viewModel: .init(server: server))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
UserListView(viewModel: viewModel)
|
UserListView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Stinsen
|
import Stinsen
|
||||||
|
@ -15,7 +14,8 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \UserSignInCoordinator.start)
|
let stack = NavigationStack(initial: \UserSignInCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
let viewModel: UserSignInViewModel
|
let viewModel: UserSignInViewModel
|
||||||
|
|
||||||
|
@ -23,7 +23,8 @@ final class UserSignInCoordinator: NavigationCoordinatable {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
UserSignInView(viewModel: viewModel)
|
UserSignInView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -17,7 +16,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
PreferenceUIHostingControllerView {
|
PreferenceUIHostingControllerView {
|
||||||
VLCPlayerView(viewModel: self.viewModel)
|
VLCPlayerView(viewModel: self.viewModel)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -17,7 +16,8 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
|
|
||||||
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
let stack = NavigationStack(initial: \VideoPlayerCoordinator.start)
|
||||||
|
|
||||||
@Root var start = makeStart
|
@Root
|
||||||
|
var start = makeStart
|
||||||
|
|
||||||
let viewModel: VideoPlayerViewModel
|
let viewModel: VideoPlayerViewModel
|
||||||
|
|
||||||
|
@ -25,10 +25,10 @@ final class VideoPlayerCoordinator: NavigationCoordinatable {
|
||||||
self.viewModel = viewModel
|
self.viewModel = viewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder func makeStart() -> some View {
|
@ViewBuilder
|
||||||
|
func makeStart() -> some View {
|
||||||
VLCPlayerView(viewModel: viewModel)
|
VLCPlayerView(viewModel: viewModel)
|
||||||
.navigationBarHidden(true)
|
.navigationBarHidden(true)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -22,7 +21,7 @@ struct ErrorMessage: Identifiable {
|
||||||
static let noShowErrorCode = -69420
|
static let noShowErrorCode = -69420
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
return "\(code)\(title)\(logConstructor.message)"
|
"\(code)\(title)\(logConstructor.message)"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If the custom displayMessage is `nil`, it will be set to the given logConstructor's message
|
/// If the custom displayMessage is `nil`, it will be set to the given logConstructor's message
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
||||||
/**
|
/**
|
||||||
The implementation of the network errors here are a temporary measure.
|
The implementation of the network errors here are a temporary measure.
|
||||||
It is very repetitive, messy, and doesn't fulfill the entire specification of "error reporting".
|
It is very repetitive, messy, and doesn't fulfill the entire specification of "error reporting".
|
||||||
The specific kind of errors here should be created and surfaced from within JellyfinAPI on API calls.
|
|
||||||
|
Needs to be replaced
|
||||||
*/
|
*/
|
||||||
|
|
||||||
enum NetworkError: Error {
|
enum NetworkError: Error {
|
||||||
|
@ -29,11 +29,11 @@ enum NetworkError: Error {
|
||||||
|
|
||||||
var errorMessage: ErrorMessage {
|
var errorMessage: ErrorMessage {
|
||||||
switch self {
|
switch self {
|
||||||
case .URLError(let response, let displayMessage, let logConstructor):
|
case let .URLError(response, displayMessage, logConstructor):
|
||||||
return NetworkError.parseURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
return NetworkError.parseURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
case .HTTPURLError(let response, let displayMessage, let logConstructor):
|
case let .HTTPURLError(response, displayMessage, logConstructor):
|
||||||
return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
return NetworkError.parseHTTPURLError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
case .JellyfinError(let response, let displayMessage, let logConstructor):
|
case let .JellyfinError(response, displayMessage, logConstructor):
|
||||||
return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
return NetworkError.parseJellyfinError(from: response, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,14 +62,16 @@ enum NetworkError: Error {
|
||||||
logFunction(logConstructor.message, logConstructor.tag, logConstructor.function, logConstructor.file, logConstructor.line)
|
logFunction(logConstructor.message, logConstructor.tag, logConstructor.function, logConstructor.file, logConstructor.line)
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func parseURLError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage {
|
private static func parseURLError(from response: ErrorResponse, displayMessage: String?,
|
||||||
|
logConstructor: LogConstructor) -> ErrorMessage
|
||||||
|
{
|
||||||
|
|
||||||
let errorMessage: ErrorMessage
|
let errorMessage: ErrorMessage
|
||||||
var logMessage = "An error has occurred."
|
var logMessage = "An error has occurred."
|
||||||
var logConstructor = logConstructor
|
var logConstructor = logConstructor
|
||||||
|
|
||||||
switch response {
|
switch response {
|
||||||
case .error(_, _, _, let err):
|
case let .error(_, _, _, err):
|
||||||
|
|
||||||
// These codes are currently referenced from:
|
// These codes are currently referenced from:
|
||||||
// https://developer.apple.com/documentation/foundation/1508628-url_loading_system_error_codes
|
// https://developer.apple.com/documentation/foundation/1508628-url_loading_system_error_codes
|
||||||
|
@ -100,7 +102,9 @@ enum NetworkError: Error {
|
||||||
return errorMessage
|
return errorMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage {
|
private static func parseHTTPURLError(from response: ErrorResponse, displayMessage: String?,
|
||||||
|
logConstructor: LogConstructor) -> ErrorMessage
|
||||||
|
{
|
||||||
|
|
||||||
let errorMessage: ErrorMessage
|
let errorMessage: ErrorMessage
|
||||||
let logMessage = "An HTTP URL error has occurred"
|
let logMessage = "An HTTP URL error has occurred"
|
||||||
|
@ -119,14 +123,16 @@ enum NetworkError: Error {
|
||||||
return errorMessage
|
return errorMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?, logConstructor: LogConstructor) -> ErrorMessage {
|
private static func parseJellyfinError(from response: ErrorResponse, displayMessage: String?,
|
||||||
|
logConstructor: LogConstructor) -> ErrorMessage
|
||||||
|
{
|
||||||
|
|
||||||
let errorMessage: ErrorMessage
|
let errorMessage: ErrorMessage
|
||||||
var logMessage = "An error has occurred."
|
var logMessage = "An error has occurred."
|
||||||
var logConstructor = logConstructor
|
var logConstructor = logConstructor
|
||||||
|
|
||||||
switch response {
|
switch response {
|
||||||
case .error(let code, _, _, _):
|
case let .error(code, _, _, _):
|
||||||
|
|
||||||
// Generic HTTP status codes
|
// Generic HTTP status codes
|
||||||
switch code {
|
switch code {
|
||||||
|
|
|
@ -1,29 +1,15 @@
|
||||||
/*
|
//
|
||||||
Copyright (c) 2018 Wolt Enterprises
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
//
|
||||||
in the Software without restriction, including without limitation the rights
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
//
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIImage {
|
public extension UIImage {
|
||||||
public convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
convenience init?(blurHash: String, size: CGSize, punch: Float = 1) {
|
||||||
guard blurHash.count >= 6 else { return nil }
|
guard blurHash.count >= 6 else { return nil }
|
||||||
|
|
||||||
let sizeFlag = String(blurHash[0]).decode83()
|
let sizeFlag = String(blurHash[0]).decode83()
|
||||||
|
@ -82,7 +68,8 @@ extension UIImage {
|
||||||
|
|
||||||
guard let provider = CGDataProvider(data: data) else { return nil }
|
guard let provider = CGDataProvider(data: data) else { return nil }
|
||||||
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
|
guard let cgImage = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 24, bytesPerRow: bytesPerRow,
|
||||||
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: true, intent: .defaultIntent) else { return nil }
|
space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: bitmapInfo, provider: provider, decode: nil,
|
||||||
|
shouldInterpolate: true, intent: .defaultIntent) else { return nil }
|
||||||
|
|
||||||
self.init(cgImage: cgImage)
|
self.init(cgImage: cgImage)
|
||||||
}
|
}
|
||||||
|
@ -100,17 +87,15 @@ private func decodeAC(_ value: Int, maximumValue: Float) -> (Float, Float, Float
|
||||||
let quantG = (value / 19) % 19
|
let quantG = (value / 19) % 19
|
||||||
let quantB = value % 19
|
let quantB = value % 19
|
||||||
|
|
||||||
let rgb = (
|
let rgb = (signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
||||||
signPow((Float(quantR) - 9) / 9, 2) * maximumValue,
|
|
||||||
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
signPow((Float(quantG) - 9) / 9, 2) * maximumValue,
|
||||||
signPow((Float(quantB) - 9) / 9, 2) * maximumValue
|
signPow((Float(quantB) - 9) / 9, 2) * maximumValue)
|
||||||
)
|
|
||||||
|
|
||||||
return rgb
|
return rgb
|
||||||
}
|
}
|
||||||
|
|
||||||
private func signPow(_ value: Float, _ exp: Float) -> Float {
|
private func signPow(_ value: Float, _ exp: Float) -> Float {
|
||||||
return copysign(pow(abs(value), exp), value)
|
copysign(pow(abs(value), exp), value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func linearTosRGB(_ value: Float) -> Int {
|
private func linearTosRGB(_ value: Float) -> Int {
|
||||||
|
@ -124,7 +109,7 @@ private func sRGBToLinear<Type: BinaryInteger>(_ value: Type) -> Float {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let encodeCharacters: [String] = {
|
private let encodeCharacters: [String] = {
|
||||||
return "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~".map { String($0) }
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private let decodeCharacters: [String: Int] = {
|
private let decodeCharacters: [String: Int] = {
|
||||||
|
@ -149,7 +134,7 @@ extension String {
|
||||||
|
|
||||||
private extension String {
|
private extension String {
|
||||||
subscript(offset: Int) -> Character {
|
subscript(offset: Int) -> Character {
|
||||||
return self[index(startIndex, offsetBy: offset)]
|
self[index(startIndex, offsetBy: offset)]
|
||||||
}
|
}
|
||||||
|
|
||||||
subscript(bounds: CountableClosedRange<Int>) -> Substring {
|
subscript(bounds: CountableClosedRange<Int>) -> Substring {
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension CGSize {
|
extension CGSize {
|
||||||
|
|
||||||
static func Circle(radius: CGFloat) -> CGSize {
|
static func Circle(radius: CGFloat) -> CGSize {
|
||||||
return CGSize(width: radius, height: radius)
|
CGSize(width: radius, height: radius)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* SwiftFin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@ -17,6 +18,6 @@ public extension Collection {
|
||||||
///
|
///
|
||||||
/// - Parameter index: index of element to access element.
|
/// - Parameter index: index of element to access element.
|
||||||
subscript(safe index: Index) -> Element? {
|
subscript(safe index: Index) -> Element? {
|
||||||
return indices.contains(index) ? self[index] : nil
|
indices.contains(index) ? self[index] : nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,27 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension Color {
|
public extension Color {
|
||||||
|
|
||||||
static let jellyfinPurple = Color(uiColor: .jellyfinPurple)
|
internal static let jellyfinPurple = Color(uiColor: .jellyfinPurple)
|
||||||
|
|
||||||
#if os(tvOS) // tvOS doesn't have these
|
#if os(tvOS) // tvOS doesn't have these
|
||||||
public static let systemFill = Color(UIColor.white)
|
static let systemFill = Color(UIColor.white)
|
||||||
public static let secondarySystemFill = Color(UIColor.gray)
|
static let secondarySystemFill = Color(UIColor.gray)
|
||||||
public static let tertiarySystemFill = Color(UIColor.black)
|
static let tertiarySystemFill = Color(UIColor.black)
|
||||||
public static let lightGray = Color(UIColor.lightGray)
|
static let lightGray = Color(UIColor.lightGray)
|
||||||
#else
|
#else
|
||||||
public static let systemFill = Color(UIColor.systemFill)
|
static let systemFill = Color(UIColor.systemFill)
|
||||||
public static let systemBackground = Color(UIColor.systemBackground)
|
static let systemBackground = Color(UIColor.systemBackground)
|
||||||
public static let secondarySystemFill = Color(UIColor.secondarySystemBackground)
|
static let secondarySystemFill = Color(UIColor.secondarySystemBackground)
|
||||||
public static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground)
|
static let tertiarySystemFill = Color(UIColor.tertiarySystemBackground)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* SwiftFin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
// MARK: PortraitImageStackable
|
// MARK: PortraitImageStackable
|
||||||
|
|
||||||
extension BaseItemDto: PortraitImageStackable {
|
extension BaseItemDto: PortraitImageStackable {
|
||||||
public var portraitImageID: String {
|
public var portraitImageID: String {
|
||||||
return id ?? "no id"
|
id ?? "no id"
|
||||||
}
|
}
|
||||||
|
|
||||||
public func imageURLContsructor(maxWidth: Int) -> URL {
|
public func imageURLContsructor(maxWidth: Int) -> URL {
|
||||||
|
@ -45,12 +45,12 @@ extension BaseItemDto: PortraitImageStackable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public var blurHash: String {
|
public var blurHash: String {
|
||||||
return self.getPrimaryImageBlurHash()
|
self.getPrimaryImageBlurHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var failureInitials: String {
|
public var failureInitials: String {
|
||||||
guard let name = self.name else { return "" }
|
guard let name = self.name else { return "" }
|
||||||
let initials = name.split(separator: " ").compactMap({ String($0).first })
|
let initials = name.split(separator: " ").compactMap { String($0).first }
|
||||||
return String(initials)
|
return String(initials)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
@ -16,32 +15,33 @@ extension BaseItemDto {
|
||||||
func createVideoPlayerViewModel() -> AnyPublisher<VideoPlayerViewModel, Error> {
|
func createVideoPlayerViewModel() -> AnyPublisher<VideoPlayerViewModel, Error> {
|
||||||
let builder = DeviceProfileBuilder()
|
let builder = DeviceProfileBuilder()
|
||||||
// TODO: fix bitrate settings
|
// TODO: fix bitrate settings
|
||||||
builder.setMaxBitrate(bitrate: 60000000)
|
builder.setMaxBitrate(bitrate: 60_000_000)
|
||||||
let profile = builder.buildProfile()
|
let profile = builder.buildProfile()
|
||||||
|
|
||||||
let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id,
|
let playbackInfo = PlaybackInfoDto(userId: SessionManager.main.currentLogin.user.id,
|
||||||
maxStreamingBitrate: 60000000,
|
maxStreamingBitrate: 60_000_000,
|
||||||
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
||||||
deviceProfile: profile,
|
deviceProfile: profile,
|
||||||
autoOpenLiveStream: true)
|
autoOpenLiveStream: true)
|
||||||
|
|
||||||
return MediaInfoAPI.getPostedPlaybackInfo(itemId: self.id!,
|
return MediaInfoAPI.getPostedPlaybackInfo(itemId: self.id!,
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
maxStreamingBitrate: 60000000,
|
maxStreamingBitrate: 60_000_000,
|
||||||
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
startTimeTicks: self.userData?.playbackPositionTicks ?? 0,
|
||||||
autoOpenLiveStream: true,
|
autoOpenLiveStream: true,
|
||||||
playbackInfoDto: playbackInfo)
|
playbackInfoDto: playbackInfo)
|
||||||
.map({ response -> VideoPlayerViewModel in
|
.map { response -> VideoPlayerViewModel in
|
||||||
let mediaSource = response.mediaSources!.first!
|
let mediaSource = response.mediaSources!.first!
|
||||||
|
|
||||||
let audioStreams = mediaSource.mediaStreams?.filter({ $0.type == .audio }) ?? []
|
let audioStreams = mediaSource.mediaStreams?.filter { $0.type == .audio } ?? []
|
||||||
let subtitleStreams = mediaSource.mediaStreams?.filter({ $0.type == .subtitle }) ?? []
|
let subtitleStreams = mediaSource.mediaStreams?.filter { $0.type == .subtitle } ?? []
|
||||||
|
|
||||||
let defaultAudioStream = audioStreams.first(where: { $0.index! == mediaSource.defaultAudioStreamIndex! })
|
let defaultAudioStream = audioStreams.first(where: { $0.index! == mediaSource.defaultAudioStreamIndex! })
|
||||||
|
|
||||||
let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 })
|
let defaultSubtitleStream = subtitleStreams.first(where: { $0.index! == mediaSource.defaultSubtitleStreamIndex ?? -1 })
|
||||||
|
|
||||||
// MARK: Stream
|
// MARK: Stream
|
||||||
|
|
||||||
var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)!
|
var streamURL = URLComponents(string: SessionManager.main.currentLogin.server.currentURI)!
|
||||||
|
|
||||||
let streamType: ServerStreamType
|
let streamType: ServerStreamType
|
||||||
|
@ -61,7 +61,7 @@ extension BaseItemDto {
|
||||||
|
|
||||||
// MARK: VidoPlayerViewModel Creation
|
// MARK: VidoPlayerViewModel Creation
|
||||||
|
|
||||||
var subtitle: String? = nil
|
var subtitle: String?
|
||||||
|
|
||||||
// MARK: Attach media content to self
|
// MARK: Attach media content to self
|
||||||
|
|
||||||
|
@ -103,7 +103,7 @@ extension BaseItemDto {
|
||||||
shouldShowAutoPlay: shouldShowAutoPlay)
|
shouldShowAutoPlay: shouldShowAutoPlay)
|
||||||
|
|
||||||
return videoPlayerViewModel
|
return videoPlayerViewModel
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -187,7 +186,8 @@ public extension BaseItemDto {
|
||||||
|
|
||||||
func getLiveProgressPercentage() -> Double {
|
func getLiveProgressPercentage() -> Double {
|
||||||
if let startDate = self.startDate,
|
if let startDate = self.startDate,
|
||||||
let endDate = self.endDate {
|
let endDate = self.endDate
|
||||||
|
{
|
||||||
let start = startDate.timeIntervalSinceReferenceDate
|
let start = startDate.timeIntervalSinceReferenceDate
|
||||||
let end = endDate.timeIntervalSinceReferenceDate
|
let end = endDate.timeIntervalSinceReferenceDate
|
||||||
let now = Date().timeIntervalSinceReferenceDate
|
let now = Date().timeIntervalSinceReferenceDate
|
||||||
|
@ -276,16 +276,18 @@ public extension BaseItemDto {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let mediaStreams = mediaStreams {
|
if let mediaStreams = mediaStreams {
|
||||||
let audioStreams = mediaStreams.filter({ $0.type == .audio })
|
let audioStreams = mediaStreams.filter { $0.type == .audio }
|
||||||
let subtitleStreams = mediaStreams.filter({ $0.type == .subtitle })
|
let subtitleStreams = mediaStreams.filter { $0.type == .subtitle }
|
||||||
|
|
||||||
if !audioStreams.isEmpty {
|
if !audioStreams.isEmpty {
|
||||||
let audioList = audioStreams.compactMap({ "\($0.displayTitle ?? "No Title") (\($0.codec ?? "No Codec"))" }).joined(separator: ", ")
|
let audioList = audioStreams.compactMap { "\($0.displayTitle ?? "No Title") (\($0.codec ?? "No Codec"))" }
|
||||||
|
.joined(separator: ", ")
|
||||||
mediaItems.append(ItemDetail(title: "Audio", content: audioList))
|
mediaItems.append(ItemDetail(title: "Audio", content: audioList))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !subtitleStreams.isEmpty {
|
if !subtitleStreams.isEmpty {
|
||||||
let subtitleList = subtitleStreams.compactMap({ "\($0.displayTitle ?? "No Title") (\($0.codec ?? "No Codec"))" }).joined(separator: ", ")
|
let subtitleList = subtitleStreams.compactMap { "\($0.displayTitle ?? "No Title") (\($0.codec ?? "No Codec"))" }
|
||||||
|
.joined(separator: ", ")
|
||||||
mediaItems.append(ItemDetail(title: "Subtitles", content: subtitleList))
|
mediaItems.append(ItemDetail(title: "Subtitles", content: subtitleList))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* SwiftFin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -12,6 +13,7 @@ import UIKit
|
||||||
extension BaseItemPerson {
|
extension BaseItemPerson {
|
||||||
|
|
||||||
// MARK: Get Image
|
// MARK: Get Image
|
||||||
|
|
||||||
func getImage(baseURL: String, maxWidth: Int) -> URL {
|
func getImage(baseURL: String, maxWidth: Int) -> URL {
|
||||||
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
let x = UIScreen.main.nativeScale * CGFloat(maxWidth)
|
||||||
|
|
||||||
|
@ -45,7 +47,8 @@ extension BaseItemPerson {
|
||||||
let split = role.split(separator: "/")
|
let split = role.split(separator: "/")
|
||||||
guard split.count > 1 else { return role }
|
guard split.count > 1 else { return role }
|
||||||
|
|
||||||
guard let firstRole = split.first?.trimmingCharacters(in: CharacterSet(charactersIn: " ")), let lastRole = split.last?.trimmingCharacters(in: CharacterSet(charactersIn: " ")) else { return role }
|
guard let firstRole = split.first?.trimmingCharacters(in: CharacterSet(charactersIn: " ")),
|
||||||
|
let lastRole = split.last?.trimmingCharacters(in: CharacterSet(charactersIn: " ")) else { return role }
|
||||||
|
|
||||||
var final = firstRole
|
var final = firstRole
|
||||||
|
|
||||||
|
@ -59,39 +62,41 @@ extension BaseItemPerson {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: PortraitImageStackable
|
// MARK: PortraitImageStackable
|
||||||
|
|
||||||
extension BaseItemPerson: PortraitImageStackable {
|
extension BaseItemPerson: PortraitImageStackable {
|
||||||
public var portraitImageID: String {
|
public var portraitImageID: String {
|
||||||
return (id ?? "noid") + title + (subtitle ?? "nodescription") + blurHash + failureInitials
|
(id ?? "noid") + title + (subtitle ?? "nodescription") + blurHash + failureInitials
|
||||||
}
|
}
|
||||||
|
|
||||||
public func imageURLContsructor(maxWidth: Int) -> URL {
|
public func imageURLContsructor(maxWidth: Int) -> URL {
|
||||||
return self.getImage(baseURL: SessionManager.main.currentLogin.server.currentURI, maxWidth: maxWidth)
|
self.getImage(baseURL: SessionManager.main.currentLogin.server.currentURI, maxWidth: maxWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var title: String {
|
public var title: String {
|
||||||
return self.name ?? ""
|
self.name ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
public var subtitle: String? {
|
public var subtitle: String? {
|
||||||
return self.firstRole()
|
self.firstRole()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var blurHash: String {
|
public var blurHash: String {
|
||||||
return self.getBlurHash()
|
self.getBlurHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
public var failureInitials: String {
|
public var failureInitials: String {
|
||||||
guard let name = self.name else { return "" }
|
guard let name = self.name else { return "" }
|
||||||
let initials = name.split(separator: " ").compactMap({ String($0).first })
|
let initials = name.split(separator: " ").compactMap { String($0).first }
|
||||||
return String(initials)
|
return String(initials)
|
||||||
}
|
}
|
||||||
|
|
||||||
public var showTitle: Bool {
|
public var showTitle: Bool {
|
||||||
return true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: DiplayedType
|
// MARK: DiplayedType
|
||||||
|
|
||||||
extension BaseItemPerson {
|
extension BaseItemPerson {
|
||||||
|
|
||||||
// Only displayed person types.
|
// Only displayed person types.
|
||||||
|
@ -103,7 +108,7 @@ extension BaseItemPerson {
|
||||||
case producer = "Producer"
|
case producer = "Producer"
|
||||||
|
|
||||||
static var allCasesRaw: [String] {
|
static var allCasesRaw: [String] {
|
||||||
return self.allCases.map({ $0.rawValue })
|
self.allCases.map(\.rawValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@ -18,6 +17,6 @@ struct JellyfinAPIError: Error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var localizedDescription: String {
|
var localizedDescription: String {
|
||||||
return message
|
message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
extension NameGuidPair: PillStackable {
|
extension NameGuidPair: PillStackable {
|
||||||
var title: String {
|
var title: String {
|
||||||
return self.name ?? ""
|
self.name ?? ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
extension UIDevice {
|
extension UIDevice {
|
||||||
static var vendorUUIDString: String {
|
static var vendorUUIDString: String {
|
||||||
return current.identifierForVendor!.uuidString
|
current.identifierForVendor!.uuidString
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension View {
|
extension View {
|
||||||
func eraseToAnyView() -> AnyView {
|
func eraseToAnyView() -> AnyView {
|
||||||
return AnyView(self)
|
AnyView(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,11 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
// swiftlint:disable all
|
// swiftlint:disable all
|
||||||
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
// Generated using SwiftGen — https://github.com/SwiftGen/SwiftGen
|
||||||
|
|
||||||
|
@ -70,8 +78,9 @@ internal enum L10n {
|
||||||
internal static let home = L10n.tr("Localizable", "home")
|
internal static let home = L10n.tr("Localizable", "home")
|
||||||
/// Latest %@
|
/// Latest %@
|
||||||
internal static func latestWithString(_ p1: Any) -> String {
|
internal static func latestWithString(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "latestWithString", String(describing: p1))
|
L10n.tr("Localizable", "latestWithString", String(describing: p1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Library
|
/// Library
|
||||||
internal static let library = L10n.tr("Localizable", "library")
|
internal static let library = L10n.tr("Localizable", "library")
|
||||||
/// Light
|
/// Light
|
||||||
|
@ -84,8 +93,9 @@ internal enum L10n {
|
||||||
internal static let login = L10n.tr("Localizable", "login")
|
internal static let login = L10n.tr("Localizable", "login")
|
||||||
/// Login to %@
|
/// Login to %@
|
||||||
internal static func loginToWithString(_ p1: Any) -> String {
|
internal static func loginToWithString(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "loginToWithString", String(describing: p1))
|
L10n.tr("Localizable", "loginToWithString", String(describing: p1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// More Like This
|
/// More Like This
|
||||||
internal static let moreLikeThis = L10n.tr("Localizable", "moreLikeThis")
|
internal static let moreLikeThis = L10n.tr("Localizable", "moreLikeThis")
|
||||||
/// Next Up
|
/// Next Up
|
||||||
|
@ -96,16 +106,18 @@ internal enum L10n {
|
||||||
internal static let noResults = L10n.tr("Localizable", "noResults")
|
internal static let noResults = L10n.tr("Localizable", "noResults")
|
||||||
/// Type: %@ not implemented yet :(
|
/// Type: %@ not implemented yet :(
|
||||||
internal static func notImplementedYetWithType(_ p1: Any) -> String {
|
internal static func notImplementedYetWithType(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "notImplementedYetWithType", String(describing: p1))
|
L10n.tr("Localizable", "notImplementedYetWithType", String(describing: p1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ok
|
/// Ok
|
||||||
internal static let ok = L10n.tr("Localizable", "ok")
|
internal static let ok = L10n.tr("Localizable", "ok")
|
||||||
/// Other User
|
/// Other User
|
||||||
internal static let otherUser = L10n.tr("Localizable", "otherUser")
|
internal static let otherUser = L10n.tr("Localizable", "otherUser")
|
||||||
/// Page %1$@ of %2$@
|
/// Page %1$@ of %2$@
|
||||||
internal static func pageOfWithNumbers(_ p1: Any, _ p2: Any) -> String {
|
internal static func pageOfWithNumbers(_ p1: Any, _ p2: Any) -> String {
|
||||||
return L10n.tr("Localizable", "pageOfWithNumbers", String(describing: p1), String(describing: p2))
|
L10n.tr("Localizable", "pageOfWithNumbers", String(describing: p1), String(describing: p2))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Password
|
/// Password
|
||||||
internal static let password = L10n.tr("Localizable", "password")
|
internal static let password = L10n.tr("Localizable", "password")
|
||||||
/// Play
|
/// Play
|
||||||
|
@ -122,8 +134,9 @@ internal enum L10n {
|
||||||
internal static let search = L10n.tr("Localizable", "search")
|
internal static let search = L10n.tr("Localizable", "search")
|
||||||
/// S%1$@:E%2$@
|
/// S%1$@:E%2$@
|
||||||
internal static func seasonAndEpisode(_ p1: Any, _ p2: Any) -> String {
|
internal static func seasonAndEpisode(_ p1: Any, _ p2: Any) -> String {
|
||||||
return L10n.tr("Localizable", "seasonAndEpisode", String(describing: p1), String(describing: p2))
|
L10n.tr("Localizable", "seasonAndEpisode", String(describing: p1), String(describing: p2))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Seasons
|
/// Seasons
|
||||||
internal static let seasons = L10n.tr("Localizable", "seasons")
|
internal static let seasons = L10n.tr("Localizable", "seasons")
|
||||||
/// See All
|
/// See All
|
||||||
|
@ -132,16 +145,18 @@ internal enum L10n {
|
||||||
internal static let selectCastDestination = L10n.tr("Localizable", "selectCastDestination")
|
internal static let selectCastDestination = L10n.tr("Localizable", "selectCastDestination")
|
||||||
/// Server %s already exists. Add new URL?
|
/// Server %s already exists. Add new URL?
|
||||||
internal static func serverAlreadyExistsPrompt(_ p1: UnsafePointer<CChar>) -> String {
|
internal static func serverAlreadyExistsPrompt(_ p1: UnsafePointer<CChar>) -> String {
|
||||||
return L10n.tr("Localizable", "serverAlreadyExistsPrompt", p1)
|
L10n.tr("Localizable", "serverAlreadyExistsPrompt", p1)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Server Information
|
/// Server Information
|
||||||
internal static let serverInformation = L10n.tr("Localizable", "serverInformation")
|
internal static let serverInformation = L10n.tr("Localizable", "serverInformation")
|
||||||
/// Server URL
|
/// Server URL
|
||||||
internal static let serverURL = L10n.tr("Localizable", "serverURL")
|
internal static let serverURL = L10n.tr("Localizable", "serverURL")
|
||||||
/// Signed in as %@
|
/// Signed in as %@
|
||||||
internal static func signedInAsWithString(_ p1: Any) -> String {
|
internal static func signedInAsWithString(_ p1: Any) -> String {
|
||||||
return L10n.tr("Localizable", "signedInAsWithString", String(describing: p1))
|
L10n.tr("Localizable", "signedInAsWithString", String(describing: p1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sort by
|
/// Sort by
|
||||||
internal static let sortBy = L10n.tr("Localizable", "sortBy")
|
internal static let sortBy = L10n.tr("Localizable", "sortBy")
|
||||||
/// STUDIO
|
/// STUDIO
|
||||||
|
@ -169,6 +184,7 @@ internal enum L10n {
|
||||||
/// Your Favorites
|
/// Your Favorites
|
||||||
internal static let yourFavorites = L10n.tr("Localizable", "yourFavorites")
|
internal static let yourFavorites = L10n.tr("Localizable", "yourFavorites")
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||||
|
|
||||||
|
@ -191,4 +207,5 @@ private final class BundleToken {
|
||||||
#endif
|
#endif
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// swiftlint:enable convenience_type
|
// swiftlint:enable convenience_type
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -21,5 +20,4 @@ struct DetailItem {
|
||||||
|
|
||||||
let baseItem: BaseItemDto
|
let baseItem: BaseItemDto
|
||||||
let type: DetailItemType
|
let type: DetailItemType
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
// lol can someone buy me a coffee this took forever :|
|
// lol can someone buy me a coffee this took forever :|
|
||||||
|
|
||||||
|
@ -47,25 +48,34 @@ class DeviceProfileBuilder {
|
||||||
|
|
||||||
// Build direct play profiles
|
// Build direct play profiles
|
||||||
var directPlayProfiles: [DirectPlayProfile] = []
|
var directPlayProfiles: [DirectPlayProfile] = []
|
||||||
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav", videoCodec: "h264,mpeg4,vp9", type: .video)]
|
directPlayProfiles =
|
||||||
|
[DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav", videoCodec: "h264,mpeg4,vp9", type: .video)]
|
||||||
|
|
||||||
// Device supports Dolby Digital (AC3, EAC3)
|
// Device supports Dolby Digital (AC3, EAC3)
|
||||||
if supportsFeature(minimumSupported: .A8X) {
|
if supportsFeature(minimumSupported: .A8X) {
|
||||||
if supportsFeature(minimumSupported: .A9) {
|
if supportsFeature(minimumSupported: .A9) {
|
||||||
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "hevc,h264,hev1,mpeg4,vp9", type: .video)] // HEVC/H.264 with Dolby Digital
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus",
|
||||||
|
videoCodec: "hevc,h264,hev1,mpeg4,vp9",
|
||||||
|
type: .video)] // HEVC/H.264 with Dolby Digital
|
||||||
} else {
|
} else {
|
||||||
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "ac3,eac3,aac,mp3,wav,opus", videoCodec: "h264,mpeg4,vp9", type: .video)] // H.264 with Dolby Digital
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "ac3,eac3,aac,mp3,wav,opus",
|
||||||
|
videoCodec: "h264,mpeg4,vp9", type: .video)] // H.264 with Dolby Digital
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device supports Dolby Vision?
|
// Device supports Dolby Vision?
|
||||||
if supportsFeature(minimumSupported: .A10X) {
|
if supportsFeature(minimumSupported: .A10X) {
|
||||||
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", videoCodec: "dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9", type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus",
|
||||||
|
videoCodec: "dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9",
|
||||||
|
type: .video)] // H.264/HEVC with Dolby Digital - No Atmos - Vision
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device supports Dolby Atmos?
|
// Device supports Dolby Atmos?
|
||||||
if supportsFeature(minimumSupported: .A12) {
|
if supportsFeature(minimumSupported: .A12) {
|
||||||
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm", audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus", videoCodec: "h264,hevc,dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9", type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision
|
directPlayProfiles = [DirectPlayProfile(container: "mov,mp4,mkv,webm",
|
||||||
|
audioCodec: "aac,mp3,wav,ac3,eac3,flac,truehd,dts,dca,opus",
|
||||||
|
videoCodec: "h264,hevc,dvhe,dvh1,h264,hevc,hev1,mpeg4,vp9",
|
||||||
|
type: .video)] // H.264/HEVC with Dolby Digital & Atmos - Vision
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build transcoding profiles
|
// Build transcoding profiles
|
||||||
|
@ -75,29 +85,41 @@ class DeviceProfileBuilder {
|
||||||
// Device supports Dolby Digital (AC3, EAC3)
|
// Device supports Dolby Digital (AC3, EAC3)
|
||||||
if supportsFeature(minimumSupported: .A8X) {
|
if supportsFeature(minimumSupported: .A8X) {
|
||||||
if supportsFeature(minimumSupported: .A9) {
|
if supportsFeature(minimumSupported: .A9) {
|
||||||
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,hevc,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,hevc,mpeg4",
|
||||||
|
audioCodec: "aac,mp3,wav,eac3,ac3,flac,opus", _protocol: "hls",
|
||||||
|
context: .streaming, maxAudioChannels: "6", minSegments: 2,
|
||||||
|
breakOnNonKeyFrames: true)]
|
||||||
} else {
|
} else {
|
||||||
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4", audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "h264,mpeg4",
|
||||||
|
audioCodec: "aac,mp3,wav,eac3,ac3,opus", _protocol: "hls",
|
||||||
|
context: .streaming, maxAudioChannels: "6", minSegments: 2,
|
||||||
|
breakOnNonKeyFrames: true)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device supports FLAC?
|
// Device supports FLAC?
|
||||||
if supportsFeature(minimumSupported: .A10X) {
|
if supportsFeature(minimumSupported: .A10X) {
|
||||||
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "hevc,h264,mpeg4", audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls", context: .streaming, maxAudioChannels: "6", minSegments: 2, breakOnNonKeyFrames: true)]
|
transcodingProfiles = [TranscodingProfile(container: "ts", type: .video, videoCodec: "hevc,h264,mpeg4",
|
||||||
|
audioCodec: "aac,mp3,wav,ac3,eac3,flac,opus", _protocol: "hls",
|
||||||
|
context: .streaming, maxAudioChannels: "6", minSegments: 2,
|
||||||
|
breakOnNonKeyFrames: true)]
|
||||||
}
|
}
|
||||||
|
|
||||||
var codecProfiles: [CodecProfile] = []
|
var codecProfiles: [CodecProfile] = []
|
||||||
|
|
||||||
let h264CodecConditions: [ProfileCondition] = [
|
let h264CodecConditions: [ProfileCondition] = [
|
||||||
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
||||||
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline", isRequired: false),
|
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|baseline|constrained baseline",
|
||||||
|
isRequired: false),
|
||||||
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "80", isRequired: false),
|
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "80", isRequired: false),
|
||||||
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
|
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false),
|
||||||
|
]
|
||||||
let hevcCodecConditions: [ProfileCondition] = [
|
let hevcCodecConditions: [ProfileCondition] = [
|
||||||
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
ProfileCondition(condition: .notEquals, property: .isAnamorphic, value: "true", isRequired: false),
|
||||||
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|main 10", isRequired: false),
|
ProfileCondition(condition: .equalsAny, property: .videoProfile, value: "high|main|main 10", isRequired: false),
|
||||||
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "175", isRequired: false),
|
ProfileCondition(condition: .lessThanEqual, property: .videoLevel, value: "175", isRequired: false),
|
||||||
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false)]
|
ProfileCondition(condition: .notEquals, property: .isInterlaced, value: "true", isRequired: false),
|
||||||
|
]
|
||||||
|
|
||||||
codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264"))
|
codecProfiles.append(CodecProfile(type: .video, applyConditions: h264CodecConditions, codec: "h264"))
|
||||||
|
|
||||||
|
@ -124,13 +146,37 @@ class DeviceProfileBuilder {
|
||||||
|
|
||||||
let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")]
|
let responseProfiles: [ResponseProfile] = [ResponseProfile(container: "m4v", type: .video, mimeType: "video/mp4")]
|
||||||
|
|
||||||
let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate, musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate, directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [], codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles)
|
let profile = DeviceProfile(maxStreamingBitrate: maxStreamingBitrate, maxStaticBitrate: maxStaticBitrate,
|
||||||
|
musicStreamingTranscodingBitrate: musicStreamingTranscodingBitrate,
|
||||||
|
directPlayProfiles: directPlayProfiles, transcodingProfiles: transcodingProfiles, containerProfiles: [],
|
||||||
|
codecProfiles: codecProfiles, responseProfiles: responseProfiles, subtitleProfiles: subtitleProfiles)
|
||||||
|
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
private func supportsFeature(minimumSupported: CPUModel) -> Bool {
|
private func supportsFeature(minimumSupported: CPUModel) -> Bool {
|
||||||
let intValues: [CPUModel: Int] = [.A4: 1, .A5: 2, .A5X: 3, .A6: 4, .A6X: 5, .A7: 6, .A7X: 7, .A8: 8, .A8X: 9, .A9: 10, .A9X: 11, .A10: 12, .A10X: 13, .A11: 14, .A12: 15, .A12X: 16, .A12Z: 16, .A13: 17, .A14: 18, .A99: 99]
|
let intValues: [CPUModel: Int] = [
|
||||||
|
.A4: 1,
|
||||||
|
.A5: 2,
|
||||||
|
.A5X: 3,
|
||||||
|
.A6: 4,
|
||||||
|
.A6X: 5,
|
||||||
|
.A7: 6,
|
||||||
|
.A7X: 7,
|
||||||
|
.A8: 8,
|
||||||
|
.A8X: 9,
|
||||||
|
.A9: 10,
|
||||||
|
.A9X: 11,
|
||||||
|
.A10: 12,
|
||||||
|
.A10X: 13,
|
||||||
|
.A11: 14,
|
||||||
|
.A12: 15,
|
||||||
|
.A12X: 16,
|
||||||
|
.A12Z: 16,
|
||||||
|
.A13: 17,
|
||||||
|
.A14: 18,
|
||||||
|
.A99: 99,
|
||||||
|
]
|
||||||
return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0
|
return intValues[CPUinfo()] ?? 0 >= intValues[minimumSupported] ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation
|
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-detect-device-rotation
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
//
|
||||||
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
//
|
||||||
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum PlaybackSpeed: Double, CaseIterable {
|
||||||
|
case quarter = 0.25
|
||||||
|
case half = 0.5
|
||||||
|
case threeQuarter = 0.75
|
||||||
|
case one = 1.0
|
||||||
|
case oneQuarter = 1.25
|
||||||
|
case oneHalf = 1.5
|
||||||
|
case oneThreeQuarter = 1.75
|
||||||
|
case two = 2.0
|
||||||
|
|
||||||
|
var displayTitle: String {
|
||||||
|
switch self {
|
||||||
|
case .quarter:
|
||||||
|
return "0.25x"
|
||||||
|
case .half:
|
||||||
|
return "0.5x"
|
||||||
|
case .threeQuarter:
|
||||||
|
return "0.75x"
|
||||||
|
case .one:
|
||||||
|
return "1x"
|
||||||
|
case .oneQuarter:
|
||||||
|
return "1.25x"
|
||||||
|
case .oneHalf:
|
||||||
|
return "1.5x"
|
||||||
|
case .oneThreeQuarter:
|
||||||
|
return "1.75x"
|
||||||
|
case .two:
|
||||||
|
return "2x"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
/* JellyfinPlayer/Swiftfin is subject to the terms of the Mozilla Public
|
//
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
*
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
|
//
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import UIKit
|
||||||
|
|
||||||
enum VideoPlayerJumpLength: Int32, CaseIterable, Defaults.Serializable {
|
enum VideoPlayerJumpLength: Int32, CaseIterable, Defaults.Serializable {
|
||||||
case thirty = 30
|
case thirty = 30
|
||||||
|
@ -17,11 +16,11 @@ enum VideoPlayerJumpLength: Int32, CaseIterable, Defaults.Serializable {
|
||||||
case five = 5
|
case five = 5
|
||||||
|
|
||||||
var label: String {
|
var label: String {
|
||||||
return "\(self.rawValue) seconds"
|
"\(self.rawValue) seconds"
|
||||||
}
|
}
|
||||||
|
|
||||||
var shortLabel: String {
|
var shortLabel: String {
|
||||||
return "\(self.rawValue)s"
|
"\(self.rawValue)s"
|
||||||
}
|
}
|
||||||
|
|
||||||
var forwardImageLabel: String {
|
var forwardImageLabel: String {
|
||||||
|
|
|
@ -1,12 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Created by Noah Kamara
|
//
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
@ -14,7 +12,7 @@ public class ServerDiscovery {
|
||||||
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
|
public struct ServerLookupResponse: Codable, Hashable, Identifiable {
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
return hasher.combine(id)
|
hasher.combine(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
private let address: String
|
private let address: String
|
||||||
|
@ -24,6 +22,7 @@ public class ServerDiscovery {
|
||||||
public var url: URL {
|
public var url: URL {
|
||||||
URL(string: self.address)!
|
URL(string: self.address)!
|
||||||
}
|
}
|
||||||
|
|
||||||
public var host: String {
|
public var host: String {
|
||||||
let components = URLComponents(string: self.address)
|
let components = URLComponents(string: self.address)
|
||||||
if let host = components?.host {
|
if let host = components?.host {
|
||||||
|
@ -50,11 +49,9 @@ public class ServerDiscovery {
|
||||||
private let broadcastConn: UDPBroadcastConnection
|
private let broadcastConn: UDPBroadcastConnection
|
||||||
|
|
||||||
public init() {
|
public init() {
|
||||||
func receiveHandler(_ ipAddress: String, _ port: Int, _ response: Data) {
|
func receiveHandler(_ ipAddress: String, _ port: Int, _ response: Data) {}
|
||||||
}
|
|
||||||
|
|
||||||
func errorHandler(error: UDPBroadcastConnection.ConnectionError) {
|
func errorHandler(error: UDPBroadcastConnection.ConnectionError) {}
|
||||||
}
|
|
||||||
self.broadcastConn = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler)
|
self.broadcastConn = try! UDPBroadcastConnection(port: 7359, handler: receiveHandler, errorHandler: errorHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Created by Gunter Hager on 10.02.16.
|
//
|
||||||
* Copyright © 2016 Gunter Hager. All rights reserved.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Darwin
|
import Darwin
|
||||||
|
import Foundation
|
||||||
|
|
||||||
// Addresses
|
// Addresses
|
||||||
|
|
||||||
let INADDR_ANY = in_addr(s_addr: 0)
|
let INADDR_ANY = in_addr(s_addr: 0)
|
||||||
let INADDR_BROADCAST = in_addr(s_addr: 0xffffffff)
|
let INADDR_BROADCAST = in_addr(s_addr: 0xFFFF_FFFF)
|
||||||
|
|
||||||
/// An object representing the UDP broadcast connection. Uses a dispatch source to handle the incoming traffic on the UDP socket.
|
/// An object representing the UDP broadcast connection. Uses a dispatch source to handle the incoming traffic on the UDP socket.
|
||||||
open class UDPBroadcastConnection {
|
open class UDPBroadcastConnection {
|
||||||
|
@ -38,7 +36,7 @@ open class UDPBroadcastConnection {
|
||||||
var responseSource: DispatchSourceRead?
|
var responseSource: DispatchSourceRead?
|
||||||
|
|
||||||
/// The dispatch queue to run responseSource & reconnection on
|
/// The dispatch queue to run responseSource & reconnection on
|
||||||
var dispatchQueue: DispatchQueue = DispatchQueue.main
|
var dispatchQueue = DispatchQueue.main
|
||||||
|
|
||||||
/// Bind to port to start listening without first sending a message
|
/// Bind to port to start listening without first sending a message
|
||||||
var shouldBeBound: Bool = false
|
var shouldBeBound: Bool = false
|
||||||
|
@ -56,13 +54,11 @@ open class UDPBroadcastConnection {
|
||||||
/// - errorHandler: Handler that gets called when an error occurs.
|
/// - errorHandler: Handler that gets called when an error occurs.
|
||||||
/// - Throws: Throws a `ConnectionError` if an error occurs.
|
/// - Throws: Throws a `ConnectionError` if an error occurs.
|
||||||
public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws {
|
public init(port: UInt16, bindIt: Bool = false, handler: ReceiveHandler?, errorHandler: ErrorHandler?) throws {
|
||||||
self.address = sockaddr_in(
|
self.address = sockaddr_in(sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
||||||
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
|
|
||||||
sin_family: sa_family_t(AF_INET),
|
sin_family: sa_family_t(AF_INET),
|
||||||
sin_port: UDPBroadcastConnection.htonsPort(port: port),
|
sin_port: UDPBroadcastConnection.htonsPort(port: port),
|
||||||
sin_addr: INADDR_BROADCAST,
|
sin_addr: INADDR_BROADCAST,
|
||||||
sin_zero: ( 0, 0, 0, 0, 0, 0, 0, 0 )
|
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0))
|
||||||
)
|
|
||||||
|
|
||||||
self.handler = handler
|
self.handler = handler
|
||||||
self.errorHandler = errorHandler
|
self.errorHandler = errorHandler
|
||||||
|
@ -137,7 +133,8 @@ open class UDPBroadcastConnection {
|
||||||
let UDPSocket = Int32(source.handle)
|
let UDPSocket = Int32(source.handle)
|
||||||
|
|
||||||
let bytesRead = withUnsafeMutablePointer(to: &socketAddress) {
|
let bytesRead = withUnsafeMutablePointer(to: &socketAddress) {
|
||||||
recvfrom(UDPSocket, UnsafeMutableRawPointer(mutating: response), response.count, 0, UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength)
|
recvfrom(UDPSocket, UnsafeMutableRawPointer(mutating: response), response.count, 0,
|
||||||
|
UnsafeMutableRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1), &socketAddressLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
do {
|
do {
|
||||||
|
@ -154,7 +151,11 @@ open class UDPBroadcastConnection {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let endpoint = withUnsafePointer(to: &socketAddress, { self.getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0).bindMemory(to: sockaddr.self, capacity: 1)) })
|
guard let endpoint = withUnsafePointer(to: &socketAddress,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
.getEndpointFromSocketAddress(socketAddressPointer: UnsafeRawPointer($0)
|
||||||
|
.bindMemory(to: sockaddr.self, capacity: 1)) })
|
||||||
else {
|
else {
|
||||||
// debugPrint("Failed to get the address and port from the socket address received from recvfrom")
|
// debugPrint("Failed to get the address and port from the socket address received from recvfrom")
|
||||||
self.closeConnection()
|
self.closeConnection()
|
||||||
|
@ -174,7 +175,6 @@ open class UDPBroadcastConnection {
|
||||||
self.errorHandler?(ConnectionError.underlying(error: error))
|
self.errorHandler?(ConnectionError.underlying(error: error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newResponseSource.resume()
|
newResponseSource.resume()
|
||||||
|
@ -202,7 +202,7 @@ open class UDPBroadcastConnection {
|
||||||
guard let source = responseSource else { return }
|
guard let source = responseSource else { return }
|
||||||
let UDPSocket = Int32(source.handle)
|
let UDPSocket = Int32(source.handle)
|
||||||
let socketLength = socklen_t(address.sin_len)
|
let socketLength = socklen_t(address.sin_len)
|
||||||
try data.withUnsafeBytes { (broadcastMessage) in
|
try data.withUnsafeBytes { broadcastMessage in
|
||||||
let broadcastMessageLength = data.count
|
let broadcastMessageLength = data.count
|
||||||
let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in
|
let sent = withUnsafeMutablePointer(to: &address) { pointer -> Int in
|
||||||
let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1)
|
let memory = UnsafeRawPointer(pointer).bindMemory(to: sockaddr.self, capacity: 1)
|
||||||
|
@ -282,9 +282,8 @@ open class UDPBroadcastConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort {
|
fileprivate class func ntohs(value: CUnsignedShort) -> CUnsignedShort {
|
||||||
return (value << 8) + (value >> 8)
|
(value << 8) + (value >> 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Created by Gunter Hager on 25.03.19.
|
// Created by Gunter Hager on 25.03.19.
|
||||||
|
@ -312,5 +311,4 @@ public extension UDPBroadcastConnection {
|
||||||
// Underlying
|
// Underlying
|
||||||
case underlying(error: Error)
|
case underlying(error: Error)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import Puppy
|
import Puppy
|
||||||
|
@ -34,7 +33,7 @@ class LogManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
func logFileURL() -> URL {
|
func logFileURL() -> URL {
|
||||||
return self.getDocumentsDirectory().appendingPathComponent("logs.txt")
|
self.getDocumentsDirectory().appendingPathComponent("logs.txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDocumentsDirectory() -> URL {
|
func getDocumentsDirectory() -> URL {
|
||||||
|
@ -49,7 +48,8 @@ class LogManager {
|
||||||
class LogFormatter: LogFormattable {
|
class LogFormatter: LogFormattable {
|
||||||
func formatMessage(_ level: LogLevel, message: String, tag: String, function: String,
|
func formatMessage(_ level: LogLevel, message: String, tag: String, function: String,
|
||||||
file: String, line: UInt, swiftLogInfo: [String: String],
|
file: String, line: UInt, swiftLogInfo: [String: String],
|
||||||
label: String, date: Date, threadID: UInt64) -> String {
|
label: String, date: Date, threadID: UInt64) -> String
|
||||||
|
{
|
||||||
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
|
let file = shortFileName(file).replacingOccurrences(of: ".swift", with: "")
|
||||||
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
|
return " [\(level.emoji) \(level)] \(file)#\(line):\(function) \(message)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import CoreData
|
import CoreData
|
||||||
|
@ -18,8 +17,8 @@ import UIKit
|
||||||
typealias CurrentLogin = (server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User)
|
typealias CurrentLogin = (server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User)
|
||||||
|
|
||||||
// MARK: NewSessionManager
|
// MARK: NewSessionManager
|
||||||
final class SessionManager {
|
|
||||||
|
|
||||||
|
final class SessionManager {
|
||||||
|
|
||||||
// MARK: currentLogin
|
// MARK: currentLogin
|
||||||
|
|
||||||
|
@ -30,12 +29,15 @@ final class SessionManager {
|
||||||
static let main = SessionManager()
|
static let main = SessionManager()
|
||||||
|
|
||||||
// MARK: init
|
// MARK: init
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
if let lastUserID = Defaults[.lastServerUserID],
|
if let lastUserID = Defaults[.lastServerUserID],
|
||||||
let user = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
let user = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
||||||
[Where<SwiftfinStore.Models.StoredUser>("id == %@", lastUserID)]) {
|
[Where<SwiftfinStore.Models.StoredUser>("id == %@", lastUserID)])
|
||||||
|
{
|
||||||
|
|
||||||
guard let server = user.server, let accessToken = user.accessToken else { fatalError("No associated server or access token for last user?") }
|
guard let server = user.server,
|
||||||
|
let accessToken = user.accessToken else { fatalError("No associated server or access token for last user?") }
|
||||||
guard let existingServer = SwiftfinStore.dataStack.fetchExisting(server) else { return }
|
guard let existingServer = SwiftfinStore.dataStack.fetchExisting(server) else { return }
|
||||||
|
|
||||||
JellyfinAPI.basePath = server.currentURI
|
JellyfinAPI.basePath = server.currentURI
|
||||||
|
@ -45,20 +47,23 @@ final class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: fetchServers
|
// MARK: fetchServers
|
||||||
|
|
||||||
func fetchServers() -> [SwiftfinStore.State.Server] {
|
func fetchServers() -> [SwiftfinStore.State.Server] {
|
||||||
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.StoredServer>())
|
let servers = try! SwiftfinStore.dataStack.fetchAll(From<SwiftfinStore.Models.StoredServer>())
|
||||||
return servers.map({ $0.state })
|
return servers.map(\.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: fetchUsers
|
// MARK: fetchUsers
|
||||||
|
|
||||||
func fetchUsers(for server: SwiftfinStore.State.Server) -> [SwiftfinStore.State.User] {
|
func fetchUsers(for server: SwiftfinStore.State.Server) -> [SwiftfinStore.State.User] {
|
||||||
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id))
|
Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id))
|
||||||
else { fatalError("No stored server associated with given state server?") }
|
else { fatalError("No stored server associated with given state server?") }
|
||||||
return storedServer.users.map({ $0.state }).sorted(by: { $0.username < $1.username })
|
return storedServer.users.map(\.state).sorted(by: { $0.username < $1.username })
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: connectToServer publisher
|
// MARK: connectToServer publisher
|
||||||
|
|
||||||
// Connects to a server at the given uri, storing if successful
|
// Connects to a server at the given uri, storing if successful
|
||||||
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
func connectToServer(with uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
||||||
var uriComponents = URLComponents(string: uri) ?? URLComponents()
|
var uriComponents = URLComponents(string: uri) ?? URLComponents()
|
||||||
|
@ -76,7 +81,7 @@ final class SessionManager {
|
||||||
JellyfinAPI.basePath = uri
|
JellyfinAPI.basePath = uri
|
||||||
|
|
||||||
return SystemAPI.getPublicSystemInfo()
|
return SystemAPI.getPublicSystemInfo()
|
||||||
.tryMap({ response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
.tryMap { response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
||||||
|
|
||||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||||
let newServer = transaction.create(Into<SwiftfinStore.Models.StoredServer>())
|
let newServer = transaction.create(Into<SwiftfinStore.Models.StoredServer>())
|
||||||
|
@ -96,30 +101,35 @@ final class SessionManager {
|
||||||
|
|
||||||
// Check for existing server on device
|
// Check for existing server on device
|
||||||
if let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
if let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
[Where<SwiftfinStore.Models.StoredServer>("id == %@", newServer.id)]) {
|
[Where<SwiftfinStore.Models.StoredServer>("id == %@",
|
||||||
|
newServer.id)])
|
||||||
|
{
|
||||||
throw SwiftfinStore.Errors.existingServer(existingServer.state)
|
throw SwiftfinStore.Errors.existingServer(existingServer.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (newServer, transaction)
|
return (newServer, transaction)
|
||||||
})
|
}
|
||||||
.handleEvents(receiveOutput: { (_, transaction) in
|
.handleEvents(receiveOutput: { _, transaction in
|
||||||
try? transaction.commitAndWait()
|
try? transaction.commitAndWait()
|
||||||
})
|
})
|
||||||
.map({ (server, _) in
|
.map { server, _ in
|
||||||
return server.state
|
server.state
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: addURIToServer publisher
|
// MARK: addURIToServer publisher
|
||||||
|
|
||||||
func addURIToServer(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
func addURIToServer(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
||||||
return Just(server)
|
Just(server)
|
||||||
.tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
.tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
||||||
|
|
||||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||||
|
|
||||||
guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)]) else {
|
[Where<SwiftfinStore.Models.StoredServer>("id == %@",
|
||||||
|
server.id)])
|
||||||
|
else {
|
||||||
fatalError("No stored server associated with given state server?")
|
fatalError("No stored server associated with given state server?")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,24 +138,27 @@ final class SessionManager {
|
||||||
|
|
||||||
return (editServer, transaction)
|
return (editServer, transaction)
|
||||||
}
|
}
|
||||||
.handleEvents(receiveOutput: { (_, transaction) in
|
.handleEvents(receiveOutput: { _, transaction in
|
||||||
try? transaction.commitAndWait()
|
try? transaction.commitAndWait()
|
||||||
})
|
})
|
||||||
.map({ (server, _) in
|
.map { server, _ in
|
||||||
return server.state
|
server.state
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: setServerCurrentURI publisher
|
// MARK: setServerCurrentURI publisher
|
||||||
|
|
||||||
func setServerCurrentURI(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
func setServerCurrentURI(server: SwiftfinStore.State.Server, uri: String) -> AnyPublisher<SwiftfinStore.State.Server, Error> {
|
||||||
return Just(server)
|
Just(server)
|
||||||
.tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
.tryMap { server -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
|
||||||
|
|
||||||
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
let transaction = SwiftfinStore.dataStack.beginUnsafe()
|
||||||
|
|
||||||
guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
guard let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)]) else {
|
[Where<SwiftfinStore.Models.StoredServer>("id == %@",
|
||||||
|
server.id)])
|
||||||
|
else {
|
||||||
fatalError("No stored server associated with given state server?")
|
fatalError("No stored server associated with given state server?")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,24 +171,27 @@ final class SessionManager {
|
||||||
|
|
||||||
return (editServer, transaction)
|
return (editServer, transaction)
|
||||||
}
|
}
|
||||||
.handleEvents(receiveOutput: { (_, transaction) in
|
.handleEvents(receiveOutput: { _, transaction in
|
||||||
try? transaction.commitAndWait()
|
try? transaction.commitAndWait()
|
||||||
})
|
})
|
||||||
.map({ (server, _) in
|
.map { server, _ in
|
||||||
return server.state
|
server.state
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: loginUser publisher
|
// MARK: loginUser publisher
|
||||||
|
|
||||||
// Logs in a user with an associated server, storing if successful
|
// Logs in a user with an associated server, storing if successful
|
||||||
func loginUser(server: SwiftfinStore.State.Server, username: String, password: String) -> AnyPublisher<SwiftfinStore.State.User, Error> {
|
func loginUser(server: SwiftfinStore.State.Server, username: String,
|
||||||
|
password: String) -> AnyPublisher<SwiftfinStore.State.User, Error>
|
||||||
|
{
|
||||||
setAuthHeader(with: "")
|
setAuthHeader(with: "")
|
||||||
|
|
||||||
JellyfinAPI.basePath = server.currentURI
|
JellyfinAPI.basePath = server.currentURI
|
||||||
|
|
||||||
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
|
||||||
.tryMap({ response -> (SwiftfinStore.Models.StoredServer, SwiftfinStore.Models.StoredUser, UnsafeDataTransaction) in
|
.tryMap { response -> (SwiftfinStore.Models.StoredServer, SwiftfinStore.Models.StoredUser, UnsafeDataTransaction) in
|
||||||
|
|
||||||
guard let accessToken = response.accessToken else { throw JellyfinAPIError("Access token missing from network call") }
|
guard let accessToken = response.accessToken else { throw JellyfinAPIError("Access token missing from network call") }
|
||||||
|
|
||||||
|
@ -191,7 +207,9 @@ final class SessionManager {
|
||||||
|
|
||||||
// Check for existing user on device
|
// Check for existing user on device
|
||||||
if let existingUser = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
if let existingUser = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
||||||
[Where<SwiftfinStore.Models.StoredUser>("id == %@", newUser.id)]) {
|
[Where<SwiftfinStore.Models.StoredUser>("id == %@",
|
||||||
|
newUser.id)])
|
||||||
|
{
|
||||||
throw SwiftfinStore.Errors.existingUser(existingUser.state)
|
throw SwiftfinStore.Errors.existingUser(existingUser.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,15 +218,18 @@ final class SessionManager {
|
||||||
newUser.accessToken = newAccessToken
|
newUser.accessToken = newAccessToken
|
||||||
|
|
||||||
guard let userServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
guard let userServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)])
|
[
|
||||||
|
Where<SwiftfinStore.Models.StoredServer>("id == %@",
|
||||||
|
server.id),
|
||||||
|
])
|
||||||
else { fatalError("No stored server associated with given state server?") }
|
else { fatalError("No stored server associated with given state server?") }
|
||||||
|
|
||||||
guard let editUserServer = transaction.edit(userServer) else { fatalError("Can't get proxy for existing object?") }
|
guard let editUserServer = transaction.edit(userServer) else { fatalError("Can't get proxy for existing object?") }
|
||||||
editUserServer.users.insert(newUser)
|
editUserServer.users.insert(newUser)
|
||||||
|
|
||||||
return (editUserServer, newUser, transaction)
|
return (editUserServer, newUser, transaction)
|
||||||
})
|
}
|
||||||
.handleEvents(receiveOutput: { [unowned self] (server, user, transaction) in
|
.handleEvents(receiveOutput: { [unowned self] server, user, transaction in
|
||||||
setAuthHeader(with: user.accessToken?.value ?? "")
|
setAuthHeader(with: user.accessToken?.value ?? "")
|
||||||
try? transaction.commitAndWait()
|
try? transaction.commitAndWait()
|
||||||
|
|
||||||
|
@ -221,13 +242,14 @@ final class SessionManager {
|
||||||
currentLogin = (server: currentServer.state, user: currentUser.state)
|
currentLogin = (server: currentServer.state, user: currentUser.state)
|
||||||
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
|
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignIn, object: nil)
|
||||||
})
|
})
|
||||||
.map({ (_, user, _) in
|
.map { _, user, _ in
|
||||||
return user.state
|
user.state
|
||||||
})
|
}
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: loginUser
|
// MARK: loginUser
|
||||||
|
|
||||||
func loginUser(server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User) {
|
func loginUser(server: SwiftfinStore.State.Server, user: SwiftfinStore.State.User) {
|
||||||
JellyfinAPI.basePath = server.currentURI
|
JellyfinAPI.basePath = server.currentURI
|
||||||
Defaults[.lastServerUserID] = user.id
|
Defaults[.lastServerUserID] = user.id
|
||||||
|
@ -237,6 +259,7 @@ final class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: logout
|
// MARK: logout
|
||||||
|
|
||||||
func logout() {
|
func logout() {
|
||||||
currentLogin = nil
|
currentLogin = nil
|
||||||
JellyfinAPI.basePath = ""
|
JellyfinAPI.basePath = ""
|
||||||
|
@ -246,6 +269,7 @@ final class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: purge
|
// MARK: purge
|
||||||
|
|
||||||
func purge() {
|
func purge() {
|
||||||
// Delete all servers
|
// Delete all servers
|
||||||
let servers = fetchServers()
|
let servers = fetchServers()
|
||||||
|
@ -258,16 +282,20 @@ final class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: delete user
|
// MARK: delete user
|
||||||
|
|
||||||
func delete(user: SwiftfinStore.State.User) {
|
func delete(user: SwiftfinStore.State.User) {
|
||||||
guard let storedUser = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
guard let storedUser = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
|
||||||
[Where<SwiftfinStore.Models.StoredUser>("id == %@", user.id)]) else { fatalError("No stored user for state user?")}
|
[Where<SwiftfinStore.Models.StoredUser>("id == %@", user.id)])
|
||||||
|
else { fatalError("No stored user for state user?") }
|
||||||
_delete(user: storedUser, transaction: nil)
|
_delete(user: storedUser, transaction: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: delete server
|
// MARK: delete server
|
||||||
|
|
||||||
func delete(server: SwiftfinStore.State.Server) {
|
func delete(server: SwiftfinStore.State.Server) {
|
||||||
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
guard let storedServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
|
||||||
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)]) else { fatalError("No stored server for state server?")}
|
[Where<SwiftfinStore.Models.StoredServer>("id == %@", server.id)])
|
||||||
|
else { fatalError("No stored server for state server?") }
|
||||||
_delete(server: storedServer, transaction: nil)
|
_delete(server: storedServer, transaction: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,17 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum SwiftfinNotificationCenter {
|
enum SwiftfinNotificationCenter {
|
||||||
|
|
||||||
static let main: NotificationCenter = {
|
static let main: NotificationCenter = {
|
||||||
return NotificationCenter()
|
NotificationCenter()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
enum Keys {
|
enum Keys {
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import Defaults
|
import Defaults
|
||||||
|
import Foundation
|
||||||
|
|
||||||
enum SwiftfinStore {
|
enum SwiftfinStore {
|
||||||
|
|
||||||
// MARK: State
|
// MARK: State
|
||||||
|
|
||||||
// Safe, copyable representations of their underlying CoreStoredObject
|
// Safe, copyable representations of their underlying CoreStoredObject
|
||||||
// Relationships are represented by the related object's IDs or value
|
// Relationships are represented by the related object's IDs or value
|
||||||
enum State {
|
enum State {
|
||||||
|
@ -27,7 +27,9 @@ enum SwiftfinStore {
|
||||||
let version: String
|
let version: String
|
||||||
let userIDs: [String]
|
let userIDs: [String]
|
||||||
|
|
||||||
fileprivate init(uris: Set<String>, currentURI: String, name: String, id: String, os: String, version: String, usersIDs: [String]) {
|
fileprivate init(uris: Set<String>, currentURI: String, name: String, id: String, os: String, version: String,
|
||||||
|
usersIDs: [String])
|
||||||
|
{
|
||||||
self.uris = uris
|
self.uris = uris
|
||||||
self.currentURI = currentURI
|
self.currentURI = currentURI
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -38,7 +40,7 @@ enum SwiftfinStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
static var sample: Server {
|
static var sample: Server {
|
||||||
return Server(uris: ["https://www.notaurl.com", "http://www.maybeaurl.org"],
|
Server(uris: ["https://www.notaurl.com", "http://www.maybeaurl.org"],
|
||||||
currentURI: "https://www.notaurl.com",
|
currentURI: "https://www.notaurl.com",
|
||||||
name: "Johnny's Tree",
|
name: "Johnny's Tree",
|
||||||
id: "123abc",
|
id: "123abc",
|
||||||
|
@ -62,7 +64,7 @@ enum SwiftfinStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
static var sample: User {
|
static var sample: User {
|
||||||
return User(username: "JohnnyAppleseed",
|
User(username: "JohnnyAppleseed",
|
||||||
id: "123abc",
|
id: "123abc",
|
||||||
serverID: "123abc",
|
serverID: "123abc",
|
||||||
accessToken: "open-sesame")
|
accessToken: "open-sesame")
|
||||||
|
@ -71,6 +73,7 @@ enum SwiftfinStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Models
|
// MARK: Models
|
||||||
|
|
||||||
enum Models {
|
enum Models {
|
||||||
|
|
||||||
final class StoredServer: CoreStoreObject {
|
final class StoredServer: CoreStoreObject {
|
||||||
|
@ -97,13 +100,13 @@ enum SwiftfinStore {
|
||||||
var users: Set<StoredUser>
|
var users: Set<StoredUser>
|
||||||
|
|
||||||
var state: State.Server {
|
var state: State.Server {
|
||||||
return State.Server(uris: uris,
|
State.Server(uris: uris,
|
||||||
currentURI: currentURI,
|
currentURI: currentURI,
|
||||||
name: name,
|
name: name,
|
||||||
id: id,
|
id: id,
|
||||||
os: os,
|
os: os,
|
||||||
version: version,
|
version: version,
|
||||||
usersIDs: users.map({ $0.id }))
|
usersIDs: users.map(\.id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,37 +148,51 @@ enum SwiftfinStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Errors
|
// MARK: Errors
|
||||||
|
|
||||||
enum Errors {
|
enum Errors {
|
||||||
case existingServer(State.Server)
|
case existingServer(State.Server)
|
||||||
case existingUser(State.User)
|
case existingUser(State.User)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: dataStack
|
// MARK: dataStack
|
||||||
|
|
||||||
static let dataStack: DataStack = {
|
static let dataStack: DataStack = {
|
||||||
let schema = CoreStoreSchema(modelVersion: "V1",
|
let schema = CoreStoreSchema(modelVersion: "V1",
|
||||||
entities: [
|
entities: [
|
||||||
Entity<SwiftfinStore.Models.StoredServer>("Server"),
|
Entity<SwiftfinStore.Models.StoredServer>("Server"),
|
||||||
Entity<SwiftfinStore.Models.StoredUser>("User"),
|
Entity<SwiftfinStore.Models.StoredUser>("User"),
|
||||||
Entity<SwiftfinStore.Models.StoredAccessToken>("AccessToken")
|
Entity<SwiftfinStore.Models.StoredAccessToken>("AccessToken"),
|
||||||
],
|
],
|
||||||
versionLock: [
|
versionLock: [
|
||||||
"AccessToken": [0xa8c475e874494bb1, 0x79486e93449f0b3d, 0xa7dc4a0003541edb, 0x94183fae7580ef72],
|
"AccessToken": [
|
||||||
"Server": [0x936b46acd8e8f0e3, 0x59890d4d9f3f885f, 0x819cf7a4abf98b22, 0xe16125c5af885a06],
|
0xA8C4_75E8_7449_4BB1,
|
||||||
"User": [0x845de08a74bc53ed, 0xe95a406a29f3a5d0, 0x9eda732821a15ea9, 0xb5afa531e41ce8a]
|
0x7948_6E93_449F_0B3D,
|
||||||
|
0xA7DC_4A00_0354_1EDB,
|
||||||
|
0x9418_3FAE_7580_EF72,
|
||||||
|
],
|
||||||
|
"Server": [
|
||||||
|
0x936B_46AC_D8E8_F0E3,
|
||||||
|
0x5989_0D4D_9F3F_885F,
|
||||||
|
0x819C_F7A4_ABF9_8B22,
|
||||||
|
0xE161_25C5_AF88_5A06,
|
||||||
|
],
|
||||||
|
"User": [
|
||||||
|
0x845D_E08A_74BC_53ED,
|
||||||
|
0xE95A_406A_29F3_A5D0,
|
||||||
|
0x9EDA_7328_21A1_5EA9,
|
||||||
|
0xB5A_FA53_1E41_CE8A,
|
||||||
|
],
|
||||||
])
|
])
|
||||||
|
|
||||||
let _dataStack = DataStack(schema)
|
let _dataStack = DataStack(schema)
|
||||||
try! _dataStack.addStorageAndWait(
|
try! _dataStack.addStorageAndWait(SQLiteStore(fileName: "Swiftfin.sqlite",
|
||||||
SQLiteStore(
|
localStorageOptions: .recreateStoreOnModelMismatch))
|
||||||
fileName: "Swiftfin.sqlite",
|
|
||||||
localStorageOptions: .recreateStoreOnModelMismatch
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return _dataStack
|
return _dataStack
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: LocalizedError
|
// MARK: LocalizedError
|
||||||
|
|
||||||
extension SwiftfinStore.Errors: LocalizedError {
|
extension SwiftfinStore.Errors: LocalizedError {
|
||||||
|
|
||||||
var title: String {
|
var title: String {
|
||||||
|
@ -189,9 +206,9 @@ extension SwiftfinStore.Errors: LocalizedError {
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
case .existingServer(let server):
|
case let .existingServer(server):
|
||||||
return "Server \(server.name) already exists with same server ID"
|
return "Server \(server.name) already exists with same server ID"
|
||||||
case .existingUser(let user):
|
case let .existingUser(user):
|
||||||
return "User \(user.username) already exists with same user ID"
|
return "User \(user.username) already exists with same user ID"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Defaults
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -15,11 +14,11 @@ extension SwiftfinStore {
|
||||||
enum Defaults {
|
enum Defaults {
|
||||||
|
|
||||||
static let generalSuite: UserDefaults = {
|
static let generalSuite: UserDefaults = {
|
||||||
return UserDefaults(suiteName: "swiftfinstore-general-defaults")!
|
UserDefaults(suiteName: "swiftfinstore-general-defaults")!
|
||||||
}()
|
}()
|
||||||
|
|
||||||
static let universalSuite: UserDefaults = {
|
static let universalSuite: UserDefaults = {
|
||||||
return UserDefaults(suiteName: "swiftfinstore-universal-defaults")!
|
UserDefaults(suiteName: "swiftfinstore-universal-defaults")!
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +34,8 @@ extension Defaults.Keys {
|
||||||
static let inNetworkBandwidth = Key<Int>("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite)
|
static let inNetworkBandwidth = Key<Int>("InNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let outOfNetworkBandwidth = Key<Int>("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite)
|
static let outOfNetworkBandwidth = Key<Int>("OutOfNetworkBandwidth", default: 40_000_000, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let isAutoSelectSubtitles = Key<Bool>("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
static let isAutoSelectSubtitles = Key<Bool>("isAutoSelectSubtitles", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let autoSelectSubtitlesLangCode = Key<String>("AutoSelectSubtitlesLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite)
|
static let autoSelectSubtitlesLangCode = Key<String>("AutoSelectSubtitlesLangCode", default: "Auto",
|
||||||
|
suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let autoSelectAudioLangCode = Key<String>("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite)
|
static let autoSelectAudioLangCode = Key<String>("AutoSelectAudioLangCode", default: "Auto", suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
|
|
||||||
// Customize settings
|
// Customize settings
|
||||||
|
@ -45,8 +45,10 @@ extension Defaults.Keys {
|
||||||
// Video player / overlay settings
|
// Video player / overlay settings
|
||||||
static let overlayType = Key<OverlayType>("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite)
|
static let overlayType = Key<OverlayType>("overlayType", default: .normal, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let jumpGesturesEnabled = Key<Bool>("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
static let jumpGesturesEnabled = Key<Bool>("gesturesEnabled", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite)
|
static let videoPlayerJumpForward = Key<VideoPlayerJumpLength>("videoPlayerJumpForward", default: .fifteen,
|
||||||
static let videoPlayerJumpBackward = Key<VideoPlayerJumpLength>("videoPlayerJumpBackward", default: .fifteen, suite: SwiftfinStore.Defaults.generalSuite)
|
suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
|
static let videoPlayerJumpBackward = Key<VideoPlayerJumpLength>("videoPlayerJumpBackward", default: .fifteen,
|
||||||
|
suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let autoplayEnabled = Key<Bool>("autoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
static let autoplayEnabled = Key<Bool>("autoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let resumeOffset = Key<Bool>("resumeOffset", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
static let resumeOffset = Key<Bool>("resumeOffset", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
|
|
||||||
|
@ -56,11 +58,13 @@ extension Defaults.Keys {
|
||||||
static let shouldShowAutoPlay = Key<Bool>("shouldShowAutoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
static let shouldShowAutoPlay = Key<Bool>("shouldShowAutoPlayNextItem", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
|
|
||||||
// Should show video player items in overlay menu
|
// Should show video player items in overlay menu
|
||||||
static let shouldShowJumpButtonsInOverlayMenu = Key<Bool>("shouldShowJumpButtonsInMenu", default: true, suite: SwiftfinStore.Defaults.generalSuite)
|
static let shouldShowJumpButtonsInOverlayMenu = Key<Bool>("shouldShowJumpButtonsInMenu", default: true,
|
||||||
|
suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
|
|
||||||
// Experimental settings
|
// Experimental settings
|
||||||
struct Experimental {
|
enum Experimental {
|
||||||
static let syncSubtitleStateWithAdjacent = Key<Bool>("experimental.syncSubtitleState", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
static let syncSubtitleStateWithAdjacent = Key<Bool>("experimental.syncSubtitleState", default: false,
|
||||||
|
suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
static let liveTVAlphaEnabled = Key<Bool>("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
static let liveTVAlphaEnabled = Key<Bool>("liveTVAlphaEnabled", default: false, suite: SwiftfinStore.Defaults.generalSuite)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -18,16 +17,20 @@ struct AddServerURIPayload: Identifiable {
|
||||||
let uri: String
|
let uri: String
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
return server.id.appending(uri)
|
server.id.appending(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class ConnectToServerViewModel: ViewModel {
|
final class ConnectToServerViewModel: ViewModel {
|
||||||
|
|
||||||
@RouterObject var router: ConnectToServerCoodinator.Router?
|
@RouterObject
|
||||||
@Published var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
|
var router: ConnectToServerCoodinator.Router?
|
||||||
@Published var searching = false
|
@Published
|
||||||
@Published var addServerURIPayload: AddServerURIPayload?
|
var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
|
||||||
|
@Published
|
||||||
|
var searching = false
|
||||||
|
@Published
|
||||||
|
var addServerURIPayload: AddServerURIPayload?
|
||||||
var backAddServerURIPayload: AddServerURIPayload?
|
var backAddServerURIPayload: AddServerURIPayload?
|
||||||
|
|
||||||
private let discovery = ServerDiscovery()
|
private let discovery = ServerDiscovery()
|
||||||
|
@ -58,20 +61,22 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
// This is disgusting. ViewModel Error handling overall needs to be refactored
|
// This is disgusting. ViewModel Error handling overall needs to be refactored
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished: ()
|
case .finished: ()
|
||||||
case .failure(let error):
|
case let .failure(error):
|
||||||
switch error {
|
switch error {
|
||||||
case is SwiftfinStore.Errors:
|
case is SwiftfinStore.Errors:
|
||||||
let swiftfinError = error as! SwiftfinStore.Errors
|
let swiftfinError = error as! SwiftfinStore.Errors
|
||||||
switch swiftfinError {
|
switch swiftfinError {
|
||||||
case .existingServer(let server):
|
case let .existingServer(server):
|
||||||
self.addServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
self.addServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
||||||
self.backAddServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
self.backAddServerURIPayload = AddServerURIPayload(server: server, uri: uri)
|
||||||
default:
|
default:
|
||||||
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer",
|
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical,
|
||||||
|
tag: "connectToServer",
|
||||||
completion: completion)
|
completion: completion)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer",
|
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical,
|
||||||
|
tag: "connectToServer",
|
||||||
completion: completion)
|
completion: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +111,8 @@ final class ConnectToServerViewModel: ViewModel {
|
||||||
} receiveValue: { server in
|
} receiveValue: { server in
|
||||||
SessionManager.main.setServerCurrentURI(server: server, uri: addServerURIPayload.uri)
|
SessionManager.main.setServerCurrentURI(server: server, uri: addServerURIPayload.uri)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "connectToServer",
|
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical,
|
||||||
|
tag: "connectToServer",
|
||||||
completion: completion)
|
completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
self.router?.dismissCoordinator()
|
self.router?.dismissCoordinator()
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
@ -14,9 +13,12 @@ final class EpisodesRowViewModel: ViewModel {
|
||||||
|
|
||||||
// TODO: Protocol these viewmodels for generalization instead of Episode
|
// TODO: Protocol these viewmodels for generalization instead of Episode
|
||||||
|
|
||||||
@ObservedObject var episodeItemViewModel: EpisodeItemViewModel
|
@ObservedObject
|
||||||
@Published var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:]
|
var episodeItemViewModel: EpisodeItemViewModel
|
||||||
@Published var selectedSeason: BaseItemDto? {
|
@Published
|
||||||
|
var seasonsEpisodes: [BaseItemDto: [BaseItemDto]] = [:]
|
||||||
|
@Published
|
||||||
|
var selectedSeason: BaseItemDto? {
|
||||||
willSet {
|
willSet {
|
||||||
if seasonsEpisodes[newValue!]!.isEmpty {
|
if seasonsEpisodes[newValue!]!.isEmpty {
|
||||||
retrieveEpisodesForSeason(newValue!)
|
retrieveEpisodesForSeason(newValue!)
|
||||||
|
@ -70,8 +72,10 @@ final class SingleSeasonEpisodesRowViewModel: ViewModel {
|
||||||
|
|
||||||
// TODO: Protocol these viewmodels for generalization instead of Season
|
// TODO: Protocol these viewmodels for generalization instead of Season
|
||||||
|
|
||||||
@ObservedObject var seasonItemViewModel: SeasonItemViewModel
|
@ObservedObject
|
||||||
@Published var episodes: [BaseItemDto]
|
var seasonItemViewModel: SeasonItemViewModel
|
||||||
|
@Published
|
||||||
|
var episodes: [BaseItemDto]
|
||||||
|
|
||||||
init(seasonItemViewModel: SeasonItemViewModel) {
|
init(seasonItemViewModel: SeasonItemViewModel) {
|
||||||
self.seasonItemViewModel = seasonItemViewModel
|
self.seasonItemViewModel = seasonItemViewModel
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import ActivityIndicator
|
import ActivityIndicator
|
||||||
import Combine
|
import Combine
|
||||||
|
@ -14,14 +13,19 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class HomeViewModel: ViewModel {
|
final class HomeViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var latestAddedItems: [BaseItemDto] = []
|
@Published
|
||||||
@Published var resumeItems: [BaseItemDto] = []
|
var latestAddedItems: [BaseItemDto] = []
|
||||||
@Published var nextUpItems: [BaseItemDto] = []
|
@Published
|
||||||
@Published var librariesShowRecentlyAddedIDs: [String] = []
|
var resumeItems: [BaseItemDto] = []
|
||||||
@Published var libraries: [BaseItemDto] = []
|
@Published
|
||||||
|
var nextUpItems: [BaseItemDto] = []
|
||||||
|
@Published
|
||||||
|
var librariesShowRecentlyAddedIDs: [String] = []
|
||||||
|
@Published
|
||||||
|
var libraries: [BaseItemDto] = []
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
var recentFilterSet: LibraryFilters = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
var recentFilterSet = LibraryFilters(filters: [], sortOrder: [.descending], sortBy: [.dateAdded])
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -35,7 +39,8 @@ final class HomeViewModel: ViewModel {
|
||||||
nc.addObserver(self, selector: #selector(didSignOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
nc.addObserver(self, selector: #selector(didSignOut), name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func didSignIn() {
|
@objc
|
||||||
|
private func didSignIn() {
|
||||||
for cancellable in cancellables {
|
for cancellable in cancellables {
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
}
|
}
|
||||||
|
@ -48,7 +53,8 @@ final class HomeViewModel: ViewModel {
|
||||||
refresh()
|
refresh()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func didSignOut() {
|
@objc
|
||||||
|
private func didSignOut() {
|
||||||
for cancellable in cancellables {
|
for cancellable in cancellables {
|
||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
}
|
}
|
||||||
|
@ -56,7 +62,8 @@ final class HomeViewModel: ViewModel {
|
||||||
cancellables.removeAll()
|
cancellables.removeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func refresh() {
|
@objc
|
||||||
|
func refresh() {
|
||||||
LogManager.shared.log.debug("Refresh called.")
|
LogManager.shared.log.debug("Refresh called.")
|
||||||
|
|
||||||
refreshLibrariesLatest()
|
refreshLibrariesLatest()
|
||||||
|
@ -66,6 +73,7 @@ final class HomeViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Libraries Latest Items
|
// MARK: Libraries Latest Items
|
||||||
|
|
||||||
private func refreshLibrariesLatest() {
|
private func refreshLibrariesLatest() {
|
||||||
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -82,7 +90,8 @@ final class HomeViewModel: ViewModel {
|
||||||
var newLibraries: [BaseItemDto] = []
|
var newLibraries: [BaseItemDto] = []
|
||||||
|
|
||||||
response.items!.forEach { item in
|
response.items!.forEach { item in
|
||||||
LogManager.shared.log.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
LogManager.shared.log
|
||||||
|
.debug("Retrieved user view: \(item.id!) (\(item.name ?? "nil")) with type \(item.collectionType ?? "nil")")
|
||||||
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
if item.collectionType == "movies" || item.collectionType == "tvshows" {
|
||||||
newLibraries.append(item)
|
newLibraries.append(item)
|
||||||
}
|
}
|
||||||
|
@ -98,11 +107,12 @@ final class HomeViewModel: ViewModel {
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
}
|
}
|
||||||
}, receiveValue: { response in
|
}, receiveValue: { response in
|
||||||
let excludeIDs = response.configuration?.latestItemsExcludes != nil ? response.configuration!.latestItemsExcludes! : []
|
let excludeIDs = response.configuration?.latestItemsExcludes != nil ? response.configuration!
|
||||||
|
.latestItemsExcludes! : []
|
||||||
|
|
||||||
for excludeID in excludeIDs {
|
for excludeID in excludeIDs {
|
||||||
newLibraries.removeAll { library in
|
newLibraries.removeAll { library in
|
||||||
return library.id == excludeID
|
library.id == excludeID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,9 +124,18 @@ final class HomeViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Latest Added Items
|
// MARK: Latest Added Items
|
||||||
|
|
||||||
private func refreshLatestAddedItems() {
|
private func refreshLatestAddedItems() {
|
||||||
UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.user.id,
|
UserLibraryAPI.getLatestMedia(userId: SessionManager.main.currentLogin.user.id,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
fields: [
|
||||||
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
enableImageTypes: [.primary, .backdrop, .thumb],
|
enableImageTypes: [.primary, .backdrop, .thumb],
|
||||||
enableUserData: true,
|
enableUserData: true,
|
||||||
limit: 8)
|
limit: 8)
|
||||||
|
@ -136,10 +155,19 @@ final class HomeViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Resume Items
|
// MARK: Resume Items
|
||||||
|
|
||||||
private func refreshResumeItems() {
|
private func refreshResumeItems() {
|
||||||
ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id,
|
ItemsAPI.getResumeItems(userId: SessionManager.main.currentLogin.user.id,
|
||||||
limit: 6,
|
limit: 6,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
fields: [
|
||||||
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
enableUserData: true)
|
enableUserData: true)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
@ -158,10 +186,19 @@ final class HomeViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Next Up Items
|
// MARK: Next Up Items
|
||||||
|
|
||||||
private func refreshNextUpItems() {
|
private func refreshNextUpItems() {
|
||||||
TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id,
|
TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id,
|
||||||
limit: 6,
|
limit: 6,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
fields: [
|
||||||
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
enableUserData: true)
|
enableUserData: true)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class CollectionItemViewModel: ItemViewModel {
|
final class CollectionItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
@Published var collectionItems: [BaseItemDto] = []
|
@Published
|
||||||
|
var collectionItems: [BaseItemDto] = []
|
||||||
|
|
||||||
override init(item: BaseItemDto) {
|
override init(item: BaseItemDto) {
|
||||||
super.init(item: item)
|
super.init(item: item)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -14,8 +13,10 @@ import Stinsen
|
||||||
|
|
||||||
final class EpisodeItemViewModel: ItemViewModel {
|
final class EpisodeItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
@RouterObject var itemRouter: ItemCoordinator.Router?
|
@RouterObject
|
||||||
@Published var series: BaseItemDto?
|
var itemRouter: ItemCoordinator.Router?
|
||||||
|
@Published
|
||||||
|
var series: BaseItemDto?
|
||||||
|
|
||||||
override init(item: BaseItemDto) {
|
override init(item: BaseItemDto) {
|
||||||
super.init(item: item)
|
super.init(item: item)
|
||||||
|
@ -29,7 +30,7 @@ final class EpisodeItemViewModel: ItemViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldDisplayRuntime() -> Bool {
|
override func shouldDisplayRuntime() -> Bool {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getEpisodeSeries() {
|
func getEpisodeSeries() {
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -14,19 +13,27 @@ import UIKit
|
||||||
|
|
||||||
class ItemViewModel: ViewModel {
|
class ItemViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var item: BaseItemDto
|
@Published
|
||||||
@Published var playButtonItem: BaseItemDto? {
|
var item: BaseItemDto
|
||||||
|
@Published
|
||||||
|
var playButtonItem: BaseItemDto? {
|
||||||
didSet {
|
didSet {
|
||||||
if let playButtonItem = playButtonItem {
|
if let playButtonItem = playButtonItem {
|
||||||
refreshItemVideoPlayerViewModel(for: playButtonItem)
|
refreshItemVideoPlayerViewModel(for: playButtonItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var similarItems: [BaseItemDto] = []
|
|
||||||
@Published var isWatched = false
|
@Published
|
||||||
@Published var isFavorited = false
|
var similarItems: [BaseItemDto] = []
|
||||||
@Published var informationItems: [BaseItemDto.ItemDetail]
|
@Published
|
||||||
@Published var mediaItems: [BaseItemDto.ItemDetail]
|
var isWatched = false
|
||||||
|
@Published
|
||||||
|
var isFavorited = false
|
||||||
|
@Published
|
||||||
|
var informationItems: [BaseItemDto.ItemDetail]
|
||||||
|
@Published
|
||||||
|
var mediaItems: [BaseItemDto.ItemDetail]
|
||||||
var itemVideoPlayerViewModel: VideoPlayerViewModel?
|
var itemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||||
|
|
||||||
init(item: BaseItemDto) {
|
init(item: BaseItemDto) {
|
||||||
|
@ -70,15 +77,16 @@ class ItemViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getItemDisplayName() -> String {
|
func getItemDisplayName() -> String {
|
||||||
return item.name ?? ""
|
item.name ?? ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func shouldDisplayRuntime() -> Bool {
|
func shouldDisplayRuntime() -> Bool {
|
||||||
return true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSimilarItems() {
|
func getSimilarItems() {
|
||||||
LibraryAPI.getSimilarItems(itemId: item.id!, userId: SessionManager.main.currentLogin.user.id, limit: 20, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
LibraryAPI.getSimilarItems(itemId: item.id!, userId: SessionManager.main.currentLogin.user.id, limit: 20,
|
||||||
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people])
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
final class MovieItemViewModel: ItemViewModel {
|
final class MovieItemViewModel: ItemViewModel {}
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -14,9 +13,12 @@ import Stinsen
|
||||||
|
|
||||||
final class SeasonItemViewModel: ItemViewModel {
|
final class SeasonItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
@RouterObject var itemRouter: ItemCoordinator.Router?
|
@RouterObject
|
||||||
@Published var episodes: [BaseItemDto] = []
|
var itemRouter: ItemCoordinator.Router?
|
||||||
@Published var seriesItem: BaseItemDto?
|
@Published
|
||||||
|
var episodes: [BaseItemDto] = []
|
||||||
|
@Published
|
||||||
|
var seriesItem: BaseItemDto?
|
||||||
|
|
||||||
override init(item: BaseItemDto) {
|
override init(item: BaseItemDto) {
|
||||||
super.init(item: item)
|
super.init(item: item)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class SeriesItemViewModel: ItemViewModel {
|
final class SeriesItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
@Published var seasons: [BaseItemDto] = []
|
@Published
|
||||||
|
var seasons: [BaseItemDto] = []
|
||||||
|
|
||||||
override init(item: BaseItemDto) {
|
override init(item: BaseItemDto) {
|
||||||
super.init(item: item)
|
super.init(item: item)
|
||||||
|
@ -29,13 +29,15 @@ final class SeriesItemViewModel: ItemViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
override func shouldDisplayRuntime() -> Bool {
|
override func shouldDisplayRuntime() -> Bool {
|
||||||
return false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getNextUp() {
|
private func getNextUp() {
|
||||||
|
|
||||||
LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
|
LogManager.shared.log.debug("Getting next up for show \(self.item.id!) (\(self.item.name!))")
|
||||||
TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], seriesId: self.item.id!, enableUserData: true)
|
TvShowsAPI.getNextUp(userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
|
seriesId: self.item.id!, enableUserData: true)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
@ -67,7 +69,9 @@ final class SeriesItemViewModel: ItemViewModel {
|
||||||
|
|
||||||
private func requestSeasons() {
|
private func requestSeasons() {
|
||||||
LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))")
|
LogManager.shared.log.debug("Getting seasons of show \(self.item.id!) (\(self.item.name!))")
|
||||||
TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.main.currentLogin.user.id, fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people], enableUserData: true)
|
TvShowsAPI.getSeasons(seriesId: item.id ?? "", userId: SessionManager.main.currentLogin.user.id,
|
||||||
|
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people],
|
||||||
|
enableUserData: true)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -13,7 +12,8 @@ import JellyfinAPI
|
||||||
|
|
||||||
final class LatestMediaViewModel: ViewModel {
|
final class LatestMediaViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var items = [BaseItemDto]()
|
@Published
|
||||||
|
var items = [BaseItemDto]()
|
||||||
|
|
||||||
let library: BaseItemDto
|
let library: BaseItemDto
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ final class LatestMediaViewModel: ViewModel {
|
||||||
.seasonUserData,
|
.seasonUserData,
|
||||||
.overview,
|
.overview,
|
||||||
.genres,
|
.genres,
|
||||||
.people
|
.people,
|
||||||
],
|
],
|
||||||
includeItemTypes: ["Series", "Movie"],
|
includeItemTypes: ["Series", "Movie"],
|
||||||
enableUserData: true, limit: 12)
|
enableUserData: true, limit: 12)
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -21,16 +20,25 @@ enum FilterType {
|
||||||
|
|
||||||
final class LibraryFilterViewModel: ViewModel {
|
final class LibraryFilterViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var modifiedFilters = LibraryFilters()
|
@Published
|
||||||
|
var modifiedFilters = LibraryFilters()
|
||||||
|
|
||||||
@Published var possibleGenres = [NameGuidPair]()
|
@Published
|
||||||
@Published var possibleTags = [String]()
|
var possibleGenres = [NameGuidPair]()
|
||||||
@Published var possibleSortOrders = APISortOrder.allCases
|
@Published
|
||||||
@Published var possibleSortBys = SortBy.allCases
|
var possibleTags = [String]()
|
||||||
@Published var possibleItemFilters = ItemFilter.supportedTypes
|
@Published
|
||||||
@Published var enabledFilterType: [FilterType]
|
var possibleSortOrders = APISortOrder.allCases
|
||||||
@Published var selectedSortOrder: APISortOrder = .descending
|
@Published
|
||||||
@Published var selectedSortBy: SortBy = .name
|
var possibleSortBys = SortBy.allCases
|
||||||
|
@Published
|
||||||
|
var possibleItemFilters = ItemFilter.supportedTypes
|
||||||
|
@Published
|
||||||
|
var enabledFilterType: [FilterType]
|
||||||
|
@Published
|
||||||
|
var selectedSortOrder: APISortOrder = .descending
|
||||||
|
@Published
|
||||||
|
var selectedSortBy: SortBy = .name
|
||||||
|
|
||||||
var parentId: String = ""
|
var parentId: String = ""
|
||||||
|
|
||||||
|
@ -44,7 +52,8 @@ final class LibraryFilterViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
init(filters: LibraryFilters? = nil,
|
init(filters: LibraryFilters? = nil,
|
||||||
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter], parentId: String) {
|
enabledFilterType: [FilterType] = [.tag, .genre, .sortBy, .sortOrder, .filter], parentId: String)
|
||||||
|
{
|
||||||
self.enabledFilterType = enabledFilterType
|
self.enabledFilterType = enabledFilterType
|
||||||
self.selectedSortBy = filters?.sortBy.first ?? .name
|
self.selectedSortBy = filters?.sortBy.first ?? .name
|
||||||
self.selectedSortOrder = filters?.sortOrder.first ?? .descending
|
self.selectedSortOrder = filters?.sortOrder.first ?? .descending
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
final class LibraryListViewModel: ViewModel {
|
final class LibraryListViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var libraries: [BaseItemDto] = []
|
@Published
|
||||||
|
var libraries: [BaseItemDto] = []
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
var withFavorites = LibraryFilters(filters: [.isFavorite], sortOrder: [], withGenres: [], sortBy: [])
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import CombineExt
|
import CombineExt
|
||||||
|
@ -15,15 +14,21 @@ import SwiftUI
|
||||||
|
|
||||||
final class LibrarySearchViewModel: ViewModel {
|
final class LibrarySearchViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var supportedItemTypeList = [ItemType]()
|
@Published
|
||||||
|
var supportedItemTypeList = [ItemType]()
|
||||||
|
|
||||||
@Published var selectedItemType: ItemType = .movie
|
@Published
|
||||||
|
var selectedItemType: ItemType = .movie
|
||||||
|
|
||||||
@Published var movieItems = [BaseItemDto]()
|
@Published
|
||||||
@Published var showItems = [BaseItemDto]()
|
var movieItems = [BaseItemDto]()
|
||||||
@Published var episodeItems = [BaseItemDto]()
|
@Published
|
||||||
|
var showItems = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var episodeItems = [BaseItemDto]()
|
||||||
|
|
||||||
@Published var suggestions = [BaseItemDto]()
|
@Published
|
||||||
|
var suggestions = [BaseItemDto]()
|
||||||
|
|
||||||
var searchQuerySubject = CurrentValueSubject<String, Never>("")
|
var searchQuerySubject = CurrentValueSubject<String, Never>("")
|
||||||
var parentID: String?
|
var parentID: String?
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -26,16 +25,23 @@ final class LibraryViewModel: ViewModel {
|
||||||
var genre: NameGuidPair?
|
var genre: NameGuidPair?
|
||||||
var studio: NameGuidPair?
|
var studio: NameGuidPair?
|
||||||
|
|
||||||
@Published var items = [BaseItemDto]()
|
@Published
|
||||||
@Published var rows = [LibraryRow]()
|
var items = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var rows = [LibraryRow]()
|
||||||
|
|
||||||
@Published var totalPages = 0
|
@Published
|
||||||
@Published var currentPage = 0
|
var totalPages = 0
|
||||||
@Published var hasNextPage = false
|
@Published
|
||||||
@Published var hasPreviousPage = false
|
var currentPage = 0
|
||||||
|
@Published
|
||||||
|
var hasNextPage = false
|
||||||
|
@Published
|
||||||
|
var hasPreviousPage = false
|
||||||
|
|
||||||
// temp
|
// temp
|
||||||
@Published var filters: LibraryFilters
|
@Published
|
||||||
|
var filters: LibraryFilters
|
||||||
|
|
||||||
private let columns: Int
|
private let columns: Int
|
||||||
private var libraries = [BaseItemDto]()
|
private var libraries = [BaseItemDto]()
|
||||||
|
@ -86,8 +92,17 @@ final class LibraryViewModel: ViewModel {
|
||||||
searchTerm: nil,
|
searchTerm: nil,
|
||||||
sortOrder: filters.sortOrder,
|
sortOrder: filters.sortOrder,
|
||||||
parentId: parentID,
|
parentId: parentID,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
fields: [
|
||||||
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
|
includeItemTypes: filters.filters
|
||||||
|
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series", "BoxSet"],
|
||||||
filters: filters.filters,
|
filters: filters.filters,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
tags: filters.tags,
|
tags: filters.tags,
|
||||||
|
@ -129,8 +144,17 @@ final class LibraryViewModel: ViewModel {
|
||||||
searchTerm: nil,
|
searchTerm: nil,
|
||||||
sortOrder: filters.sortOrder,
|
sortOrder: filters.sortOrder,
|
||||||
parentId: parentID,
|
parentId: parentID,
|
||||||
fields: [.primaryImageAspectRatio, .seriesPrimaryImage, .seasonUserData, .overview, .genres, .people, .chapters],
|
fields: [
|
||||||
includeItemTypes: filters.filters.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
.primaryImageAspectRatio,
|
||||||
|
.seriesPrimaryImage,
|
||||||
|
.seasonUserData,
|
||||||
|
.overview,
|
||||||
|
.genres,
|
||||||
|
.people,
|
||||||
|
.chapters,
|
||||||
|
],
|
||||||
|
includeItemTypes: filters.filters
|
||||||
|
.contains(.isFavorite) ? ["Movie", "Series", "Season", "Episode"] : ["Movie", "Series"],
|
||||||
filters: filters.filters,
|
filters: filters.filters,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
tags: filters.tags,
|
tags: filters.tags,
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
@ -26,8 +25,10 @@ struct LiveTVChannelProgram: Hashable {
|
||||||
|
|
||||||
final class LiveTVChannelsViewModel: ViewModel {
|
final class LiveTVChannelsViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var channels = [BaseItemDto]()
|
@Published
|
||||||
@Published var channelPrograms = [LiveTVChannelProgram]() {
|
var channels = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var channelPrograms = [LiveTVChannelProgram]() {
|
||||||
didSet {
|
didSet {
|
||||||
rows = []
|
rows = []
|
||||||
let rowChannels = channelPrograms.chunked(into: 4)
|
let rowChannels = channelPrograms.chunked(into: 4)
|
||||||
|
@ -36,7 +37,9 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var rows = [LiveTVChannelRow]()
|
|
||||||
|
@Published
|
||||||
|
var rows = [LiveTVChannelRow]()
|
||||||
|
|
||||||
private var programs = [BaseItemDto]()
|
private var programs = [BaseItemDto]()
|
||||||
private var channelProgramsList = [BaseItemDto: [BaseItemDto]]()
|
private var channelProgramsList = [BaseItemDto: [BaseItemDto]]()
|
||||||
|
@ -64,7 +67,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
}, receiveValue: { [weak self] response in
|
}, receiveValue: { [weak self] _ in
|
||||||
LogManager.shared.log.debug("Received Guide Info")
|
LogManager.shared.log.debug("Received Guide Info")
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
self.getChannels()
|
self.getChannels()
|
||||||
|
@ -73,14 +76,12 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getChannels() {
|
func getChannels() {
|
||||||
LiveTvAPI.getLiveTvChannels(
|
LiveTvAPI.getLiveTvChannels(userId: SessionManager.main.currentLogin.user.id,
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
enableImageTypes: [.primary],
|
enableImageTypes: [.primary],
|
||||||
enableUserData: false,
|
enableUserData: false,
|
||||||
enableFavoriteSorting: true
|
enableFavoriteSorting: true)
|
||||||
)
|
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
@ -95,17 +96,16 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
|
|
||||||
private func getPrograms() {
|
private func getPrograms() {
|
||||||
// http://192.168.1.50:8096/LiveTv/Programs
|
// http://192.168.1.50:8096/LiveTv/Programs
|
||||||
guard channels.count > 0 else {
|
guard !channels.isEmpty else {
|
||||||
LogManager.shared.log.debug("Cannot get programs, channels list empty. ")
|
LogManager.shared.log.debug("Cannot get programs, channels list empty. ")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let channelIds = channels.compactMap { $0.id }
|
let channelIds = channels.compactMap(\.id)
|
||||||
|
|
||||||
let minEndDate = Date.now.addComponentsToDate(hours: -1)
|
let minEndDate = Date.now.addComponentsToDate(hours: -1)
|
||||||
let maxStartDate = minEndDate.addComponentsToDate(hours: 6)
|
let maxStartDate = minEndDate.addComponentsToDate(hours: 6)
|
||||||
|
|
||||||
let getProgramsDto = GetProgramsDto(
|
let getProgramsDto = GetProgramsDto(channelIds: channelIds,
|
||||||
channelIds: channelIds,
|
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
userId: SessionManager.main.currentLogin.user.id,
|
||||||
maxStartDate: maxStartDate,
|
maxStartDate: maxStartDate,
|
||||||
minEndDate: minEndDate,
|
minEndDate: minEndDate,
|
||||||
|
@ -114,8 +114,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
imageTypeLimit: 1,
|
imageTypeLimit: 1,
|
||||||
enableImageTypes: [.primary],
|
enableImageTypes: [.primary],
|
||||||
enableUserData: false
|
enableUserData: false)
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -146,7 +145,8 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
if let startDate = prg.startDate,
|
if let startDate = prg.startDate,
|
||||||
let endDate = prg.endDate,
|
let endDate = prg.endDate,
|
||||||
now.timeIntervalSinceReferenceDate > startDate.timeIntervalSinceReferenceDate &&
|
now.timeIntervalSinceReferenceDate > startDate.timeIntervalSinceReferenceDate &&
|
||||||
now.timeIntervalSinceReferenceDate < endDate.timeIntervalSinceReferenceDate {
|
now.timeIntervalSinceReferenceDate < endDate.timeIntervalSinceReferenceDate
|
||||||
|
{
|
||||||
currentPrg = prg
|
currentPrg = prg
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
if let existingTimer = timer {
|
if let existingTimer = timer {
|
||||||
existingTimer.invalidate()
|
existingTimer.invalidate()
|
||||||
}
|
}
|
||||||
timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] timer in
|
timer = Timer(fire: nextMinute, interval: 60 * 10, repeats: true) { [weak self] _ in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
LogManager.shared.log.debug("LiveTVChannels schedule check...")
|
LogManager.shared.log.debug("LiveTVChannels schedule check...")
|
||||||
DispatchQueue.global(qos: .background).async {
|
DispatchQueue.global(qos: .background).async {
|
||||||
|
@ -193,7 +193,7 @@ final class LiveTVChannelsViewModel: ViewModel {
|
||||||
|
|
||||||
extension Array {
|
extension Array {
|
||||||
func chunked(into size: Int) -> [[Element]] {
|
func chunked(into size: Int) -> [[Element]] {
|
||||||
return stride(from: 0, to: count, by: size).map {
|
stride(from: 0, to: count, by: size).map {
|
||||||
Array(self[$0 ..< Swift.min($0 + size, count)])
|
Array(self[$0 ..< Swift.min($0 + size, count)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,28 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
final class LiveTVProgramsViewModel: ViewModel {
|
final class LiveTVProgramsViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var recommendedItems = [BaseItemDto]()
|
@Published
|
||||||
@Published var seriesItems = [BaseItemDto]()
|
var recommendedItems = [BaseItemDto]()
|
||||||
@Published var movieItems = [BaseItemDto]()
|
@Published
|
||||||
@Published var sportsItems = [BaseItemDto]()
|
var seriesItems = [BaseItemDto]()
|
||||||
@Published var kidsItems = [BaseItemDto]()
|
@Published
|
||||||
@Published var newsItems = [BaseItemDto]()
|
var movieItems = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var sportsItems = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var kidsItems = [BaseItemDto]()
|
||||||
|
@Published
|
||||||
|
var newsItems = [BaseItemDto]()
|
||||||
|
|
||||||
private var channels = [String: BaseItemDto]()
|
private var channels = [String: BaseItemDto]()
|
||||||
|
|
||||||
|
@ -28,18 +33,16 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
func findChannel(id: String) -> BaseItemDto? {
|
func findChannel(id: String) -> BaseItemDto? {
|
||||||
return channels[id]
|
channels[id]
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getChannels() {
|
private func getChannels() {
|
||||||
LiveTvAPI.getLiveTvChannels(
|
LiveTvAPI.getLiveTvChannels(userId: SessionManager.main.currentLogin.user.id,
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
limit: 1000,
|
limit: 1000,
|
||||||
enableImageTypes: [.primary],
|
enableImageTypes: [.primary],
|
||||||
enableUserData: false,
|
enableUserData: false,
|
||||||
enableFavoriteSorting: true
|
enableFavoriteSorting: true)
|
||||||
)
|
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
@ -64,15 +67,13 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func getRecommendedPrograms() {
|
private func getRecommendedPrograms() {
|
||||||
LiveTvAPI.getRecommendedPrograms(
|
LiveTvAPI.getRecommendedPrograms(userId: SessionManager.main.currentLogin.user.id,
|
||||||
userId: SessionManager.main.currentLogin.user.id,
|
|
||||||
limit: 9,
|
limit: 9,
|
||||||
isAiring: true,
|
isAiring: true,
|
||||||
imageTypeLimit: 1,
|
imageTypeLimit: 1,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio],
|
fields: [.channelInfo, .primaryImageAspectRatio],
|
||||||
enableTotalRecordCount: false
|
enableTotalRecordCount: false)
|
||||||
)
|
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { [weak self] completion in
|
.sink(receiveCompletion: { [weak self] completion in
|
||||||
self?.handleAPIRequestError(completion: completion)
|
self?.handleAPIRequestError(completion: completion)
|
||||||
|
@ -95,8 +96,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
limit: 9,
|
limit: 9,
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -121,8 +121,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
limit: 9,
|
limit: 9,
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -143,8 +142,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
limit: 9,
|
limit: 9,
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -165,8 +163,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
limit: 9,
|
limit: 9,
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
@ -187,8 +184,7 @@ final class LiveTVProgramsViewModel: ViewModel {
|
||||||
limit: 9,
|
limit: 9,
|
||||||
enableTotalRecordCount: false,
|
enableTotalRecordCount: false,
|
||||||
enableImageTypes: [.primary, .thumb],
|
enableImageTypes: [.primary, .thumb],
|
||||||
fields: [.channelInfo, .primaryImageAspectRatio]
|
fields: [.channelInfo, .primaryImageAspectRatio])
|
||||||
)
|
|
||||||
|
|
||||||
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
LiveTvAPI.getPrograms(getProgramsDto: getProgramsDto)
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
final class MainTabViewModel: ViewModel {
|
final class MainTabViewModel: ViewModel {
|
||||||
@Published var backgroundURL: URL?
|
@Published
|
||||||
@Published var lastBackgroundURL: URL?
|
var backgroundURL: URL?
|
||||||
@Published var backgroundBlurHash: String = "001fC^"
|
@Published
|
||||||
|
var lastBackgroundURL: URL?
|
||||||
|
@Published
|
||||||
|
var backgroundBlurHash: String = "001fC^"
|
||||||
|
|
||||||
override init() {
|
override init() {
|
||||||
super.init()
|
super.init()
|
||||||
|
@ -22,7 +24,8 @@ final class MainTabViewModel: ViewModel {
|
||||||
nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil)
|
nc.addObserver(self, selector: #selector(backgroundDidChange), name: Notification.Name("backgroundDidChange"), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func backgroundDidChange() {
|
@objc
|
||||||
|
func backgroundDidChange() {
|
||||||
self.lastBackgroundURL = self.backgroundURL
|
self.lastBackgroundURL = self.backgroundURL
|
||||||
self.backgroundURL = BackgroundManager.current.backgroundURL
|
self.backgroundURL = BackgroundManager.current.backgroundURL
|
||||||
self.backgroundBlurHash = BackgroundManager.current.blurhash
|
self.backgroundBlurHash = BackgroundManager.current.blurhash
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -15,11 +14,16 @@ import SwiftUICollection
|
||||||
|
|
||||||
final class MovieLibrariesViewModel: ViewModel {
|
final class MovieLibrariesViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var rows = [LibraryRow]()
|
@Published
|
||||||
@Published var totalPages = 0
|
var rows = [LibraryRow]()
|
||||||
@Published var currentPage = 0
|
@Published
|
||||||
@Published var hasNextPage = false
|
var totalPages = 0
|
||||||
@Published var hasPreviousPage = false
|
@Published
|
||||||
|
var currentPage = 0
|
||||||
|
@Published
|
||||||
|
var hasNextPage = false
|
||||||
|
@Published
|
||||||
|
var hasPreviousPage = false
|
||||||
|
|
||||||
private var libraries = [BaseItemDto]()
|
private var libraries = [BaseItemDto]()
|
||||||
private let columns: Int
|
private let columns: Int
|
||||||
|
@ -27,9 +31,7 @@ final class MovieLibrariesViewModel: ViewModel {
|
||||||
@RouterObject
|
@RouterObject
|
||||||
var router: MovieLibrariesCoordinator.Router?
|
var router: MovieLibrariesCoordinator.Router?
|
||||||
|
|
||||||
init(
|
init(columns: Int = 7) {
|
||||||
columns: Int = 7
|
|
||||||
) {
|
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -38,8 +40,7 @@ final class MovieLibrariesViewModel: ViewModel {
|
||||||
|
|
||||||
func requestLibraries() {
|
func requestLibraries() {
|
||||||
|
|
||||||
UserViewsAPI.getUserViews(
|
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||||
userId: SessionManager.main.currentLogin.user.id)
|
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
@ -62,10 +63,10 @@ final class MovieLibrariesViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func calculateRows() -> [LibraryRow] {
|
private func calculateRows() -> [LibraryRow] {
|
||||||
guard libraries.count > 0 else { return [] }
|
guard !libraries.isEmpty else { return [] }
|
||||||
let rowCount = libraries.count / columns
|
let rowCount = libraries.count / columns
|
||||||
var calculatedRows = [LibraryRow]()
|
var calculatedRows = [LibraryRow]()
|
||||||
for i in (0...rowCount) {
|
for i in 0 ... rowCount {
|
||||||
let firstItemIndex = i * columns
|
let firstItemIndex = i * columns
|
||||||
var lastItemIndex = firstItemIndex + columns
|
var lastItemIndex = firstItemIndex + columns
|
||||||
if lastItemIndex > libraries.count {
|
if lastItemIndex > libraries.count {
|
||||||
|
@ -83,12 +84,8 @@ final class MovieLibrariesViewModel: ViewModel {
|
||||||
rowCells.append(loadingCell)
|
rowCells.append(loadingCell)
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedRows.append(
|
calculatedRows.append(LibraryRow(section: i,
|
||||||
LibraryRow(
|
items: rowCells))
|
||||||
section: i,
|
|
||||||
items: rowCells
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return calculatedRows
|
return calculatedRows
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
class ServerDetailViewModel: ViewModel {
|
class ServerDetailViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var server: SwiftfinStore.State.Server
|
@Published
|
||||||
|
var server: SwiftfinStore.State.Server
|
||||||
|
|
||||||
init(server: SwiftfinStore.State.Server) {
|
init(server: SwiftfinStore.State.Server) {
|
||||||
self.server = server
|
self.server = server
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class ServerListViewModel: ObservableObject {
|
class ServerListViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var servers: [SwiftfinStore.State.Server] = []
|
@Published
|
||||||
|
var servers: [SwiftfinStore.State.Server] = []
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ class ServerListViewModel: ObservableObject {
|
||||||
fetchServers()
|
fetchServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func didPurge() {
|
@objc
|
||||||
|
private func didPurge() {
|
||||||
fetchServers()
|
fetchServers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
|
import Defaults
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Defaults
|
|
||||||
|
|
||||||
final class SettingsViewModel: ObservableObject {
|
final class SettingsViewModel: ObservableObject {
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -15,11 +14,16 @@ import SwiftUICollection
|
||||||
|
|
||||||
final class TVLibrariesViewModel: ViewModel {
|
final class TVLibrariesViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var rows = [LibraryRow]()
|
@Published
|
||||||
@Published var totalPages = 0
|
var rows = [LibraryRow]()
|
||||||
@Published var currentPage = 0
|
@Published
|
||||||
@Published var hasNextPage = false
|
var totalPages = 0
|
||||||
@Published var hasPreviousPage = false
|
@Published
|
||||||
|
var currentPage = 0
|
||||||
|
@Published
|
||||||
|
var hasNextPage = false
|
||||||
|
@Published
|
||||||
|
var hasPreviousPage = false
|
||||||
|
|
||||||
private var libraries = [BaseItemDto]()
|
private var libraries = [BaseItemDto]()
|
||||||
private let columns: Int
|
private let columns: Int
|
||||||
|
@ -27,9 +31,7 @@ final class TVLibrariesViewModel: ViewModel {
|
||||||
@RouterObject
|
@RouterObject
|
||||||
var router: TVLibrariesCoordinator.Router?
|
var router: TVLibrariesCoordinator.Router?
|
||||||
|
|
||||||
init(
|
init(columns: Int = 7) {
|
||||||
columns: Int = 7
|
|
||||||
) {
|
|
||||||
self.columns = columns
|
self.columns = columns
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
|
@ -38,8 +40,7 @@ final class TVLibrariesViewModel: ViewModel {
|
||||||
|
|
||||||
func requestLibraries() {
|
func requestLibraries() {
|
||||||
|
|
||||||
UserViewsAPI.getUserViews(
|
UserViewsAPI.getUserViews(userId: SessionManager.main.currentLogin.user.id)
|
||||||
userId: SessionManager.main.currentLogin.user.id)
|
|
||||||
.trackActivity(loading)
|
.trackActivity(loading)
|
||||||
.sink(receiveCompletion: { completion in
|
.sink(receiveCompletion: { completion in
|
||||||
self.handleAPIRequestError(completion: completion)
|
self.handleAPIRequestError(completion: completion)
|
||||||
|
@ -62,10 +63,10 @@ final class TVLibrariesViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func calculateRows() -> [LibraryRow] {
|
private func calculateRows() -> [LibraryRow] {
|
||||||
guard libraries.count > 0 else { return [] }
|
guard !libraries.isEmpty else { return [] }
|
||||||
let rowCount = libraries.count / columns
|
let rowCount = libraries.count / columns
|
||||||
var calculatedRows = [LibraryRow]()
|
var calculatedRows = [LibraryRow]()
|
||||||
for i in (0...rowCount) {
|
for i in 0 ... rowCount {
|
||||||
let firstItemIndex = i * columns
|
let firstItemIndex = i * columns
|
||||||
var lastItemIndex = firstItemIndex + columns
|
var lastItemIndex = firstItemIndex + columns
|
||||||
if lastItemIndex > libraries.count {
|
if lastItemIndex > libraries.count {
|
||||||
|
@ -83,12 +84,8 @@ final class TVLibrariesViewModel: ViewModel {
|
||||||
rowCells.append(loadingCell)
|
rowCells.append(loadingCell)
|
||||||
}
|
}
|
||||||
|
|
||||||
calculatedRows.append(
|
calculatedRows.append(LibraryRow(section: i,
|
||||||
LibraryRow(
|
items: rowCells))
|
||||||
section: i,
|
|
||||||
items: rowCells
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return calculatedRows
|
return calculatedRows
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
class UserListViewModel: ViewModel {
|
class UserListViewModel: ViewModel {
|
||||||
|
|
||||||
@Published var users: [SwiftfinStore.State.User] = []
|
@Published
|
||||||
|
var users: [SwiftfinStore.State.User] = []
|
||||||
|
|
||||||
var server: SwiftfinStore.State.Server
|
var server: SwiftfinStore.State.Server
|
||||||
|
|
||||||
|
@ -22,10 +22,12 @@ class UserListViewModel: ViewModel {
|
||||||
super.init()
|
super.init()
|
||||||
|
|
||||||
let nc = SwiftfinNotificationCenter.main
|
let nc = SwiftfinNotificationCenter.main
|
||||||
nc.addObserver(self, selector: #selector(didChangeCurrentLoginURI), name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI, object: nil)
|
nc.addObserver(self, selector: #selector(didChangeCurrentLoginURI), name: SwiftfinNotificationCenter.Keys.didChangeServerCurrentURI,
|
||||||
|
object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func didChangeCurrentLoginURI(_ notification: Notification) {
|
@objc
|
||||||
|
func didChangeCurrentLoginURI(_ notification: Notification) {
|
||||||
guard let newServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new state server") }
|
guard let newServerState = notification.object as? SwiftfinStore.State.Server else { fatalError("Need to have new state server") }
|
||||||
self.server = newServerState
|
self.server = newServerState
|
||||||
}
|
}
|
||||||
|
@ -43,5 +45,4 @@ class UserListViewModel: ViewModel {
|
||||||
SessionManager.main.delete(user: user)
|
SessionManager.main.delete(user: user)
|
||||||
fetchUsers()
|
fetchUsers()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import CoreStore
|
import CoreStore
|
||||||
import Foundation
|
import Foundation
|
||||||
|
@ -13,7 +12,8 @@ import Stinsen
|
||||||
|
|
||||||
final class UserSignInViewModel: ViewModel {
|
final class UserSignInViewModel: ViewModel {
|
||||||
|
|
||||||
@RouterObject var router: UserSignInCoordinator.Router?
|
@RouterObject
|
||||||
|
var router: UserSignInCoordinator.Router?
|
||||||
let server: SwiftfinStore.State.Server
|
let server: SwiftfinStore.State.Server
|
||||||
|
|
||||||
init(server: SwiftfinStore.State.Server) {
|
init(server: SwiftfinStore.State.Server) {
|
||||||
|
@ -39,7 +39,6 @@ final class UserSignInViewModel: ViewModel {
|
||||||
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
|
self.handleAPIRequestError(displayMessage: "Unable to connect to server.", logLevel: .critical, tag: "login",
|
||||||
completion: completion)
|
completion: completion)
|
||||||
} receiveValue: { _ in
|
} receiveValue: { _ in
|
||||||
|
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import SwiftUI
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
struct Subtitle {
|
struct Subtitle {
|
||||||
var name: String
|
var name: String
|
||||||
|
@ -26,6 +25,8 @@ struct AudioTrack {
|
||||||
}
|
}
|
||||||
|
|
||||||
class PlaybackItem: ObservableObject {
|
class PlaybackItem: ObservableObject {
|
||||||
@Published var videoType: PlayMethod = .directPlay
|
@Published
|
||||||
@Published var videoUrl: URL = URL(string: "https://example.com")!
|
var videoType: PlayMethod = .directPlay
|
||||||
|
@Published
|
||||||
|
var videoUrl = URL(string: "https://example.com")!
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
import Combine
|
import Combine
|
||||||
import Defaults
|
import Defaults
|
||||||
|
@ -25,11 +24,16 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
|
|
||||||
// Manually kept state because VLCKit doesn't properly set "played"
|
// Manually kept state because VLCKit doesn't properly set "played"
|
||||||
// on the VLCMediaPlayer object
|
// on the VLCMediaPlayer object
|
||||||
@Published var playerState: VLCMediaPlayerState = .buffering
|
@Published
|
||||||
@Published var leftLabelText: String = "--:--"
|
var playerState: VLCMediaPlayerState = .buffering
|
||||||
@Published var rightLabelText: String = "--:--"
|
@Published
|
||||||
@Published var playbackSpeed: PlaybackSpeed = .one
|
var leftLabelText: String = "--:--"
|
||||||
@Published var subtitlesEnabled: Bool {
|
@Published
|
||||||
|
var rightLabelText: String = "--:--"
|
||||||
|
@Published
|
||||||
|
var playbackSpeed: PlaybackSpeed = .one
|
||||||
|
@Published
|
||||||
|
var subtitlesEnabled: Bool {
|
||||||
didSet {
|
didSet {
|
||||||
if syncSubtitleStateWithAdjacent {
|
if syncSubtitleStateWithAdjacent {
|
||||||
previousItemVideoPlayerViewModel?.matchSubtitlesEnabled(with: self)
|
previousItemVideoPlayerViewModel?.matchSubtitlesEnabled(with: self)
|
||||||
|
@ -37,8 +41,11 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var selectedAudioStreamIndex: Int
|
|
||||||
@Published var selectedSubtitleStreamIndex: Int {
|
@Published
|
||||||
|
var selectedAudioStreamIndex: Int
|
||||||
|
@Published
|
||||||
|
var selectedSubtitleStreamIndex: Int {
|
||||||
didSet {
|
didSet {
|
||||||
if syncSubtitleStateWithAdjacent {
|
if syncSubtitleStateWithAdjacent {
|
||||||
previousItemVideoPlayerViewModel?.matchSubtitleStream(with: self)
|
previousItemVideoPlayerViewModel?.matchSubtitleStream(with: self)
|
||||||
|
@ -46,26 +53,37 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
|
||||||
@Published var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
@Published
|
||||||
@Published var jumpBackwardLength: VideoPlayerJumpLength {
|
var previousItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||||
|
@Published
|
||||||
|
var nextItemVideoPlayerViewModel: VideoPlayerViewModel?
|
||||||
|
@Published
|
||||||
|
var jumpBackwardLength: VideoPlayerJumpLength {
|
||||||
willSet {
|
willSet {
|
||||||
Defaults[.videoPlayerJumpBackward] = newValue
|
Defaults[.videoPlayerJumpBackward] = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var jumpForwardLength: VideoPlayerJumpLength {
|
|
||||||
|
@Published
|
||||||
|
var jumpForwardLength: VideoPlayerJumpLength {
|
||||||
willSet {
|
willSet {
|
||||||
Defaults[.videoPlayerJumpForward] = newValue
|
Defaults[.videoPlayerJumpForward] = newValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var sliderIsScrubbing: Bool = false
|
|
||||||
@Published var sliderPercentage: Double = 0 {
|
@Published
|
||||||
|
var sliderIsScrubbing: Bool = false
|
||||||
|
@Published
|
||||||
|
var sliderPercentage: Double = 0 {
|
||||||
willSet {
|
willSet {
|
||||||
sliderScrubbingSubject.send(self)
|
sliderScrubbingSubject.send(self)
|
||||||
sliderPercentageChanged(newValue: newValue)
|
sliderPercentageChanged(newValue: newValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Published var autoplayEnabled: Bool {
|
|
||||||
|
@Published
|
||||||
|
var autoplayEnabled: Bool {
|
||||||
willSet {
|
willSet {
|
||||||
previousItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
previousItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
||||||
nextItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
nextItemVideoPlayerViewModel?.autoplayEnabled = newValue
|
||||||
|
@ -81,6 +99,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
let shouldShowJumpButtonsInOverlayMenu: Bool
|
let shouldShowJumpButtonsInOverlayMenu: Bool
|
||||||
|
|
||||||
// MARK: General
|
// MARK: General
|
||||||
|
|
||||||
let item: BaseItemDto
|
let item: BaseItemDto
|
||||||
let title: String
|
let title: String
|
||||||
let subtitle: String?
|
let subtitle: String?
|
||||||
|
@ -93,9 +112,11 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
let streamType: ServerStreamType
|
let streamType: ServerStreamType
|
||||||
|
|
||||||
// MARK: Experimental
|
// MARK: Experimental
|
||||||
|
|
||||||
let syncSubtitleStateWithAdjacent: Bool
|
let syncSubtitleStateWithAdjacent: Bool
|
||||||
|
|
||||||
// MARK: tvOS
|
// MARK: tvOS
|
||||||
|
|
||||||
let confirmClose: Bool
|
let confirmClose: Bool
|
||||||
|
|
||||||
// Full response kept for convenience
|
// Full response kept for convenience
|
||||||
|
@ -114,17 +135,17 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSecondTicks: Int64 {
|
var currentSecondTicks: Int64 {
|
||||||
return Int64(currentSeconds) * 10_000_000
|
Int64(currentSeconds) * 10_000_000
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Helpers
|
// MARK: Helpers
|
||||||
|
|
||||||
var currentAudioStream: MediaStream? {
|
var currentAudioStream: MediaStream? {
|
||||||
return audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
|
audioStreams.first(where: { $0.index == selectedAudioStreamIndex })
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentSubtitleStream: MediaStream? {
|
var currentSubtitleStream: MediaStream? {
|
||||||
return subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
subtitleStreams.first(where: { $0.index == selectedSubtitleStreamIndex })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
// Necessary PassthroughSubject to capture manual scrubbing from sliders
|
||||||
|
@ -152,7 +173,8 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
overlayType: OverlayType,
|
overlayType: OverlayType,
|
||||||
shouldShowPlayPreviousItem: Bool,
|
shouldShowPlayPreviousItem: Bool,
|
||||||
shouldShowPlayNextItem: Bool,
|
shouldShowPlayNextItem: Bool,
|
||||||
shouldShowAutoPlay: Bool) {
|
shouldShowAutoPlay: Bool)
|
||||||
|
{
|
||||||
self.item = item
|
self.item = item
|
||||||
self.title = title
|
self.title = title
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
|
@ -214,6 +236,7 @@ final class VideoPlayerViewModel: ViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Adjacent Items
|
// MARK: Adjacent Items
|
||||||
|
|
||||||
extension VideoPlayerViewModel {
|
extension VideoPlayerViewModel {
|
||||||
|
|
||||||
func getAdjacentEpisodes() {
|
func getAdjacentEpisodes() {
|
||||||
|
@ -306,7 +329,8 @@ extension VideoPlayerViewModel {
|
||||||
matchSubtitlesEnabled(with: masterViewModel)
|
matchSubtitlesEnabled(with: masterViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let masterSubtitleStream = masterViewModel.subtitleStreams.first(where: { $0.index == masterViewModel.selectedSubtitleStreamIndex }),
|
guard let masterSubtitleStream = masterViewModel.subtitleStreams
|
||||||
|
.first(where: { $0.index == masterViewModel.selectedSubtitleStreamIndex }),
|
||||||
let matchingSubtitleStream = self.subtitleStreams.first(where: { mediaStreamAboutEqual($0, masterSubtitleStream) }),
|
let matchingSubtitleStream = self.subtitleStreams.first(where: { mediaStreamAboutEqual($0, masterSubtitleStream) }),
|
||||||
let matchingSubtitleStreamIndex = matchingSubtitleStream.index else { return }
|
let matchingSubtitleStreamIndex = matchingSubtitleStream.index else { return }
|
||||||
|
|
||||||
|
@ -325,24 +349,27 @@ extension VideoPlayerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func mediaStreamAboutEqual(_ lhs: MediaStream, _ rhs: MediaStream) -> Bool {
|
private func mediaStreamAboutEqual(_ lhs: MediaStream, _ rhs: MediaStream) -> Bool {
|
||||||
return lhs.displayTitle == rhs.displayTitle && lhs.language == rhs.language
|
lhs.displayTitle == rhs.displayTitle && lhs.language == rhs.language
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Progress Report Timer
|
// MARK: Progress Report Timer
|
||||||
|
|
||||||
extension VideoPlayerViewModel {
|
extension VideoPlayerViewModel {
|
||||||
|
|
||||||
private func sendNewProgressReportWithTimer() {
|
private func sendNewProgressReportWithTimer() {
|
||||||
self.progressReportTimer?.invalidate()
|
self.progressReportTimer?.invalidate()
|
||||||
self.progressReportTimer = Timer.scheduledTimer(timeInterval: 0.7, target: self, selector: #selector(_sendProgressReport), userInfo: nil, repeats: false)
|
self.progressReportTimer = Timer.scheduledTimer(timeInterval: 0.7, target: self, selector: #selector(_sendProgressReport),
|
||||||
|
userInfo: nil, repeats: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Updates
|
// MARK: Updates
|
||||||
|
|
||||||
extension VideoPlayerViewModel {
|
extension VideoPlayerViewModel {
|
||||||
|
|
||||||
|
|
||||||
// MARK: sendPlayReport
|
// MARK: sendPlayReport
|
||||||
|
|
||||||
func sendPlayReport() {
|
func sendPlayReport() {
|
||||||
|
|
||||||
self.startTimeTicks = Int64(Date().timeIntervalSince1970) * 10_000_000
|
self.startTimeTicks = Int64(Date().timeIntervalSince1970) * 10_000_000
|
||||||
|
@ -368,8 +395,7 @@ extension VideoPlayerViewModel {
|
||||||
playSessionId: response.playSessionId,
|
playSessionId: response.playSessionId,
|
||||||
repeatMode: .repeatNone,
|
repeatMode: .repeatNone,
|
||||||
nowPlayingQueue: nil,
|
nowPlayingQueue: nil,
|
||||||
playlistItemId: "playlistItem0"
|
playlistItemId: "playlistItem0")
|
||||||
)
|
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
PlaystateAPI.reportPlaybackStart(playbackStartInfo: startInfo)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
@ -381,6 +407,7 @@ extension VideoPlayerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: sendPauseReport
|
// MARK: sendPauseReport
|
||||||
|
|
||||||
func sendPauseReport(paused: Bool) {
|
func sendPauseReport(paused: Bool) {
|
||||||
|
|
||||||
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
||||||
|
@ -404,8 +431,7 @@ extension VideoPlayerViewModel {
|
||||||
playSessionId: response.playSessionId,
|
playSessionId: response.playSessionId,
|
||||||
repeatMode: .repeatNone,
|
repeatMode: .repeatNone,
|
||||||
nowPlayingQueue: nil,
|
nowPlayingQueue: nil,
|
||||||
playlistItemId: "playlistItem0"
|
playlistItemId: "playlistItem0")
|
||||||
)
|
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackStart(playbackStartInfo: pauseInfo)
|
PlaystateAPI.reportPlaybackStart(playbackStartInfo: pauseInfo)
|
||||||
.sink { completion in
|
.sink { completion in
|
||||||
|
@ -417,6 +443,7 @@ extension VideoPlayerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: sendProgressReport
|
// MARK: sendProgressReport
|
||||||
|
|
||||||
func sendProgressReport() {
|
func sendProgressReport() {
|
||||||
|
|
||||||
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
let subtitleStreamIndex = subtitlesEnabled ? selectedSubtitleStreamIndex : nil
|
||||||
|
@ -447,7 +474,8 @@ extension VideoPlayerViewModel {
|
||||||
self.sendNewProgressReportWithTimer()
|
self.sendNewProgressReportWithTimer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func _sendProgressReport() {
|
@objc
|
||||||
|
private func _sendProgressReport() {
|
||||||
guard let lastProgressReport = lastProgressReport else { return }
|
guard let lastProgressReport = lastProgressReport else { return }
|
||||||
|
|
||||||
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: lastProgressReport)
|
PlaystateAPI.reportPlaybackProgress(playbackProgressInfo: lastProgressReport)
|
||||||
|
@ -462,6 +490,7 @@ extension VideoPlayerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: sendStopReport
|
// MARK: sendStopReport
|
||||||
|
|
||||||
func sendStopReport() {
|
func sendStopReport() {
|
||||||
|
|
||||||
let stopInfo = PlaybackStopInfo(item: item,
|
let stopInfo = PlaybackStopInfo(item: item,
|
||||||
|
@ -487,6 +516,7 @@ extension VideoPlayerViewModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Embedded/Normal Subtitle Streams
|
// MARK: Embedded/Normal Subtitle Streams
|
||||||
|
|
||||||
extension VideoPlayerViewModel {
|
extension VideoPlayerViewModel {
|
||||||
|
|
||||||
func createEmbeddedSubtitleStream(with subtitleStream: MediaStream) -> URL {
|
func createEmbeddedSubtitleStream(with subtitleStream: MediaStream) -> URL {
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
//
|
//
|
||||||
/*
|
// Swiftfin is subject to the terms of the Mozilla Public
|
||||||
* SwiftFin is subject to the terms of the Mozilla Public
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
||||||
* License, v2.0. If a copy of the MPL was not distributed with this
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
//
|
||||||
*
|
// Copyright (c) 2022 Jellyfin & Jellyfin Contributors
|
||||||
* Copyright 2021 Aiden Vigue & Jellyfin Contributors
|
//
|
||||||
*/
|
|
||||||
|
|
||||||
|
import ActivityIndicator
|
||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
import ActivityIndicator
|
|
||||||
import JellyfinAPI
|
import JellyfinAPI
|
||||||
|
|
||||||
class ViewModel: ObservableObject {
|
class ViewModel: ObservableObject {
|
||||||
|
|
||||||
@Published var isLoading = false
|
@Published
|
||||||
@Published var errorMessage: ErrorMessage?
|
var isLoading = false
|
||||||
|
@Published
|
||||||
|
var errorMessage: ErrorMessage?
|
||||||
|
|
||||||
let loading = ActivityIndicator()
|
let loading = ActivityIndicator()
|
||||||
var cancellables = Set<AnyCancellable>()
|
var cancellables = Set<AnyCancellable>()
|
||||||
|
@ -24,12 +25,15 @@ class ViewModel: ObservableObject {
|
||||||
loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables)
|
loading.loading.assign(to: \.isLoading, on: self).store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAPIRequestError(displayMessage: String? = nil, logLevel: LogLevel = .error, tag: String = "", function: String = #function, file: String = #file, line: UInt = #line, completion: Subscribers.Completion<Error>) {
|
func handleAPIRequestError(displayMessage: String? = nil, logLevel: LogLevel = .error, tag: String = "", function: String = #function,
|
||||||
|
file: String = #file, line: UInt = #line, completion: Subscribers.Completion<Error>)
|
||||||
|
{
|
||||||
switch completion {
|
switch completion {
|
||||||
case .finished:
|
case .finished:
|
||||||
self.errorMessage = nil
|
self.errorMessage = nil
|
||||||
case .failure(let error):
|
case let .failure(error):
|
||||||
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line)
|
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file,
|
||||||
|
line: line)
|
||||||
|
|
||||||
switch error {
|
switch error {
|
||||||
case is ErrorResponse:
|
case is ErrorResponse:
|
||||||
|
@ -39,14 +43,17 @@ class ViewModel: ObservableObject {
|
||||||
case .error(-1, _, _, _):
|
case .error(-1, _, _, _):
|
||||||
networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
// Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
|
// Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
|
||||||
LogManager.shared.log.error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)")
|
LogManager.shared.log
|
||||||
|
.error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)")
|
||||||
case .error(-2, _, _, _):
|
case .error(-2, _, _, _):
|
||||||
networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
LogManager.shared.log.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
|
LogManager.shared.log
|
||||||
|
.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
|
||||||
default:
|
default:
|
||||||
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
|
||||||
// Able to use user-facing friendly description here since just HTTP status codes
|
// Able to use user-facing friendly description here since just HTTP status codes
|
||||||
LogManager.shared.log.error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)")
|
LogManager.shared.log
|
||||||
|
.error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.errorMessage = networkError.errorMessage
|
self.errorMessage = networkError.errorMessage
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue